Try-Catch-Finally not working with Send-Mailmessage in PowerShell script - powershell

I'm using try-catch-finally blocks in my account expiration notification email and the if condition in the finally block isn't considered:
Write-Host $sName " : Selected to receive email: password will expire in "$days
if (($emailaddress) -ne $null) {
try {
Send-Mailmessage -smtpServer $SMTPServer -from $MailSender -to $emailaddress -subject $subject2 -body $EmailBody -bodyasHTML -priority High -Encoding $textEncoding -ErrorAction Stop
}
catch {
write-host "Error: Could not send email to $recipient via $smtpServer"
$sent = "Send fail"
$countfailed++
}
finally {
if ($error.Count -eq 0) {
write-host "Sent email for $sName to $emailaddress"
$countsent0++
}
}
} else {
Write-Host "$dName ($sName) has no email address."
$sent = "No"
$countnotsent++
}
Expecting $countsent0 to increment and $sent to be set to the appropriate message. The catch block works ($countfailed increments and $sent is set to "Send fail"). The last else statement after the finally block works as well (if there is no email address for the account $countnotsent increments and $sent is set to "No").

The automatic $Error variable is a running log of all errors that have occurred in the entire session so far, so unless you run $Error.Clear() beforehand, you may get false positives with if ($error.Count -eq 0) { ... }
However, since you may not want to erase this log for the entire session, consider using a simple Boolean variable to indicate whether an error was caught or not.
A simplified example:
$countsent0 = 0
$ok = $false # Helper Boolean variable.
try {
1 / 0 # Simulate an error that can be caught.
$ok = $true # At the end of this block, signal that no error occurred.
}
catch {
Write-Warning 'oops!'
}
finally {
if ($ok) { $countsent0++; Write-Host 'All''s well'. }
}
"`$countsent0: $countsent0"
However, given that your catch block doesn't abort execution, you don't even need a finally block:
$countsent0 = 0
try {
1 / 0 # Simulate an error that can be caught.
# Getting here implies success.
$countsent0++
}
catch {
Write-Warning 'oops!'
}
"`$countsent0: $countsent0"

Related

Call a different function based on value of $event

This tool has 2 possible options;
Reset 1 Access Point
Reset ALL Access Points at a site
For the sake of record keeping, I have a function that sends an email alert when either of these events occur.
Reset a single AP:
Function Manage-APReset {
Write-Verbose "Function start: Manage-APReset"
Write-Host "Executing access point reset for $apName .."
IF($controllerName -eq $null) {
Error-NoCon }
else {
## Establish connection(s)
[string]$cUser = "srv-****"
$cPassword = ConvertTo-SecureString -String "X***********" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ($cUser, $cPassword)
Write-Host "Establishing SSH connection to Cisco Controller $controllerName"
New-SSHSession -ComputerName $controllerName -Credential $cred
$session = Get-SSHSession -Index 0
$stream = $session.Session.CreateShellStream("PS-SSH", 0, 0, 0, 0, 100)
sleep 4
Write-Host "Connected. Authenticating for SSH Stream.."
## Invoke login
$stream.WriteLine('srv-*****')
sleep 3
$stream.WriteLine('X********j8')
sleep 2
$Stream.Read()
Write-Host "Authenticated!"
## Invoke commands
$stream.WriteLine("config ap reset $apName")
sleep 2
$stream.WriteLine('y')
sleep 3
$stream.Read()
Write-Host "$apName has been reset successfully. Please allow up to 5 minutes to come back up"
Admin-SendAlert($event = 1)
Remove-SSHSession -SessionId 0,1,2,3,4,5,6,7,8,9
Repeat
}
}
You'll notice at the end I call Admin-SendAlert which handles the email alert. During this time I pass $event = 1 to allow Admin-SendAlert to know what condition is occurring.
Function Admin-SendAlert {
Write-Verbose "Function started: Admin-SendAlert"
## Event 1: Single AP Reset Successfully
if($event = 1) {
$eventSub = "Single: $apName has been reset"
$eventBod = "$apName has been reset successfully by $tech`n Reason Summary: $reasonSum"
}
if($event = 2) {
$eventSub ="Full Store Reset: $Store All APs Reset"
$eventBod = "The following APs have been reset at Store $Store by user $tech. `n`nAll APs:`n $apArray`n Reason Summary: $reasonSum"
}
Send-MailMessage -To "CSOC <blank#email.com>" -From "AP Manager Beta <srv-blank#email.com>" -Subject $eventSub -SmtpServer smtp.email.com -Body $eventBod
}
I don't believe this is how this should be handled as the value of $event remains whichever comes first. How should I be doing this?
Your main issue is that you are using the assignment operator in place of the equality comparator. So if($event = 1) should be if($event -eq 1)
I see room for improvement as well for you if clauses. You are checking the numerical value of $event. It will only be one of those values. Never two. Your if clauses are mutually exclusive yet you attempt to evaluate both -eq 1 and -eq 2. Not the best idea as it make for muddy code. You should be using if and elseif to contain it in the same block
if($event -eq 1) {
# Stuff happens
} elseif($event -eq 2) {
# Stuff happens
} else {
# Last resort
}
Further to that, if yourself with too many elseif clauses, you would be better off using switch
switch($event){
1 {
# Stuff happens
break
}
2 {
# Stuff happens
break
}
default {
# Last resort
}
}
Note: if you don't use break it will evaluate all switch conditions and execute all that match.

