Script to count number of emails for multiple users - powershell

I am needing a little help with my script. I have this working if I use just a single email address. I need to add a list of 8 emails addresses for this to scan. How would I modify this to send 1 email for all 8 users?
I have seen scripts that make a html file that displays everything in a nice table but those are ran against all users in exchange and I only needs this for a group of 8 users.
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
#Powershell blah blah blah
$nl = [Environment]::NewLine
#Mailbox to gather stats on
$mailboxs=$mailbox= 'user1#domain.com','user2#domain.com'
#Get todays
$startDate=Get-Date
$endDate=Get-Date
#Subtract 1 day from todays date (report ending day) and 1 day from todays date (report starting day)
$startDateFormatted=$startDate.AddDays(-1).ToShortDateString()
$endDateFormatted=$endDate.AddDays(-1).ToShortDateString()
foreach ($mailbox in $mailboxs)
{
# Sent e-mails
$sendCount = Get-TransportService | Get-MessageTrackingLog -Start "$startDateFormatted 00:00:00" -End "$endDateFormatted 23:59:59" -Sender $mailbox -resultsize unlimited | select-object -unique MessageId
# Received e-mails - This works but not on generic accounts
$receiveCount = Get-TransportService | Get-MessageTrackingLog -Start "$startDateFormatted 00:00:00" -End "$endDateFormatted 23:59:59" -Recipients $mailbox -resultsize unlimited | select-object -unique MessageId
$sendCountString = $sendCount.count
$receiveCountString = $receiveCount.count
}
$Output =
$Mailbox |
foreach {
$ResultHash =
#{
Address = $_
Sent = $Sendcountstring
Received = $Receivecountstring
}
New-Object -TypeName PSObject -Property $ResultHash |
Select Address,Sent,Received
}
#Who to send the e-mail report to.
#Multiple e-mail addresses should be in this format "<email1#domain.com>, <email2#domain.com>"
$MailParams = #{
From = "ISSReports#domain.com"
To = "user3#domain.com"
subject = "Daily e-mail report for ISS for $startDateFormatted"
BodyAsHTML = $true
smtpServer = "mail.domain.com"
}
$header =
#"
"Mailbox Stats
Report date range: $startDateFormatted 00:00:00 - $endDateFormatted 23:59:59
"#
$body = $Output | ConvertTo-Html -As Table -Head $header | out-string
Send-MailMessage #MailParams -Body $body

I put this together from your script and some pieces borrowed from a couple of my own. For this kind of reporting, you only need to read through the logs once, and you can eliminate the need to de-dupe by messageid by only returning the Deliver events. There will be one deliver event per email sent, containing both the sender and recipients.
#Mailboxs to gather stats on
$mailbox= 'user1#domain.com','user2#domain.com'
#Get todays date twice
$startDate=Get-Date
$endDate=Get-Date
#Hash tables for send and receive counts
$Sent = #{}
$Received = #{}
#Subtract 1 day from todays date (report ending day) and 1 days from todays date (report starting day)
$startDateFormatted=$startDate.AddDays(-1).ToShortDateString()
$endDateFormatted=$endDate.AddDays(-1).ToShortDateString()
$TransportServers = Get-ExchangeServer |
Where {$_.serverrole -match "hubtransport"} |
Select -ExpandProperty Name
foreach ($TransportServer in $TransportServers)
{
Get-MessageTrackingLog -Start "$startDateFormatted 00:00:00" -End "$endDateFormatted 23:59:59" -EventID Deliver -Server $TransportServer -ResultSize Unlimited |
foreach {
if ($mailbox -contains $_.sender )
{ $Sent[$_.Sender]++ }
foreach ($Recipient in $_.Recipients)
{
if ($mailbox -contains $Recipient )
{ $Received[$Recipient]++ }
}
}
}
$Output =
$Mailbox |
foreach {
$ResultHash =
#{
Address = $_
Sent = $Sent[$_]
Received = $Received[$_]
}
New-Object -TypeName PSObject -Property $ResultHash |
Select Address,Sent,Received
}
#Who to send the e-mail report to.
#Multiple e-mail addresses should be in this format "<email1#domain.com>, <email2#domain.com>"
$MailParams = #{
From = "ISSReports#domain.com"
To = "user3#domain.com"
subject = "Weekly e-mail report for $mailbox for $startDateFormatted - $endDateFormatted"
BodyAsHTML = $true
smtpServer = "mail.domain.com"
}
$header =
#"
"Mailbox Stats
Report date range: $startDateFormatted 00:00:00 - $endDateFormatted 23:59:59
"#
$body = $Output | ConvertTo-Html -As Table -Head $header | out-string
Send-MailMessage #MailParams -Body $body

