Using pipeline object to populate mail -to and -attachment - powershell

First ever Powershell script so any advice or recommendations are appreciated. I'm parsing a .csv into smaller .csv's to send out information about servers to recipients and i'm running into a problem in my foreach. How do I get this to work?
One interesting thing is that in Send-MailMessage, -to should not accept pipeline objects, It still throws an error, but it still sends the emails. However the attachment will never send.
#had to set this as a variable because # was throwing splatting errors
$Mail = "#Email.com"
#Import csv and split information, exports UID.csv
Import-csv C:\path\info.csv | Group-Object UID |
ForEach-Object {
$_.Group | Export-csv "C:\path\$($_.Name).csv" -NoTypeInformation
}
#Import file again to get unique list of UID and send mail with
#respective UID.csv
Import-csv C:\path\info.csv | Group-Object UID |
ForEach-Object {
$_.UID | Send-MailMessage -From "<Me#email.com>" -To "<$($_.Name)$Mail>" `
-Attachments "C:\path\$($_.Name).csv" `
-Subject "Testing" -Body "Please Ignore This" -Priority High `
-SmtpServer smtp.server.com
}

in Send-MailMessage, -to should not accept pipeline objects
In principle it does, namely if the pipeline objects have a .To property (which is not the case for you).
However, with your current approach, you don't need pipeline input at all, given that you're supplying all input as arguments.
Additionally, your pipeline input is incorrect, because $_.UID sends $null through the pipeline, given that $_ - a group-info object output by Group-Object - doesn't have a .UID property.
Using delay-bind script blocks ({ ... }), you can simplify your command as follows, obviating the need for a ForEach-Object call:
Import-csv C:\path\info.csv | Group-Object UID |
Send-MailMessage -From "<Me#email.com>" -To { "<$($_.Name)#Email.com>" } `
-Attachments { "C:\path\$($_.Name).csv" } `
-Subject "Testing" -Body "Please Ignore This" -Priority High `
-SmtpServer smtp.server.com
In short, the script blocks passed to -To and Attachments are evaluated for each input object, and their output determines the parameter value in each iteration. In the script block, $_ represents the pipeline object at hand, as usual.
Note that such delay-bind script blocks can only be used with parameters that are designed to accept pipeline input (irrespective of whether by value (whole object) or by a specific property's value).

Related

Adding a variable to a -Body when sending an email

I'm creating a PowerShell script to make our starters and leavers process smoother. We have a separate team who needs to add certain accounts.
What I'd like to do is take the variable that is declared at the start of the script, the new users name, and put it in an email asking for this other department to set them up.
$username = "joe"
Send-MailMessage -SmtpServer smtp.office365.com -From "it#support.com" -To "other#department.com" -Subject 'Starter/Leaver ' -Body "Hi department, `n `nPlease can you add/remove" $username "from the necessary account please `n `nThanks"
I get an error saying:
Send-MailMessage : A positional parameter cannot be found that accepts argument "joe"
The issue here is that the string object sent to -Body is broken because of the quoting. You can just surround the entire body with one set of quotes to achieve the desired result.
$username = "joe"
Send-MailMessage -SmtpServer smtp.office365.com -From "it#support.com" -To "other#department.com" -Subject 'Starter/Leaver ' -Body "Hi department, `n`nPlease can you add/remove $username from the necessary account please `n`nThanks"
Neater Alternative:
I know this answer is not as concise, but it is more readable and adds some flexibility. It uses a here-string to create the body, which doesn't require having to add all of the line feed characters. It also uses splatting to execute the command. With splatting, you can just update the hash table when you need to change something.
$Body = #"
Hi department,
Please can you add/remove $username from the necessary account please
Thanks
"#
$Message = #{ SmtpServer = 'smtp.office365.com'
From = "it#support.com"
To = "other#department.com"
Subject = 'Starter/Leaver'
Body = $Body
}
Send-MailMessage #Message
When running a PowerShell command, parameters are space-delimited. In the case of Send-MailMessage positional parameters are also allowed, which means you don't have to provide the parameter name when passing in a value. In your attempt, your first quote pair was passed to the -Body parameter. After the first closing quote, a space followed by $username is interpreted. Because positional parameters are enabled for the command, PowerShell attempts to assign $username to a parameter and fails. Of course this also means that if you intend to include a space in your string, it must be surrounded by quotes.
Additional Reading:
See About Parameters for an overview of how parameters work.
See About Splatting for information on splatting.

Send-MailMessage shortens lines in message when triggered by Task Scheduler and breaks FullNames

I'm not able to find a reason why lines in emails are shortened when it is triggered by Task Scheduler(lines aren't shortened when the script is executed manualy from ISE!). I'd like to pass FullName to email and use it as link to document (when the path and file doesn't contain spaces, the link works great).
If I use "format-list" instead of "format-table" it looks better (even when triggered by Task Scheduler) and I have to add parameter "$body = $newdoc | Out-String -Width 255" to prevent breaking lines - but space in filenames still breaks links:
Next thing is the FullName contains spaces - I tried many ways (like $variable.replace; $var = $var -replace " ","` ", etc.)
#date and time formating
$culture = Get-Culture
$culture.DateTimeFormat.LongTimePattern = 'HH:mm'
$culture.DateTimeFormat.ShortDatePattern = 'dd-MM-yyyy'
Set-Culture $culture
#find files changed during last hour, sort descending
$newdoc = get-childitem -File -Path \\ottm09itoms01\OTA-IT_Operators\ -Recurse | ? {$_.LastWriteTime -gt (Get-Date).AddHours(-1)} | sort lastwritetime -Descending | Format-table -Property LastWriteTime, fullname
$body = $newdoc | Out-String
$enc = New-Object System.Text.utf8encoding
Send-MailMessage -From $sender -To $receiver2 -Subject "Documents updated" -body $body -Encoding $enc -SmtpServer $SMTPserver
After some digging, I suspect the problem is your use of Out-String which truncates output based on the -width parameter, which you have left unspecified. To quote the documentation:
-width
Specifies the number of characters in each line of output. Any additional characters are truncated, not wrapped. If you omit this parameter, the width is determined by the characteristics of the host program. The default value for the Windows PowerShell console is 80 (characters).
In other words, when you run this script in the ISE Out-String probably sets the width to whatever the buffer width of the ISE is, but when Task Scheduler runs it, it uses the default 80 character width.
So basically just add -width 120 (or the value of your choosing) to your Out-String and see if that fixes the problem.
To fix the links breaking on whitespace you might have to manually generate some HTML for them using -replace. Something like:
$body = $body -replace '(\\\\.*[^\s])','$1'
$body = $body.trim() -replace "`n","<br>`n"
This assumes that all your paths are UNC paths (i.e., paths starting with \\). You'd then need to throw the -BodyAsHtml on your Send-MailMessage command. This is kind of thrown together and I'm sure there's probably a better way of doing things, but it should work.