Best way to terminate a PowerShell function based on parameters

I have a few functions that get called either from Jenkins as part of a pipeline, they also get called from a pester test or lastly they can get called from the powershell console. The issue I have really stems from Jenkins not seeming to handle write-output in the way I think it should.
So what I am doing is creating a Boolean param that will allow my to choose if I terminate my function with a exit code or a return message. The exit code will be used by my pipeline logic and the return message for the rest ?
Is there a alternate approach I should be using this seems to be a bit of a hack.
function Get-ServerPowerState
{
[CmdletBinding()]
param
(
[string[]]$ilo_ip,
[ValidateSet('ON', 'OFF')]
[string]$Status,
[boolean]$fail
)
BEGIN
{
$here = Split-Path -Parent $Script:MyInvocation.MyCommand.Path
$Credentials = IMPORT-CLIXML "$($here)\Lib\iLOCred.xml"
}
PROCESS
{
foreach ($ip in $ilo_ip)
{
New-LogEntry -Message ("Getting current powerstate " + $ip)
If (Test-Connection -ComputerName $ip.ToString() -Count 1 -Quiet)
{
$hostPower = Get-HPiLOhostpower -Server $ip -Credential
$Credentials -DisableCertificateAuthentication
}
}
}
END
{
If($fail){
New-LogEntry -Message "Script been set to fail with exit code" -Log Verbose
New-LogEntry -Message "The host is powered - $($HostPower.Host_Power)" -Log Verbose
If($hostPower.HOST_POWER -match $Status)
{
Exit 0
}
else {
Exit 1
}
}
else {
New-LogEntry -Message "Script been set to NOT fail with exit code" -Log Verbose
New-LogEntry -Message "The host is powered - $($HostPower.Host_Power)" -Log Verbose
If($hostPower.HOST_POWER -match $Status)
{
return 0
}
else {
return 1
}
}
}
}
Like this
function Get-Output {
param ([switch]$asint)
if ($asint) {
return 1
}
else {
write-output 'one'
}
}
Get-Output
Get-Output -asint
If you intend to use the output in the pipeline then use Write-Output. If you intend to only send it to the host process then use Write-Host. I typically use the return keyword if I want to assign a return value to a variable.
[int]$result = Get-Output -asint

Send-MailMessage returns 4.4.1 connection timed out every 10 minutes

