How to handle optional parameters in powershell cmdlet - powershell

Question:
I am writing a cmdlet that will accept parameters and send emails. "Cc" is one of the parameters; and is non-mandatory.
Today the code shows TWO lines invoking Send-MailMessage (like the following paragraph), but i am sure there is a better way to write it:
if( $cc -eq $null){
Send-MailMessage -From $from ... ## call without -Cc
} else {
Send-MailMessage -From $from ... -Cc $cc... ## call WITH -Cc
}
I would like to avoid branching and writing the line twice.
Or even worse than "twice", writing all the combinations for each optional parameter.
(Of course, the fact that the cmdlet sends email is not important here. The problem will stand for any cmdlet that needs to avoid optional parameters)
What is the best-practice way to do this?
THANK YOU

Use splatting:
$Params = #{}
if($ShouldUseCc) {
$Params.Add('Cc', $CcValue)
}
if($ShouldUseBcc) {
$Params.Add('Bcc', $BccValue)
}
Send-MailMessage -From $from ... #Params

Related

Send email to multiple address in powershell

$PE is holds the email address of a user which is retrieved from AD user profile. I need to sent email to that user. As it keeps on changing as per user profile I am finding it difficult to pass it into this email function.
$PE=$(Get-aduser -filter {name -like $name} -properties mail).mail
Function abc($subject,$body,$PE)
{
$Htmlbody="____"
$toaddress = $PE
If (attachment -eq "")
{
Send-emailmessage -from "abc#xym.com" -bodyashtml -To $toaddress -body $htmlbody -subject $subject
}
}
I tried the above but I am getting an error for -To field and I don't see email address in $PE whereas $PE is holding the value before calling this function and inside the function it's empty.
Here is the error :Cannot validate argument on parameter 'To' .the argument is null or empty . Provide an argument that is not null or empty . Then try again
(1) Variables defined in a function are applicable only inside that function unless you scope otherwise - see Powershell documentaiton about "Scoping" for details
(2) Create the function then PASS the parameters to the function. It's easier to understand this if you use the "longhand" way of passing parameters
$PE=$(Get-aduser -filter {name -like $name} -properties mail).mail
Function abc {
Param(
$subject,
$body,
$toaddress)
Send-emailmessage -from "abc#xym.com" -bodyashtml -To $toaddress -body $body -subject $subject
}
abc -subject "MySubject" -body "____" -toaddress $PE

Powershell send-mailmessage to read 17k emails from csv & send to 100 users in each email