For these 8 mailboxes you need a count of messages sent by that mailbox? This calls for 3 advanced functions:
count the mail
create a report
send the report
So, I see it like this (mostly pseudo-code):
Function Count-SentFromMbx {
#paramter $mailbox
#parameter $startdate
#parameter $enddate
# function counts sent mail per your working single user script
# or better yet, per mjolinor's script leveraging `-contains`
# build custom object with mailbox and count as properties
# or better yet, enough information to build a digest.
# return object/collection
}
# Create a function to format the results email
# Create a function to send the results email
# Define collection of mailboxes
$mailboxes = "blah#blah.com","blah2#blah.com",etc.
# define or get the dates
$startdate = whatever
$enddate = whatever
# initialize array for results
$results = #{}
# set other variables for storage of the report
# and for recipient list
# and anything else needed 'globally'
# Pull data from the logs:
$results += Count-SentFromMbx -mailbox $mailbox -startdate $startdate -enddate $enddate
# if you end up with a collection of collections, you may need to change to
# $results = ... and add a line like `$results | $reportData += $_`
# build report using report function
# send report using send function
The main idea I am suggesting is to use advanced functions (Get-Help about_advanced_functions) for each modular task involved in the process. Then you can leverage the power of objects to make gathering the data easy. You can tailor your reports for your audience by tweaking the reports function.
Having your function return an object, try $results | ConvertTo-Html. Bare minimum, if you have to build your own report, the collection of objects, $results, gives you an organized data source, easily passed through a foreach loop. Using objects you could even expand the collection to return message digests instead of just the number of messages sent. Instead of counting messages, create a custom object with subject, recipient and the sender. (Easy way to create object is to pipe objects to select with a customized property list). Return that collection and add it to the results collection. Then reporting could include message digests AND totals. The ability of objects to carry information gives you great flexibility.
One of the key things that mjolinor did was to put your 8 addresses in a collection. When looping through, you can compare if ($collection -contains $suspect) { do stuff}. This executes the block if the $suspect is in the $collection. This allows you to loop through the logs once.

Related

Find Active Accounts No Longer Employed

