ok fellas, I've been reading, researching, learning and testing about powershell. Within the last 20 days, here is what I've been able to come up with.
get-mailbox -identity $_.name | select name,userprincipalname,#{label="Size";expression={$size = Get-Mailboxstatistics $_.identity;$size.TotalItemSize.Value.ToMB()}},#{label="Items";expression={$items = Get-Mailboxstatistics $_.name;$item.ItemCount}}
I stored this in a script called accountsizes.ps1. It works exactly as I expected by outputting all the email accounts with the sizes, but in order for me to get only the mailboxes over 2048MB, I have to call it like this:
PS C:\accountsizes.ps1 | where size -gt "2048" | select userprincipalname,size
And this works by return the email addresses and mailbox sizes in MBs. But now my dilemma is; how do I enumerate through the results and extract each email address and send an email to that user and myself, warning them that their mailbox is too large and they need to archive. From what I been reading and learning, I would have to use a ForEach loop and the send-mailmessage cmdlet. I cannot figure out how to use the ForEach and incorporate it with the script: Here is I go brain dead with the ForEach:
PS C:\accountsizes.ps1 | where size -gt "2048" | select userprincipalname,size | ForEach($user in userprincipalname){$_.userprincipalname}
I do not know the right way to go about doing this (so, don't ask me why I'm doing this way :)), I have no previous knowledge about scripting and coding.
Here is my email part:
$smtpserver = "domain.com"
$smtpFrom = "me#domain.com"
$smtpTo = "you#domain.com"
$messageSubject = "Warning Email Mailbox Too Large"
$body = "blah blah blah blah"
send-mailmessage -from $smtpFrom -to $smtpTo -subject $messageSubject -body $body
Thanks in advance for your helpful advice.
The foreach keyword and the ForEach-Object cmdlet are two different things.
If you use the foreach keyword, you give a name to the iteration variable, and you iterate on a collection. For example:
$collection = #("one", "two")
foreach ($item in $collection) {
Write-Host $item
}
Instead, if you pipe commands outputs, you have to use the ForEach-Object cmdlet with a script block. Inside the script block, you refer to the iteration variable with the special variable $_. Example:
$collection = #("one", "two")
$collection | ForEach-Object {
Write-Host $_
}
You can shorten ForEach-Object with %:
$collection | % {
Write-Host $_
}
So, in your case you should probably do this:
C:\accountsizes.ps1 | where size -gt "2048" | select userprincipalname,size | % { $_.userprincipalname }
Paolo provided excellent information and deserves upvotes if nothing else. As to your question of how to enumerate and send email you probable need something like:
C:\accountsizes.ps1 | where size -gt "2048" | %{
$smtpServer = "smtp.domain.com"
#Creating SMTP server object
$SMTP = new-object Net.Mail.SmtpClient($smtpServer)
#Creating a Mail object
$EMail = new-object Net.Mail.MailMessage
#Construct Email
$EMail.From = "me#domain.com"
$EMail.ReplyTo = "me#domain.com"
$EMail.To.Add($_.userprincipalname)
$EMail.subject = "Warning Email Mailbox Too Large"
$EMail.body = "blah blah blah blah"
$SMTP.Send($EMail)
}
You could get a lot more fancy, go by size and send different emails depending on how large their mailbox is, or get content from files for subject and body depending on size, but that is just going to make things complicated. You could also use Send-MailMessage, and that works just fine, I just like this way because it makes it easier to work with in my opinion than one really long line with a ton of switches. If the message and subject are going to be generic you may want to do something more like:
$smtpServer = "smtp.domain.com"
#Creating SMTP server object
$SMTP = new-object Net.Mail.SmtpClient($smtpServer)
#Creating a Mail object
$EMail = new-object Net.Mail.MailMessage
#Construct Email
$EMail.From = "me#domain.com"
$EMail.ReplyTo = "me#domain.com"
C:\accountsizes.ps1 | where size -gt "2048" | %{$EMail.BCC.Add($_.userprincipalname)}
$EMail.subject = "Warning Email Mailbox Too Large"
$EMail.body = "blah blah blah blah"
$SMTP.Send($EMail)
That would make one email and BCC everybody on it. Then you could do another email to yourself stating who warnings got sent to.
Related
For logging tasks I need to send me an email through Outlook. I wrote some code like this:
$outlook = New-Object -ComObject Outlook.Application
$namespace = $outlook.GetNameSpace("MAPI")
$olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]
$InboxDef = $namespace.GetDefaultFolder($olFolders::olFolderInBox)
$InboxDef.FullFolderPath -match "^\\\\(.*)\\Inbox$" | Out-Null
$recipient = $matches[1]
$email = $outlook.CreateItem(0)
$email.To = "$recipient"
$email.Subject = "Title"
$email.Body = "Text"
$email.Send()
$Outlook.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Outlook) | Out-Null
When I subsequently launch the Outlook client I see the email sent twice
The code looks good. One thing which is not clear enough is setting a recipient for the outgoing email:
$InboxDef.FullFolderPath -match "^\\\\(.*)\\Inbox$" | Out-Null
$recipient = $matches[1]
It is not clear what value is used in the code. To make sure the property is set correctly I'd suggest using the Recipients property of the MailItem class instead. The Recipients.Add method creates a new recipient in the Recipients collection. Then don't forget to use the Recipient.Resolve
method which attempts to resolve a Recipient object against the Address Book.
Read more about that in the article which I wrote for the technical blog, see How To: Fill TO,CC and BCC fields in Outlook programmatically.
I have written a piece of powershell code to send emails to different users based on an extraction from a table (and some conditions).
#extraction skipped
foreach($row in $result) {
Clear-Variable template* -Scope Global
$template = (Get-Content "C:\template.html")
#user definition skipped
$message = "Hello..."
$html = $template |
ForEach-Object { if($_.Trim() -eq "<p>$[CONTENT]</p>") {
-join(($_).ToString().Replace("$[CONTENT]", $message),$html)
} else { -join($_, $html) }
}
Send-MailMessage -From $fromAddr -To $mail -Bcc $maintainer -Subject $sub -Body ($html | Out-String) -BodyAsHtml -SmtpServer $smtpserver -Encoding UTF8
}
If you need anything else feel free to ask, I felt like this was the relevant part of the code but I'm not 100% sure.
The Problem: Basically if i get only one user the email text looks fine. If there is more than one $row the second email is full of System.Object[].
I'm not sure what I've been doing wrong. My guess would be ForEach-Object, but why work for the first and not the following?
Thanks in advance for any help.
The problem in your code on the second iteration is that it's basically doing this:
$html = $template | ... -join($_, $html) ...
where $html still contains the value from the first iteration, so you're joining the new $message with the entirety of the first email text and getting into a bit of a pickle.
You can avoid this if you add:
$html = $null
$html = $template | ... -join($_, $html)
so each iteration gets a clean start.
Having said that, you can just do as #Mathias R. Jessen suggested and replace your entire templating logic with this:
$body = -join #((Get-Content .\template.html) -replace '\$\[CONTENT\]', $message)
or more succinctly, this:
$body = (Get-Content .\template.html -Raw).Replace("`$[CONTENT]", $message)
I need to send one unique email to different email addresses exporting
them from a .csv file. I have a code but it just sends the message to all
of the emails in one email.
#Import the file that store username and emails
$data = import-csv "C:.csv"
#Declare email content
$email = $data.Email | select -unique
ForEach ($email in $data)
{
$From = "***#gmail.com"
$To = $data.Email
$Subject = "Test"
$Body = "Test"
$SMTPServer = "smtp.gmail.com"
$SMTPPort = "587"
}
#Sending email
Send-MailMessage -From $From -to $To -Subject $Subject `
-Body $Body -SmtpServer $SMTPServer -port $SMTPPort -UseSsl `
-Credential (Get-Credential -Message "Please input valid credentials")
The code above works but as mentioned it sends just one email to all the email addresses in the file. I need it to send one for each email.
The main problem I see in your code is that you first create an array of unique email addresses from the csv file in a variable $email, but later on you overwrite that same value by using it in the foreach loop.
There, in every iteration, the $email variable will become a complete row from the CSV file which is obviously not what you expect it to be.
Below a slightly adjusted version of your code. Note that I also used Splatting to create a hashtable with all properties for the Send-MailMessage cmdlet, to avoid having to use the easy to overlook backtick.
#Import the file that store username and emails
$data = import-csv "D:\mail.csv"
# Get a unique array of email addresses
$addresses = $data.Email | Select-Object -Unique
# Declare Credentials
$creds = (Get-Credential -Message "Please input valid credentials")
# loop through the email addresses array and send a mail to each of them
foreach ($email in $addresses) {
$splat = #{
From = "***#gmail.com"
To = $email
Subject = "Test"
Body = "Test"
SmtpServer = "smtp.gmail.com"
Port = 587
Credential = $creds
UseSsl = $true
}
#Sending email
Send-MailMessage #splat
}
Note that the -Port parameter is of type Int32, so you should not quote that
Update
As requested in your comment, if you want to use more fields from the CSV file, then the code would change.
Let's assume your CSV looks anything like:
"User","Email","ManagerEmail"
"Tom","t.somebody#yourcompany.com","el.jeffe#yourcompany.com"
"Dick","d.somebody#yourcompany.com","el.jeffe#yourcompany.com"
"Harry","h.somebody#yourcompany.com","di.rector#yourcompany.com"
"Dick","d.somebody#yourcompany.com","el.jeffe#yourcompany.com"
(note, user Dick is duplicated)
Then the following will read the csv, deduplicate it on the Email property and send emails to each user:
# Import the file that store username and emails
# and uniquify the objectson property Email
$data = Import-Csv "D:\mail.csv" | Sort-Object -Property Email -Unique
# Declare Credentials
$creds = (Get-Credential -Message "Please input valid credentials")
# loop through the csv objects array and send a mail to each of them
foreach ($item in $data) {
# every item is an object with properties .User, .Email and .ManagerEmail
$splat = #{
From = "***#gmail.com"
To = $item.Email
Cc = $item.ManagerEmail
Subject = "Hi there {0}" -f $item.User
Body = "Test"
SmtpServer = "smtp.gmail.com"
Port = 587
Credential = $creds
UseSsl = $true
}
#Sending email
Send-MailMessage #splat
}
Hope that helps
As per comments, please find the adjusted script and reasoning.
You were looping through the CSV but only sending the email once. It looks like it would have originally sent the email to ONE person, that being the last in your CSV.
This version will loop through and send an email for each line in the CSV.
#Import the file that store username and emails
$data = import-csv "C:.csv"
#Declare email content
$email = $data.Email | select -unique
# Declare Credentials
$creds = (Get-Credential -Message "Please input valid credentials")
ForEach ($email in $data) {
$From = "***#gmail.com"
$To = $email.Email
$Subject = "Test"
$Body = "Test"
$SMTPServer = "smtp.gmail.com"
$SMTPPort = "587"
#Sending email
Send-MailMessage -From $From -to $To -Subject $Subject `
-Body $Body -SmtpServer $SMTPServer -port $SMTPPort -UseSsl `
-Credential $creds
}
Most important error you made is
$To = $data.Email
where you add all the emails to To: field. Drew's suggestion is almost what you want to follow but with one remark. Your $To= assignment should use $email which represents single object from array, instead of the whole array (which is $data).
$To = $email.Email
Edit: As #Theo suggested in the comments, it's worth mentioning that the line
$email = $data.Email | select -unique
won't work as you use the same variable name later in foreach loop. What I'd suggest is to save unique email addresses to other variable
$uniqueEmails = $data.Email | select -unique
then iterate that one
# Change this
ForEach ($email in $data) {
# To this
ForEach ($email in $uniqueEmails ) {
And of course as you already saved Email propert to $uniqueEmails:
# This line
$To = $email.Email
# Should be changed to
$To = $email
I have a Powershell script that automates a process and emails the report of what happened.
Send-MailMessage -To $toAddress -From "no-reply#domain.org" -subject "Automation status" -body $bodystr -SmtpServer SERVER1 -EA Stop
So $bodystr is essentially an appended string throughout the script to report what happened and has multiple lines. Things like:
$bodystr = $bodystr + "Line found: 305`n"
$bodystr = $bodystr + "Moving line 305 to 574`n"
The Send-MailMessage command is at the bottom of the script outside any function. But most other code is in various different functions.
The issue is $bodystr does not seem accessible inside functions, and so the email is lacking a lot of information.
I believe I could use Set-Variable or passing arguments, but there are so many arguments it seems farther away from best practice to add a new argument for each function just to keep the string updated.
What's the best practice to handle this?
As a general rule, don't write data back to variables outside the scope of your function.
If you are compiling an email by gathering data from multiple sources, abstract it away in multiple functions that does one thing each and have them return a multiline string with the relevant output.
At the end of your script, collect the different message body parts and join them to a single string before sending.
In this example, we have a script that takes a path to a log file, defines a function to extract errors from a log file, and send an email with the errors in the body:
param(
[ValidateScript({Test-Path $_ -PathType Leaf })]
[string]$LogPath = 'C:\Path\To\File.log',
[string]$From = 'noreply#company.example',
[string]$To = #('ceo#company.example','finance#company.example'),
[string]$Subject = 'Super Important Weekly Report',
[string]$SmtpServer = $PSEmailServer,
[string]$Credential
)
# Define functions with a straight forward purpose
# e.g. Searching a logfile for errors
function Parse-Logfile {
param($LogPath)
[string[]]$LogErrors = #()
Get-Content $LogPath |ForEach-Object{
if($_ -contains $Error){
$LogErrors += $_
}
}
# Create and return a custom object has the error details as properties
New-Object psobject -Property #{
ErrorCount = $LogErrors.Count
Errors = $LogErrors
}
}
# Create a email template that's easy to maintain
# You could store this in a file and add a $TemplateFile parameter to the script ;-)
$EmailTemplate = #'
Hi there!
Found {0} errors in log file: {1}
{2}
Regards
Zeno
'#
# Use your function(s) to create and gather the details you need
$ErrorReport = Parse-Logfile -LogPath $LogPath
# If necessary, concatenate strings with -join
$ErrorString = $ErrorReport.Errors -join "`n"
# Use the format operator to the final Body string
$Body = $EmailTemplate -f $ErrorReport.ErrorCount, $LogPath, $ErrorString
# Set up a splatting table (Get-Help about_Splatting)
$MailParams = #{
To = $To
From = $From
Subject = $Subject
Body = $Body
SmtpServer = $SmtpServer
}
if($PSBoundParameters.ContainsKey('Credential')){
$MailParams['Credential'] = $Credential
}
# Send mail
Send-MailMessage #MailParams
So basically, what I have here is a script that will scan a CSV that it imports, and for every entry in the spreadsheet, except for people in the RANDOM.DOMAIN, it will find the managers email address and send an automated email to the manager telling them user XYZ is about to expire soon, and they need to do something about it.
If the managers email is unavailable for some reason, then it defaults to sending the email to me.
This script works well.
The problem I am running into is, I want to make it so only one email is sent to each manager, despite multiple users (or entries) from the spreadsheet, list them as the manager.
I.e. if Joe Bloggs has a manager Aaron T and Jane Doe has the manager Aaron T, then Aaron T will get two emails, one email for each user.
MY QUESTION:
Is there an easy way to only get it to send one email per manager, even if that manager has multiple users reporting to them that are about to expire?
$datai = Import-Csv "Soon-to-expire User Accounts22.csv" | select 'Display Name',Manager,'Domain Name','Account Expiry Time'
Connect-QADService -Service another.DC | Out-Null
$expiringUsers = #{}
foreach ($i in $datai) {
$dn = $i.'Display Name'
$dn1 = $i.'Domain Name'
$man = $i.'Manager'
$aet = $i.'Account Expiry Time'
$subject = "Account about to expire: $dn"
$getmail = get-qaduser "$man" -LdapFilter '(mail=*)' | select mail
$emailAD = $getmail.mail
if ($man -eq "-" -or $man -like 'CN=*' -or $getmail -eq $null -or $man -eq "") {
$man = "Aaron T"
$getmail = get-qaduser "$man" -LdapFilter '(mail=*)' | select mail
$emailAD = $getmail.mail
}
if ($expiringUsers.Contains($emailAD)) {
$expiringUsers[$emailAD]["dn"] += $dn += "`n"
$expiringUsers[$emailAD]["aet"] += $aet += "`n"
$expiringUsers[$emailAD]["man"] += $man += "`n"
} else {
$expiringUsers[$emailAD] = #{
#"dn1" = $dn1
#"aet" = $aet
#"man" = $man
# "dn" = #( $dn )
}
}
}
$expiringUsers | fc #as suggested
foreach ($emailAD in $expiringUsers.Keys) {
$dn = $expiringUsers[$emailAD]["dn"]
$dn1 = $expiringUsers[$emailAD]["dn1"]
$man = $expiringUsers[$emailAD]["man"]
$aet = $expiringUsers[$emailAD]["aet"]
$subject = "Account/s About to Expire!"
$content = #"
Hi,
$dn `n
$dn1 `n
$man `n
$aet `n
$emailAD `n
Technology Services
"#
Send-MailMessage -from "aaron#website.com" `
-To $emailAD `
-Subject $subject `
-Body $content `
-Priority high `
-smtpServer "relay.server"
#using this as a test instead of sending mass emais all the time
Write-Host $content
}
UPDATED with the new script as requested.... still having issues.
Is there an easy way to only get it to send one email per manager, even if that manager has multiple users reporting to them that are about to expire?
For this you need to defer e-mail processing. Collect the users in a hashtable, e.g. by manager e-mail address:
...
$expiringUsers = #{}
foreach ($i in $datai) {
If ($i.'Domain Name' -notmatch "RANDOM.DOMAIN") {
...
if ($expiringUsers.Contains($emailAD)) {
$expiringUsers[$emailAD]["dn"] += $dn
} else {
$expiringUsers[$emailAD] = #{
"dn1" = $dn1
"aet" = $aet
"man" = $man
"dn" = #( $dn )
}
}
}
}
and move the actual e-mail processing outside the loop:
foreach ($emailAD in $expiringUsers.Keys) {
$dn1 = $expiringUsers[$emailAD]["dn1"]
$man = $expiringUsers[$emailAD]["man"]
$aet = $expiringUsers[$emailAD]["aet"]
$subject = "Account about to expire: $($expiringUsers[$emailAD]["dn"])"
$content = #"
Hi,
...
Technology Services
"#
Send-MailMessage -from "Test Script - Powershell <email#test.com>" `
-To "$emailAD" `
-Subject $subject `
-Body $content `
-Priority high `
-smtpServer servername
Write-Host "Mail Sent to $man"
}
Note that for simplicity reasons the above code only records the expiry date of the first user. If you want the expiry date of each user recorded separately, you'll have to take additonal steps, e.g.
$expiringUsers[$emailAD]["expiry"] += #{
"name" = $dn;
"date" = $aet;
}
instead of
$expiringUsers[$emailAD]["dn"] += $dn
So I finally decided to revisit this script, after many, many months.
I'm get a little better at PowerShell and while I'm sure this isn't the most effective way to do it, this is something that works for me.
I've also changed the input method; it pulls the information directly from AD, instead of using a CSV file that used to be generated from an application called 'AD Manager Plus' (Hate it).
Remember, using Quest CMDlets here because we don't have a 2008 environment. (so using Get-QADUser instead of Get-ADuser)
FYI, I have only posted the code here which sorts out the data into separate tables - you can decide how you want to utilize those results. For our environment, I have it build an nice HTML table and body, then send it to the appropriate manager to deal with.
#user data input
$data = get-qaduser -SizeLimit 0 -includedproperties accountexpires | where {$_.AccountExpires -ne $null -and $_.AccountExpires -le ((Get-Date).AddDays(45)) }
#get a list of managers, unique.
$uniqueMan = $data | select Manager -Unique
#foreach manager from $uniqueman
Foreach ($manager in $uniqueman) {
#create the array variable / clear it out for the next manager.
$myarray = #()
#foreach User found in in $data query
Foreach ($user in $data) {
#Search the $user's query for people with the same manager as that of the $uniqueman list.
If ($user.Manager -eq $manager.Manager) {
#do what with the result.
#add the person to an array
$myarray += New-Object psobject -Property #{
Name = $user.'Name'
UserName = $user.'SAMAccountName'
AccountExpires = $user.'AccountExpires'
Manager = $user.Manager
}
}
#for testing, to output the results to an HTML file.
#$myarray | ConvertTo-Html | Out-File ("C:\test\" + $manager.Manager + ".html")
}
}