I have a PowerShell script that generates emails from a list of contacts imported from a CSV file:
# Get Credential
$Credential = & "C:\Powershell Scripts\Windows\Get-CredentialFromWindowsCredentialManager.ps1" ms.outlook.15:my.email#domain.com
# Get Contacts
$contacts = Import-Csv -Path "C:\Powershell Scripts\Email\Contacts.csv"
# Compose Email for each contact
foreach( $contact in $contacts )
{
Write-Output "Creating email for: $($contact.FirstName) $($contact.LastName)"
$To = "$($contact.FirstName) $($contact.LastName) <$($contact.Email)>"
$From = "My Email <my.email#domain.com>"
$Subject = "$($contact.FirstName), I have a suggestion for you!"
$Body = "<html></html>"
$SMTPServer = "smtp.office365.com"
$Port = 587
Send-MailMessage -To $To -From $From -Subject $Subject -SmtpServer $SMTPServer -Credential $Credential -UseSsl -Body $Body -BodyAsHtml -Port $Port
# Due to the Message Send rate limit (30 per minute) I added this to slow the rate down
Start-Sleep -Seconds 10
}
Every 10 minutes I get the following SMTP Exception:
Send-MailMessage : Service not available, closing transmission channel. The
server response was: 4.4.1 Connection timed out. Total session duration:
00:10:08.3716645
At C:\Powershell Scripts\Email\SendEmail.ps1:17 char:2
+ Send-MailMessage -To $To -From $From -Subject $Subject -SmtpServer $SMTPServer ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.Mail.SmtpClient:SmtpClient) [Send-MailMessage], SmtpException
+ FullyQualifiedErrorId : SmtpException,Microsoft.PowerShell.Commands.SendMailMessage
Are there any settings that I can modify or changes in code that will prevent this?
Don't take this personally, this is just a general rant, but you've got a script that performs an action against a resource that is out of your control. As such, you can't just expect an SMTP connection to succeed and wonder what you should do to prevent it from failing. Deep breath. The answer is to consider edge cases and actually cater for them in your code. Granted, you've got a sleep in there to try to not fall foul of rate limiting, but that's not a robust solution. In this instance, a simple exception handler around the Send-MailMessage call would suffice. You could include a number of retries and small sleep delays.
A "maximum number of acceptable failures" threshold could be used to break out of the retry loop and cause some kind of inward alerting, Etc.
Long story short, don't just throw spaghetti at the wall with your eyes closed.
</rant>
An example, but not necessarily the tidiest solution:
[Int32] $maxAttempts = 5;
[Int32] $failureDelay = 2;
[Int32] $interMessageDelay = 10;
[Int32] $numAttempts = 0;
[Boolean] $messageSent = $false;
:CONTACT foreach ( $contact in $contacts ) {
$numAttempts = 1;
$messageSent = $false;
while ( ($numAttempts -le $maxAttempts) -and (! $messageSent) ) {
try {
Write-Host -Object ( 'Sending message, attempt #{0} of #{1}...' -f $numAttempts, $maxAttempts );
Send-MailMessage <blah>;
Write-Host -Object "Ok.`n";
$messageSent = $true;
} #try
catch [System.Exception] {
# ^^^^^ This exception type needs changing, but I don't know the full
# type of your failure.
Write-Host -Object 'Failed.';
if ( $numAttempts -ge $maxAttempts ) {
Write-Host -Object "ERROR : Maximum attempts reached - aborting.`n";
continue CONTACT;
} else {
Write-Host -Object ( 'Sleeping for {0} second(s)...' -f $failureDelay );
Start-Sleep -Seconds $failureDelay;
$numAttempts++;
} #else-if
} #catch
} #while
Write-Host -Object ( 'Sleeping for {0} second(s)...' -f $interMessageDelay );
Start-Sleep -Seconds $interMessageDelay;
} #foreach

Receive Location Autohandling false email alerts

$exceptionList = Get-Content C:\Users\Dipen\Desktop\Exception_List.txt
$ReceiveLocations = Get-WmiObject MSBTS_ReceiveLocation -Namespace 'root\MicrosoftBizTalkServer' -Filter '(IsDisabled = True)' |
Where-Object { $exceptionList -notcontains $_.Name }
# Exit the script if there are no disabled receive locations
if ($ReceiveLocations.Count -eq 0)
{
exit
}
Example:
and
$mailBodyPT = ""
$mailTextReportPT = "There are: "
[STRING]$Subject = $SubjectPrefix + $BizTalkGroup
$mailTextReportPT += "in the BizTalk group: " + $BizTalkGroup + "."
#Send mail
foreach ($to in $EmailTo)
{
$Body = $HTMLmessage
#$SMTPClient = New-Object Net.Mail.SmtpClient($PSEmailServer)
$message = New-Object Net.Mail.MailMessage($from, $to, $Subject, $Body)
$message.IsBodyHtml = $true;
$SMTPClient.Send($message)
}
Question: when all RLs have the status "disabled" and all of these RLs are included in the exception list the value of the variable $ReceiveLocations should be false and I need to stop further processing in my script. (do nothing if all RLs are found in exception list, just exit)
But I'm still getting false email alerts. How can we set logic for not getting email alerts if there were no extra RLs found in $ReceiveLocations?
The value of the variable $ReceiveLocations is $null when your Get-WmiObject statement doesn't return results. $null doesn't have a property Count, hence the check $ReceiveLocations.Count -eq 0 fails and your script doesn't terminate before sending an e-mail.
You can avoid this issue in a number of ways, e.g. by putting $ReceiveLocations in the array subexpression operator:
if (#($ReceiveLocations).Count -eq 0) {
exit
}
or you could use the way PowerShell interprets values in boolean expressions (non-empty arrays become $true, $null becomes $false):
if (-not $ReceiveLocations) {
exit
}