Define To: field from from various values

In a CSV file I have various columns and two of those are for emails. Each row within these email columns have a group of emails.
I want to be able to send to these email addresses from a script.
I have everything setup and working, except for the TO:.
The idea of the script is that it loops each line of the csv and generates an email grabbing values from the cells of that row into various parts of the body. Then it sends of an email and loops back to the next line of the CSV to do the same, and so on until it reaches the end of the CSV file.
I'm having issues to plug a variable for the email columns, I'm guessing because the emails don't have "quotations".
How do I bring these in?
In a nutshell for the code
data is imported CSV
a loop is created foreach line of imported data
smtp, from, to, subject, attachments, body variables are defined
then the sendmail-message command is provided.
close the loop
##For the purpose of this, the emaildata.csv looks like this sample:
"NameGroup","emailGroupA","emailGroupB"
"Groupabc","a.b#b.com;c.a#b.com","xyv#b.com;xxd#b,com"
"Grouptrd","ca.r#b.com;as.b#b.com","aaa#a.com;bbb#b.com"
"Groupghd","dd.r#b.com;dd.b#b.com","dddaa#a.com;ddddddb#b.com"
$DataDir = "C:\Users\acastrellon\Documents"
$Data= import-csv $DataDir\emaildata.csv
foreach ($i in $Data) {
$NameGroup = $i.NameGroup
$TeamA = $i.emailGroupA.replace(';',"`n")
$TeamB = $i.emailGroupB.replace(';',"`n")
function send-email {
$smtpserver = " server.smtp"
$from = "myemail.com"
$to = $TeamA,$TeamB
send-MailMessage -From $from -To $to -Subject $subject -Body $body -SmtpServer $smtpServer
}
[string] $subject = "some text here: $NameGroup"
#[string] $attachment = "not here yet"
[string] $body = "
Text here that calls out $NameGroup
This also lists: $TeamA
This here lists $TeamB
Done"
send-email -subject $subject -attachment $attachment -body $body
}
#this should loop to get data from next line of csv and generate a new email with data.
Unlike a batch file where environment variables have a global impact to the current session, PowerShell isolates variables in different scopes.
You're referencing the two $TeamA & $TeamB variables inside a function but you set their values outside the function. Because the function scope (where they are read) is different to the script scope (where you set them) those variables will be empty inside the send-email function in your script.
Have a read on PowerShell scopes as you'll need to make some changes to your script functions; to either read the variables from the script scope ($script:TeamA) or to pass them into the function as a parameter