I'm creating a PS script to automate email blast to 17k users. Our exchange security baseline is set to only accept 60 requests per minute. Because I'm looping through the email list (CSV) line by line (sleep 1 sec), it took hours for my script to complete. What I'm trying to achieve now is to send the email to 100 users per request. I'm figuring out how to store the emails in an array of 100 & send the mail before going for the next 100. Any suggestion?
$recipients = Get-Content "mailinglist.csv"
foreach($rcpt in $recipients)
{
Write-Host "Attempt sending email to $rcpt ..."
Send-MailMessage -ErrorAction SilentlyContinue -ErrorVariable SendError -From $From -to $rcpt -Subject $Subject -SmtpServer $SMTPServer -port $SMTPPort -UseSsl -Credential $Cred -BodyAsHtml ($Body -f $Subject, $Date, $Venue, $Description, $Image)
$ErrorMessage = $SendError.exception.message
If($ErrorMessage)
{
Write-Host "Failure - $ErrorMessage" -ForegroundColor Red
Start-Sleep -Seconds 60
Send-MailMessage -ErrorAction SilentlyContinue -ErrorVariable SendError -From $From -to $rcpt -Subject $Subject -SmtpServer $SMTPServer -port $SMTPPort -UseSsl -Credential $Cred -BodyAsHtml ($Body -f $Subject, $Date, $Venue, $Description, $Image)
}
ElseIf($SendError.exception.message -eq $null)
{
Write-Host "Email has been sent to $rcpt" -ForegroundColor Green
Start-Sleep -Seconds 1
$n++
}
}
Write-Host "Total sent = $n"
You could use a traditional for loop and access your array elements by index.
$recipients = Get-Content "mailinglist.csv"
$To = <SomeValidEmailAddress>
$LastIndex = $recipients.GetUpperBound(0)
for ($i = 0; $i -le $LastIndex; $i+=100) {
$upperRange = [Math]::Min(($i+99),$LastIndex)
$Params = #{
ErrorAction = 'SilentlyContinue'
ErrorVariable = 'SendError'
Bcc = $recipients[$i..$upperRange]
To = $To
From = $From
Subject = $Subject
SmtpServer = $SMTPServer
Port = $SMTPPort
Credential $Cred
Body = $Body -f $Subject, $Date, $Venue, $Description, $Image
BodyAsHTML = $true
UseSsl = $true
}
"Attempt sending email to $($recipients[$i..$upperRange]) ..." # You may want to alter this to be more readable
Send-MailMessage #Params
# Other code
}
Explanation:
I've opted to use Splatting here for readability and manageability with the $Params hash table. It is entirely optional.
The -bcc parameter of Send-MailMessage supports a string array (string[]). Using this over the -To parameter will preserve privacy of the recipients. You can then easily send an email to multiple recipients provided you pass it an array. However, -To is required for Send-Mailmessage to work. It is recommended to make the email address passed into -To something that can be spammed or has a way of handling these types of emails. I have set up the $To variable for you to provide that email address. If privacy is of no concern whatsoever, -Bcc can just be replaced with -To.
Since $recipients is an array, you can access its elements by index, which supports the range operator ... $recipients[0..99] would be the first 100 items in the list.
$LastIndex stores the last index of the list, which is the value returned by the Array.GetUpperBound(Int32) method with dimension 0. Since the array is one-dimensional, 0 is the only dimension.
$upperRange is the beginning index ($i) plus 99. Should $upperRange ever be larger than $LastIndex, it will be set to $LastIndex. Depending on your PowerShell version, the $i+99 and $LastIndex comparison may not be necessary. Accessing an upperbound range beyond the size of the array, will just return all of the remaining elements of the array without throwing an error. This is likely just for completeness.

function to send email multiple times

I am working on a PowerShell script which sends emails multiple times with different subject and body each time.
I am trying to move Send-MailMessage into a function or something that I could use to reduce the code lines.
$Sender = 'jones#example.com'
$text = "<html><body>"
$text += "<p>Welcome</p>"
### A cmdlet that would give recipient email address
$Recipient = (Get-Details -user $user).email
$smtp = "server.example.com"
$subject = "welcome email"
Send-MailMessage -BodyAsHtml $text -from $Sender -SmtpServer $smtp -Priority high -to $Recipient -Subject $subject
Write-Output "executing commands to capture results"
Write-Output ""
### Few Commands executed in this step
Write-Output "Analyzing results"
### Few commands executed in this step
$newtext = "<html><body>"
$newtext += "Congrats, you are selected"
$newsubject = "results email"
Send-MailMessage -BodyAsHtml $newtext -from $Sender -SmtpServer $smtp -Priority high -to $Recipient -Subject $subject
You could create a function like this:
Function Send-Email($text,$subject,$recipient)
{
Send-MailMessage -BodyAsHtml $text -From "jones#example.com"
-SmtpServer "server.example.com" -Priority High -To $recipient -Subject $subject
}
You can call it like:
Send-Email -text "Hello" -subject "Test" -recipient "test#example.com"
You can add or remove arguments depending on what will change though. Assuming the smtp server won't change for example, this isn't needed as a parameter.
I am trying to move Send-MailMessage into a function or something that I could use to reduce the code lines.
Writing a function for one line can be useful if the options are many and never change. However you do have one that changes. There is another PowerShell feature that would work here just as well. Splatting!
$emailParameters = #{
From = $Sender
SmtpServer = $smtp
Priority = "high"
To = $Recipient
Subject = $subject
}
Send-MailMessage -BodyAsHtml $text #emailParameters
# ... other code and stuff
Send-MailMessage -BodyAsHtml $newtext #emailParameters
Now you still only have to make changes in one place and the code is arguably more terse.
Another point is that when you are making multi-line strings all at once, as supposed to building over the course of the script you can always use here strings. You only have two lines but if you code evolves over time it is a good tactic to start early instead of many $object += "string" lines
$text = #"
<html><body>
<p>Welcome</p>
"#
Note that indentation is preserved in the resulting here-string. The "# has to appear on its own line with no leading whitespace. Using double quotes means you can still expand variables as well in there.