I want to create a backstop for lack of notification to IT when an employee leaves. We receive an active employee roster in csv format including the employeeID field monthly. I plan to request for this daily which they can provide via their HRIS system. I'd like to eventually automate via task scheduler a copy from where HR puts the file to a location where we run our scripts - then kick off the below script to run against the copied csv. For now I just need help getting the Powershell script right.
What I would like to do is this:
Search AD for employees with an employeeID (no blanks to be returned)
Import a csv that has a column of employeeIDs
Perform a search against the csv from the AD results
For any employee IDs that exist in AD but not in the csv, send an email address to an address, "user $_.Name not an employee
EmployeeID is our most reliable field as HR doesn't have a list of SamAccountNames and people get married and names and email addresses change. I do not want to automate the process of disabling accounts because that would enable a mechanism for a rogue actor in HR to disable everyone's account.
My script is all wrong but here is the thought process I started with:
# Return employees with an employeeID field populated - we're not concerned with service accounts, consultants, etc
#
$adusers = Get-ADUser -searchbase "OU=MyOU,DC=MyCompany,DC=COM" -Filter {employeeID -like "*" -and enabled -eq $true} -Properties employeeID
#
# Import active roster
#
$csv = Import-Csv C:\temp\activeroster-test.csv
foreach($emp in $csv)
{
$csvID = $csv.employeeID
$csvName = $csv.Name
if($adusers.EmployeeID -notlike $csvID)
{
echo '**not found in roster**'
echo $ADusers.Name
}
}
I haven't got to the email notification part because I can't seem to even get this. It just returns the people in my roster to the tune of the amount of people in the roster. It's backwards. Help!
Edit - updated with email notification:
# Return employees with an employeeID field populated - we're not concerned with service accounts, consultants, etc
$adUsers = Get-ADUser -searchbase "OU=MyOU,DC=Example,DC=COM" -Filter {employeeID -like "*" -and enabled -eq $true} -Properties employeeID
# Email Server info
$SmtpServer = "emailserver.example.com"
$NotificationEmailAddress = "myemail#example.com"
#
# Import active roster
#
$csv = Import-Csv C:\temp\activeroster.csv
foreach ($emp in $adUsers) {
$csvIDList = $csv.EmployeeID
if ($emp.EmployeeID -notin $csvIDList) {
$Body = "The following users are still enabled in Active Directory however not found in the active employee roster " + ($($emp.Name) | out-string)
Send-MailMessage -From $NotificationEmailAddress -To $NotificationEmailAddress -Subject "Active Accounts Not In Employee Roster" -Priority High -dno onFailure -SmtpServer $SmtpServer -Body $Body
}
}
I get an email for each user. Thankfully in my test I am doing a small OU and a sample subset of the roster. Heh! Any advise? I think I may need to create another variable that encompasses all the results, yeah?
Your script isn't all wrong, you're actually pretty close. You're printing all of the returned users from your AD query with each iteration as the affected user, when you just mean to return the one you're iterating over. And you can use -notin on the list you have from the HR csv when comparing to compare to the array of IDs.
# Return employees with an employeeID field populated - we're not concerned with service accounts, consultants, etc
#
$adUsers = Get-ADUser -searchbase "OU=MyOU,DC=MyCompany,DC=COM" -Filter {employeeID -like "*" -and enabled -eq $true} -Properties employeeID
#
# Import active roster
#
$csv = Import-Csv C:\temp\activeroster-test.csv
# Email Server info
$smtpServer = "emailserver.example.com"
$notificationEmailAddress = "myemail#example.com"
[System.Collections.ArrayList]$listOfMissing = #()
foreach ($emp in $adUsers) {
$csvIDList = $csv.EmployeeID
if ($emp.EmployeeID -notin $csvIDList) {
Write-Output "**Employee $($emp.Name) not found in roster**"
$listOfMissing.Add($emp.Name)
}
}
if ($listOfMissing) {
$subject = "Active Accounts Not In Employee Roster"
$body = #"
The following users are still enabled in Active Directory however not found in the active employee roster:
$($listOfMissing | Out-String)
"#
$emailParameters = #{
From = $notificationEmailAddress
To = $notificationEmailAddress
Subject = $subject
Priority = 'High'
Dno = 'onFailure'
SmtpServer = $smtpServer
Body = $body
}
Send-MailMessage #emailParameters
} else {
Write-Output 'Email not sent, no users found'
}
I may also suggest pursuing some regular automated update of that csv with another script, or integration to the HR system, which is usually Workday or something similar, though I know in our positions we sometimes don't have that access. :)
For the email you can check out this SO post.

Filter Get-MessageTrace log on EAC with Multiple Subjects

We use an Office365 email to send emails to multiple recipients. I was told to get the sent message logs and export them into a CSV format so our team can study which messages were delivered and which ones failed. Below I have put together a PowerShell script that connects to EAC and pulls the Sent Message logs for a particular account for the past 2 days. The script is working great however, I wanted to know if there is a way to only get emails with particular subjects. For example, I could use the code below to get the logs but it only filters the list for one subject. How can I filter this using multiple subjects such subject 1, 2, and 3.
$Subject = "Thank you for your business"
$Messages = $null
$Page = 1
do
{
Write-Host "Collecting Messages - Page $Page..."
$Messages = Get-MessageTrace -SenderAddress "no_reply#myCompany.com" -StartDate (Get-Date).AddDays(-2) -EndDate (Get-Date) -PageSize 5000 -Page $Page| Select Received,SenderAddress,RecipientAddress,Subject,Status
if ($myMessages -ne $null)
{
$myMessages | Where {$_.Subject -like $Subject} |Select-Object -Property Received, SenderAddress, RecipientAddress, Subject, Status | Export-Csv C:\Logs\Logs-$PAGE.csv -NoTypeInformation
}
$Page++
$Messages += $myMessages
}
until ($myMessages -eq $null)
Add the multiple subjects into your $subject variable to create a string array.
$Subject = #("Thank you for your business","hello","123","etc")
You can then use the Contains method of the array in the Where block like so.
Where {$Subject.Contains($_.Subject)}
This will filter the emails to only those match the subject exactly.