Outputting CSV to String/Table

I have a CSV with data that is to be emailed to me at the end of a powershell script. I know that I can attach the CSV file to the email itself, but I'd like to know how to output the data from the CSV straight into the body, formatted properly. I assume that this would involve converting the CSV to a String or something.
I've tried things like ConvertTo-Csv along with Format-Table, but this comes up without formatting. I've thought of looping through the csv line-by-line but I'm not sure where to go from there.
You need to use ConvertTo-Html
$body = $YOUR_CSV_DATA | ConvertTo-Html -Fragment
Send-MailMessage -To 'abc#example.com' -From 'admin#example.com' -Subject 'Your report' -BodyAsHtml -Body $body -SmtpServer 'smtp.example.com'

Scheduling Powershell changes ObjectType

I've written a little script that checks for differences between 2 text files.
$new = get-content $outPutFile
$old = get-content $outPutFileYesterday
$result = $null
$result = Compare-Object $old $new
$resultHTML = $result.GetEnumerator() | ConvertTo-Html
Send-MailMessage -SmtpServer 10.14.23.4 -From me#mail.com -To $toAddress -Subject "DiffTest" -Body "$resultHTML" -BodyAsHtml
When I run it from an active PowerShell prompt, all is well. However, when I try to schedule it to run daily I get this error on the run-time (the block above is in a try catch that mails any execution errors):
Method invocation failed because [System.Management.Automation.PSCustomObject] doesn't contain a method named 'GetEnumerator'.
How can I fix this?
The script may run in a different user context when scheduled, potentially with a different set of read/write permissions on the filesystem.
However, In PowerShell arrays are automatically enumerated when used in expressions, so you don't need to
call the GetEnumerator() method before passing it to ConvertTo-Html.
You could start by changing your script to:
$resultHTML = $result | ConvertTo-Html
and see how it impacts the result.
Compare-Object either returns:
$null: if the ReferenceObject and the DifferenceObject are equal
an object of type PSCustomObject: if only one item differs (1)
an array of objects: if multiple differences have been found
Of these return values only the last one (the array) has a GetEnumerator() method. ConvertTo-Html produces the expected output when fed either of these return values, so you can safely drop the .GetEnumerator() part (as mentioned by Enrico). Another option is to wrap the $result in an array, which would change line 6 of your script to:
$resultHTML = #($result).GetEnumerator() | ConvertTo-Html
(1) This is the return value for compare-object in your script