Powershell to Monitor Website Availablity

I have a PS script which monitors all websites listed in text file. As of now i i am running it as a job so whenever any of website goes down it throws an alert on registered mail ID with the HTML file as an attachment consisting of all website status. What i want now that it only pick the those websites which goes down and throes me an alert. Can someone help me here please.
Script:-
$URLListFile = "C:\Users\Desktop\URL.txt"
$URLList = Get-Content $URLListFile -ErrorAction SilentlyContinue
$Result = #()
Foreach($Uri in $URLList) {
$time = try{
$request = $null
## Request the URI, and measure how long the response took.
$result1 = Measure-Command { $request = Invoke-WebRequest -Uri $uri }
$result1.TotalMilliseconds
}
catch
{
<# If the request generated an exception (i.e.: 500 server
error or 404 not found), we can pull the status code from the
Exception.Response property #>
$request = $_.Exception.Response
$time = -1
}
$result += [PSCustomObject] #{
Time = Get-Date;
Uri = $uri;
StatusCode = [int] $request.StatusCode;
StatusDescription = $request.StatusDescription;
TimeTaken = $time;
}
}
#Prepare email body in HTML format
if($result -ne $null)
{
$Outputreport = "<HTML><TITLE>Website Availability Report</TITLE><BODY background-color:peachpuff><font color =""#99000"" face=""Microsoft Tai le""><H2> Website Availability Report </H2></font><Table border=1 cellpadding=0 cellspacing=0><TR bgcolor=gray align=center><TD><B>URL</B></TD><TD><B>StatusCode</B></TD><TD><B>StatusDescription</B></TD><TD><B>TimeTaken</B></TD</TR>"
Foreach($Entry in $Result)
{
if($Entry.StatusCode -ne "200")
{
$Outputreport += "<TR bgcolor=red>"
send-mailmessage -to "abc#gmail.com" -from "abc#gmail.com" -subject "Test mail" -SmtpServer XXX####.com -Attachments "C:\Users\Desktop\Test.htm"
}
else
{
$Outputreport += "<TR>"
}
$Outputreport += "<TD>$($Entry.uri)</TD><TD align=center>$($Entry.StatusCode)</TD><TD align=center>$($Entry.StatusDescription)<TD align=center>$($Entry.timetaken)</TD></TR>"
}
$Outputreport | out-file C:\Users\Desktop\Test.htm
$Outputreport += "</Table></BODY></HTML>"
}
I can think of two ways to do this. First I assume you are trying to capture each item from $results where the status code is anything other than a success code. If this assumption is correct then you already have most of the solution in your code. The following line:
if($Entry.StatusCode -ne "200")
{
$Outputreport += "<TR bgcolor=red>"
send-mailmessage -to "abc#gmail.com" -from "abc#gmail.com" -subject "Test mail" -SmtpServer XXX####.com -Attachments "C:\Users\Desktop\Test.htm"
}
is identifying those records, so you can simply grab them here in the loop. Like this:
if($Entry.StatusCode -ne "200")
{
$Outputreport += "<TR bgcolor=red>"
send-mailmessage -to "abc#gmail.com" -from "abc#gmail.com" -subject "Test mail" -SmtpServer XXX####.com -Attachments "C:\Users\Desktop\Test.htm"
$failedCodeUri += $Entry.uri
}
In the above snippet the variable $failedCodeUri must be declared with the scope necessary to be accessible inside and outside of the foreach loop. This can be done as simply as declaring the variable on a line immediately preceding the beginning of the foreach loop. It also must be a type that can utilize the += operator. This can be done with a simple array like this.
$failedCodeUri = #()
Of course if you need to capture more than just the uri you will need a more sophisticated data type than a simple array of strings.
The other possible solution depends on the 'where' method being available on your $results object. If it is available you can simply do this:
$failedCodeUri = $results.where({$_.StatusCode -ne "200"})
This last technique would not be used inside the loop. Either before or after your foreach loop you can use this line of code to return all of the desired results.
A few things to note about this technique:
Within the 'where' code block the condition is being checked for each item in the result set. You reference the current item with $_ using the dot operator to access a property or method that exists for that item.
In the above example the entire item, with all of its properties will be returned. If you only want the uri you can do something like this:
$failedCodeUri = $results.where({$_.StatusCode -ne "200"}).uri