Best practice for emailing a body of appended strings?

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

Powershell Function runs for each item in array (I don't want this to happen)

I have created a script to provision Lync users, important details (such as assigned LineURI) for new provisions need to be emailed. Also any errors need to be sent (fluffed up with some friendly error messages of course :)).
So I created a few CSVs with all relevant data..
Then I created a function:
Function Send-Email ($attachArray) {
# Get a list of to addresses
$toAddresses = "foo#corp.local","bar#corp.local"
# Process replacments
Replace-EmailMasks
# Send conditionaly
Switch ($attachArray) {
$null {
Send-MailMessage -SmtpServer "internalrelay.corp.local" `
-From "test#andylab.local" -To $toAddresses `
-Subject "There should really be something more informative here" `
-BodyAsHTML $SCRIPT:htmlBody
}
Default {
Send-MailMessage -SmtpServer "internalrelay.corp.local" `
-From "test#andylab.local" -To $toAddresses `
-Subject "There should really be something more informative here" `
-BodyAsHTML $SCRIPT:htmlBody
-Attachments $attachArray
}
}
}
Here's how I invoke it:
# Logic, then send
If (($npSuccess -gt 0) -AND ($errorsExist -gt 0)) {
# Attaching both
# Heres the summary paragraph
$SCRIPT:customSummary = '<p>Success and errors :|</p>'
# Now I'm sending it.
Send-Email "$($tempPlace.fullname)\NewProviSsion_Output.csv","$($tempPlace.fullname)\Errors_Output.csv"
} ElseIf ($npSuccess -gt 0) {..} # output-generating Success
ElseIf ($errorsExist -gt 0) {..} # Failed somewhere
Else {..} # no output-generating Success, no overall fails
Now this works; Email looks nice, goes to who it should, files attached etc..
Problem is:
For however many files I specify in $attachArray, that's how many emails get sent. The emails are all exactly the same, going to all the same people n many times.
It's as if i'm doing this:
ForEach ($item in $attachArray) {
Send-Email "$($tempPlace.fullname)\NewProviSsion_Output.csv","$($tempPlace.fullname)\Errors_Output.csv"
}
Except i'm not..
To clarify my objective, I want the email to be sent to all in $toAddresses only once.
Can anyone enlighten me as to what's going on here?
Maybe I've just had a bad Monday morning..
The switch statement fires for each element of the array. This behavior is documented (check Get-Help about_Switch):
If the test value is a collection, such as an array, each item in the collection is evaluated in the order in which it appears.
Use a regular conditional instead (since you have only 2 cases anyway):
if ($attachArray -eq $null) {
Send-MailMessage -SmtpServer "internalrelay.corp.local" `
-From "test#andylab.local" -To $toAddresses `
-Subject "There should really be something more informative here" `
-BodyAsHTML $SCRIPT:htmlBody
} else {
Send-MailMessage -SmtpServer "internalrelay.corp.local" `
-From "test#andylab.local" -To $toAddresses `
-Subject "There should really be something more informative here" `
-BodyAsHTML $SCRIPT:htmlBody
-Attachments $attachArray
}