Compare today's Date with CSV and send out an email if expired

Can anyone show me how can I do in order to compare date in CSV files and send out a reminder email using PowerShell?
Thanks.
Regards,
Looi
I'm just guessing this might be the answer.
# Just imitating Import-Csv here...
$csv = "Text,Date",
"Abc,6/10/2017",
"Def,3/26/2017",
"Uvw,2/5/2017 ",
"Xyz,2/13/2017" | ConvertFrom-Csv
# Get the date in a sortable form and filter:
$out = $csv | Select-Object -Property Text,
#{Name="Date"; Expression={Get-Date $_.Date}} |
Where-Object {$_.Date -lt (Get-Date 3/1/2017)}
# If any are earlier than 3/1/2017, send email:
if ($out)
{
# I'm outputting strings that look like the Send-MailMessage cmdlet...
$out | ForEach-Object {
"Send-MailMessage -To `"Usr <usr#grandwazzoo.com>`"
-From `"$($_.Text) <$($_.Text)#grandwazzoo.com>`"
-Subject `"A Reminder`""
}
}

Send email with body consisting of objects

I am trying to write a script to search through AD and look for any stale computers and then sends an email to me. However, there are 3 requirements:
get the lastlogondate for any pc that is older than 90 days
once I get that list, I do a ping test
from the ping test, I take that and get the last time the password was set
Import-Module activedirectory
#Date = today's date -90 days
$date=[datetime]::Today.adddays(-90)
$pcArray = #()
$bodyArray=#()
$passArray=#()
$oldpc = Get-ADComputer -properties * -Filter 'lastlogondate -le $date' |
where {$_.name -like '*WD*'} |select name, lastlogondate
foreach ($pc in $oldpc)
{
if (!(Test-Connection $pc.name -quiet))
{
$script:pcArray += $pc.name
}
}
foreach ($failping in $pcArray)
{
$lastpass = Get-ADComputer $failping -properties * |
select name, passwordlastset
$script:passArray += $lastpass
}
#Send email
foreach ($badpc in $oldpc)
{
$script:bodyArray += $badpc
}
if($bodyArray)
{
Send-MailMessage -to "toEmailaddress "-Subject "Stale Computers Check" -From "fromEmailaddress "-SmtpServer "smtpserver" -BodyAsHtml "The following computers have not been logged into for over 90 days </br>$bodyArray </br></br>
The following computers are not pingable</br>$pcArray </br></br>The last password changed on those pcs are </br>$lastpass"
}
The email I get looks like this:
The following computers have not been logged into for over 90 days
The following computers are not pingable
pcname1 pcname2
The last password changed on those pcs are
#{name=pcname1; passwordlastset=12/08/2014 14:59:38}
As you can see, the $bodyArray variable isn't being sent yet after I run the script, in PowerShell, I call $bodyArray, and I get the following output:
name lastlogondate
---- -------------
pcname1 9/13/2014 8:06:21 PM
pcname2 9/17/2014 5:25:25 PM
pcname3 12/5/2014 11:16:16 AM
pcname4 12/8/2014 3:00:01 PM
I tried ToString() already and iterating as you can see above.
As for number 2, I would like to get the PC names each on a newline. I tried join + `n, but that didn't work.
And for number 3, I can only get 1 PC to output and not the entire array. Running via pscmd I also get 1 PC, but there should be 2 since I am looping through 2 PCs in $pcArray.
Also if you want it to look more nice and readable you can do something like this that will spit it out in a table:
$body += "<body><table width=""560"" border=""1""><tr>"
$bodyArray[0] | ForEach-Object {
foreach ($property in $_.PSObject.Properties){$body += "<td>$($property.name)</td>"}
}
$body += "</tr><tr>"
$bodyArray | ForEach-Object {
foreach ($property in $_.PSObject.Properties){$body += "<td>$($property.value)</td>"}
$body += "</tr><tr>"
}
$body += "</tr></table></body>"
The $bodyArray[0] | ForEach-Object prints out the column names as a table header. and the $bodyArray | ForEach-Object prints out all the values to the table. And if you want to get fancy you can even add some CSS to make the tables look better

How to create subset of data based on multiple matching field values in Powershell?

I have a powershell script that pulls emails using EWS managed API, reads their subject, from address, to address, and internetmessageheaders. Within the email read loop it loads all this information into a datatable.
I need to grab all the above information for each message and populate an array or datatable based on the from address. Then I'll check for relay information and send the whole list based on from address to a target address.
I can't seem to wrap my head around how to build the from address lists. I've tried turning the datatable data into a hashtable and then using a sort|get-unique to get a list of the unique from addresses but have not been able to use this information to correctly build the from address array. I've tried looping through using a select-string $_ -allmatches but haven't gotten anywhere.
Here is part of my code, It creates the datatable and populates it using information from each email.
$msgTable = New-Object system.Data.DataTable “Messages”
$col1 = New-Object system.Data.DataColumn Subject,([string])
$col2 = New-Object system.Data.DataColumn From,([string])
$col3 = New-Object system.Data.DataColumn To,([string])
$col4 = New-Object system.Data.DataColumn Relay,([string])
$msgTable.columns.add($col1)
$msgTable.columns.add($col2)
$msgTable.columns.add($col3)
$msgTable.columns.add($col4)
$frItemResult = $PublicFolder.FindItems($sfCollection,$view)
# Loop through view results and mark read
$frItemResult | ForEach-object{
if($_.HasAttachments ) {
$_.Load()
"Report Subject: $($_.Subject)"
# Loop through attachments, extract info
$_.Attachments | ForEach-object {
if($_.Name -notmatch "ATT00001"){
$_.Load($attPropset)
$row = $msgTable.NewRow();$row.Subject = $($_.item.Subject); $row.From = ($_.item.From.address); $row.To = $($_.item.ToRecipients.address); $row.Relay = "$($_.Item.InternetMessageHeaders | Where-Object {$_.name -like "*received*"})";$msgTable.Rows.Add($row)
Here is what $msgTable looks like after data is populated.
Subject From To Relay
------- ---- -- -----
Message1 user1#company.com recipient1#yahoo.com Received-SPF=pass (domain of ...
Message2 user2#company.com recipient2#comcast.net Received=from imta27.mailguys...
Message2 user2#company.com recipient3#yahoo.com Received-SPF=pass (domain of ...
Message3 user3#company.com recipient4#sbcglobal.net Received-SPF=pass (domain of ...
I need to be able to grep out all the information for user2#company.com to another variable and do the same for any number of other repeating or non-repeating from addresses.
I'll then take each of the from address variables and send it to the target recipient.
Any help is greatly appreciated.
Try using Group-Object, you can then run a foreach loop against each grouping:
$Grouping = $msgTable | Group-Object -Property From
foreach ($Group in $Grouping)
{
Write-Host $Group.Name -ForegroundColor "Green"
$Group.Group | ft -Auto
}
Cool exercise! This is how I would approach it...
...I need to be able to grep out all the information for
user2#company.com to another variable
$patternMatches = $msgTable | select-string -pattern 'user2#company.com'
...and do the same for any number of other repeating or non-repeating
from addresses.
$uniqueEmails = $msgTable | Select-Object From -Unique
foreach ($email in $uniqueEmails) {
$patternMatches = $msgTable | Select-Object -property * | select-string -pattern $email
}
I know this isn't exactly right, but it should push you into the right direction.