PowerShell: Script to Send Emails

In the past, a simple email relay script was sufficient to spool internal messages. However, information security had been enforced with many cloud service providers whereby SSL/TLS with credentials were required for STMP connections. Hence, this little snippet helps us keep up with the times, while being backward compatible to legacy systems.

# This iteration includes the workaround for anonymous email relays
<#
# Examples:
# sendEmail john@contoso.com '' mary@contoso.com -cc $null test testEmail -smtpServer 'relay.contoso.com' -port 25 -useSsl $false -anonymous $true
# sendEmail -emailFrom $EmailUser -emailPassword $emailPassword `
            -emailTo 'admin@kimconnect.com' -cc $null `
            -subject "Test Email to Validate SMTP" -body "This is a test email.<br><br>Please disregard" `
            -smtpServer $smtpServer -port $port -attachments $attachments -useSsl $true
#>

$username="test@kimconnect.net"
$password="PASSWORD"
$emailFrom="anonymous@kimconnect.net"
$emailTo="someuser@contoso.com"
$subject="Test Email to Validate SMTP"
$body="This is a test email.<br><br>Please disregard"
$smtpServer
$port=587
$attachments='c:\gpresult.html'
$useSsl=$true

<# Usage:
sendEmail -username $username `
    -password $password `
    -emailFrom $emailFrom `
    -EmailTo $emailTo -CC $null `
    -Subject $subject -Body $message `
    -SMTPServer $smtpServer `
    -Port $port `
    -Attachments $null `
    -usessl $useSsl
#>

function sendEmail{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)][string]$userName,
  [Parameter(Mandatory)][string]$password,
  [Parameter(Mandatory)][string]$emailFrom,  
  [Parameter(Mandatory)][string[]]$emailTo,    
  [Parameter(Mandatory=$false)][string[]]$cc,
  [Parameter(Mandatory=$false)]$subject="Test Email to Validate SMTP",
  [Parameter(Mandatory=$false)]$body="This is a test email.<br><br>Please disregard",
  [Parameter(Mandatory=$false)]$smtpServer=$null,
  [Parameter(Mandatory=$false)]$port=587,
  [Parameter(Mandatory=$false)]$attachments,
  [Parameter(Mandatory=$false)]$useSsl=$true,
  [Parameter(Mandatory=$false)]$anonymous=$false
  ) 
  $commonSmtpPorts=@(25,587,465,2525)
  $useSsl=[System.Convert]::ToBoolean($useSsl)
  function Check-NetConnection($server,$port,$timeout=100,$verbose=$false) {
      $tcp = New-Object System.Net.Sockets.TcpClient;
      try {
          $connect=$tcp.BeginConnect($server,$port,$null,$null)
          $wait = $connect.AsyncWaitHandle.WaitOne($timeout,$false)
          if(!$wait){
              $tcp.Close()
              if($verbose){
                  Write-Host "Connection Timeout" -ForegroundColor Red
                  }
              Return $false
          }else{
              $error.Clear()
              $null=$tcp.EndConnect($connect) # Dispose of the connection to release memory
              if(!$?){
                  if($verbose){
                      write-host $error[0].Exception.Message -ForegroundColor Red
                      }
                  $tcp.Close()
                  return $false
                  }
              $tcp.Close()
              Return $true
          }
      } catch {
          return $false
      }
  }

  function getMxRecord($emailAddress){
      $regexDomain="\@(.*)$"
      $domain=.{[void]($emailAddress -match $regexDomain);$matches[1]}
      $mxDomain=(resolve-dnsname $domain -type mx|sort -Property Preference|select -First 1).NameExchange
      $detectedSmtp=switch -Wildcard ($mxDomain){ # need to build up this list
                              "*outlook.com" {"smtp.office365.com";break}
                              "*google.com" {"smtp.gmail.com";break}
                              "*yahoodns.net" {'smtp.mail.yahoo.com';break}
                              "*inbox.com" {'my.inbox.com;break'}
                              "*mail.com" {'smtp.mail.com';break}
                              "*icloud.com" {'smtp.mail.me.com';break}
                              "*zoho.com" {'smtp.zoho.com';break}
                              default {$mxDomain}
                          }
      if($mxDomain){
          write-host "Detected MX Record`t: $mxDomain`r`nKnown SMTP Server`t: $detectedSmtp"
          return $detectedSmtp
          }
      else{
          write-warning "MX record not available for $emailAddress"
          return $null
          }
  }
  
  if($userName -and $password){
      $encryptedPass=ConvertTo-SecureString -String $password -AsPlainText -Force
      $emailCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $userName,$encryptedPass
  }elseif($anonymous){
      $nullPassword = ConvertTo-SecureString 'null' -asplaintext -force
      $emailCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'NT AUTHORITY\ANONYMOUS LOGON', $pass
  }elseif($emailPassword){
      $emailCred=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $emailFrom,$emailPassword
      [string]$emailFrom=$emailTo|select -First 1
  }else{
      $emailCred=$null
      }

  $detectedSmtpServer=if($username -match '@' -and !$anonymous){getMxRecord $username}else{$null}
  $smtpServer=if($smtpServer){
                  if($smtpServer -eq $detectedSmtpServer){
                      Write-host "Detected SMTP server matches the provided value: $smtpServer"
                  }else{
                      write-warning "Detected SMTP server $detectedSmtpServer does not match given values. Program will use the provided value: $smtpServer"
                      }
                  $smtpServer
              }else{
                  write-host "Using detected SMTP server $detectedSmtpServer"
                  $detectedSmtpServer
                  }

    $reconstructedUsername=if($emailFrom -match '@'){$emailFrom}elseif($userName -match '\\'){
        $x=[regex]::match($userName,'(.*)\\(.*)').Captures.Groups;
        $x[2].Value+'@'+$x[1].value+'.com'
        }elseif($userName -match '@'){$userName}

  $secureSmtpParams = @{        
      From                       = $reconstructedUsername
      To                         = $emailTo
      Subject                    = $subject
      Body                       = $body
      BodyAsHtml                 = $true
      DeliveryNotificationOption = 'OnFailure','OnSuccess'
      Port                       = $port
      UseSSL                     = $useSsl
  }

  $relaySmtpParams=@{
      From                       = $reconstructedUsername
      To                         = $emailTo
      Subject                    = $subject
      Body                       = $body
      BodyAsHtml                 = $true
      DeliveryNotificationOption = 'OnFailure', 'OnSuccess'
      Port                       = 25
      UseSSL                     = $useSsl
  }

  if ($port -ne 25){
      write-host "Secure SMTP Parameters detected."
      $emailParams=$secureSmtpParams
  }else{
      write-host "Unsecured SMTP Parameters detected."
      $emailParams=$relaySmtpParams
      }
  write-host "$($emailParams|out-string)"

  try{
      $sendmailCommand="Send-MailMessage `@emailParams -SmtpServer $smtpServer $(if($cc){"-cc $cc"}) $(if($emailCred){"-Credential `$emailCred"}) $(if($attachments){"-Attachments `$attachments"}) -ErrorAction Stop"
      write-host $sendmailCommand
      Invoke-Expression $sendmailCommand
      write-host "Email has been sent to $emailTo successfully"
      return $true;
      }
  catch{
      #$errorMessage = $_.Exception.Message
      #$failedItem = $_.Exception.ItemName 
      #write-host "$errorMessage`r`n$failedItem" -ForegroundColor Yellow      
      Write-Warning "Initial attempt failed!`r`n$_`r`nNow scanning open ports..."
      $openPorts=$commonSmtpPorts|?{Check-NetConnection $smtpServer $_}                
      write-host "$smtpServer has these SMTP ports opened: $(if($openPorts){$openPorts}else{'None'})"
      if($detectedSmtpServer -ne $smtpServer){
          try{
              write-host "Program now attempts to use the detected SMTP Server: $detectedSmtpServer"
              Invoke-Expression "Send-MailMessage `@emailParams -SmtpServer $detectedSmtpServer $(if($attachments){"-Attachments $attachments"}) -ErrorAction Stop"
              write-host "Email has been sent to $emailTo successfully via alternative SMTP Server: $detectedSmtpServer" -ForegroundColor Green
              return $true;
          }catch{
              write-host $error[0].Exception.Message -ForegroundColor Yellow
              return $false
              }
      }else{
          return $false
          }        
      }
}
$emailFrom="test@kimconnect.net"
$emailPassword="PASSWORD"
$emailTo="someuser@contoso.com"
$subject="Test Email to Validate SMTP"
$body="This is a test email.<br><br>Please disregard"
$smtpServer
$port=587
$attachments='c:\gpresult.html'
$useSsl=$true

function sendEmail{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory)]$emailFrom,
    [Parameter(Mandatory)]$emailPassword,
    [Parameter(Mandatory)][string[]]$emailTo,    
    [Parameter(Mandatory=$false)][string[]]$cc,
    [Parameter(Mandatory=$false)]$subject="Test Email to Validate SMTP",
    [Parameter(Mandatory=$false)]$body="This is a test email.<br><br>Please disregard",
    [Parameter(Mandatory=$false)]$smtpServer=$null,
    [Parameter(Mandatory=$false)]$port=587,
    [Parameter(Mandatory=$false)]$attachments,
    [Parameter(Mandatory=$false)]$useSsl=$true
    ) 
    $commonSmtpPorts=@(25,587,465,2525)
    function Check-NetConnection($server,$port,$timeout=100,$verbose=$false) {
        $tcp = New-Object System.Net.Sockets.TcpClient;
        try {
            $connect=$tcp.BeginConnect($server,$port,$null,$null)
            $wait = $connect.AsyncWaitHandle.WaitOne($timeout,$false)
            if(!$wait){
                $tcp.Close()
                if($verbose){
                    Write-Host "Connection Timeout" -ForegroundColor Red
                    }
                Return $false
            }else{
                $error.Clear()
                $null=$tcp.EndConnect($connect) # Dispose of the connection to release memory
                if(!$?){
                    if($verbose){
                        write-host $error[0].Exception.Message -ForegroundColor Red
                        }
                    $tcp.Close()
                    return $false
                    }
                $tcp.Close()
                Return $true
            }
        } catch {
            return $false
        }
    }

    function getMxRecord($emailAddress){
        $regexDomain="\@(.*)$"
        $domain=.{[void]($emailAddress -match $regexDomain);$matches[1]}
        $mxDomain=(resolve-dnsname $domain -type mx|sort -Property Preference|select -First 1).NameExchange
        $detectedSmtp=switch -Wildcard ($mxDomain){ # need to build up this list
                                "*outlook.com" {"smtp.office365.com";break}
                                "*google.com" {"smtp.gmail.com";break}
                                "*yahoodns.net" {'smtp.mail.yahoo.com';break}
                                "*inbox.com" {'my.inbox.com;break'}
                                "*mail.com" {'smtp.mail.com';break}
                                "*icloud.com" {'smtp.mail.me.com';break}
                                "*zoho.com" {'smtp.zoho.com';break}
                                default {$mxDomain}
                            }
        if($mxDomain){
            write-host "Detected MX Record`t: $mxDomain`r`nKnown SMTP Server`t: $detectedSmtp"
            return $detectedSmtp
            }
        else{
            write-warning "MX record not available for $emailAddress"
            return $null
            }
    }
    
    $encryptedPass=ConvertTo-SecureString -String $emailPassword -AsPlainText -Force
    if($emailPassword){
        $emailCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $emailFrom,$encryptedPass
    }else{
        $emailCred=$false
        }

    $detectedSmtpServer=getMxRecord $emailFrom
    $smtpServer=if($smtpServer){
                    if($smtpServer -eq $detectedSmtpServer){
                        Write-host "Detected SMTP server matches the provided value: $smtpServer"
                    }else{
                        write-warning "Detected SMTP server $detectedSmtpServer does not match given values. Program will use the provided value: $smtpServer"
                        }
                    $smtpServer
                }else{
                    write-host "Using detected SMTP server $detectedSmtpServer"
                    $detectedSmtpServer
                    }
    
    $secureSmtpParams = @{        
        From                       = $emailFrom
        To                         = $emailTo
        Subject                    = $subject
        Body                       = $body
        BodyAsHtml                 = $true
        DeliveryNotificationOption = 'OnFailure','OnSuccess'
        Port                       = $port
        UseSSL                     = $useSsl
    }

    $relaySmtpParams=@{
        From                       = $emailFrom
        To                         = $emailTo
        Subject                    = $subject
        Body                       = $body
        BodyAsHtml                 = $true
        DeliveryNotificationOption = 'OnFailure', 'OnSuccess'
        Port                       = 25
        UseSSL                     = $useSsl
    }

    if ($port -ne 25){
        write-host "Secure SMTP Parameters detected."
        $emailParams=$secureSmtpParams
        }else{
            write-host "Unsecured SMTP Parameters detected."
            $emailParams=$relaySmtpParams
            }

    try{
        Invoke-Expression "Send-MailMessage `@emailParams -SmtpServer $smtpServer $(if($cc){"-cc $cc"}) $(if($emailCred){"-Credential `$emailCred"}) $(if($attachments){"-Attachments `$attachments"}) -ErrorAction Stop"
        write-host "Email has been sent to $emailTo successfully"
        return $true;
        }
    catch{
        $errorMessage = $_.Exception.Message
        $failedItem = $_.Exception.ItemName        
        $openPorts=$commonSmtpPorts|?{Check-NetConnection $smtpServer $_}                
        write-host "$smtpServer has these SMTP ports opened: $($openPorts)"
        write-host "$errorMessage`r`n$failedItem" -ForegroundColor Yellow
        if($detectedSmtpServer -ne $smtpServer){
            try{
                write-host "Program now attempts to use the detected SMTP Server: $detectedSmtpServer"
                Invoke-Expression "Send-MailMessage `@emailParams -SmtpServer $detectedSmtpServer $(if($attachments){"-Attachments $attachments"}) -ErrorAction Stop"
                write-host "Email has been sent to $emailTo successfully via alternative SMTP Server: $detectedSmtpServer" -ForegroundColor Green
                return $true;
            }catch{
                write-host $error[0].Exception.Message -ForegroundColor Yellow
                return $false
                }
        }else{
            return $false
            }        
        }

}

sendTestEmail -emailFrom $EmailUser -emailPassword $emailPassword `
            -emailTo 'admin@kimconnect.com' -cc $null `
            -subject "Test Email to Validate SMTP" -body "This is a test email.<br><br>Please disregard" `
            -smtpServer $smtpServer -port $port -attachments $attachments -useSsl $true
# Deprecated version

# User input global parameters
$emailAccount="$env:serviceEmail"
$emailPassword='$env:serverEmailPassword'
$subject="Test Email"
$body="This is a test email.<br><br>Please disregard"
$port=587

# Autogenerated variables
$encryptedPass=ConvertTo-SecureString -String $emailPassword -AsPlainText -Force
$emailCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $emailAccount,$encryptedPass

function sendEmail{
    [CmdletBinding()]
    param(
    $from,
    [Parameter(Mandatory)]$to,    
    [Parameter(Mandatory=$false)]$cc,
    $subject,
    $body,
    $credential = $emailCred,
    $port='587'
    )
 
    # Resolve the smtp server dynamically
    $regexDomain="\@(.*)$"
    $domain=.{[void]($from -match $regexDomain);$matches[1]}
    $mxDomain=.{$result=(resolve-dnsname $domain -type mx).NameExchange
                if ($result.gettype() -eq [String]){return $result}else{return $result[0]}
                }
    $smtpServer= switch -Wildcard ($mxDomain){ # need to build up this list
                        "*outlook.com" {"smtp.office365.com"}
                        "*google.com" {"smtp.gmail.com"}
                        "*yahoodns.net" {'smtp.mail.yahoo.com'}
                        "*inbox.com" {'my.inbox.com'}
                        "*mail.com" {'smtp.mail.com'}
                        "*icloud.com" {'smtp.mail.me.com'}
                        "*zoho.com" {'smtp.zoho.com'}
                        default {"smtp.$domain"}
                    }
 
    # Populate the cmdlet parameters
    $emailParams = @{        
        From                       = $from
        To                         = $to
        Subject                    = $subject
        Body                       = $body
        BodyAsHtml                 = $true
        DeliveryNotificationOption = 'OnFailure', 'OnSuccess'
        SmtpServer                 = $smtpServer
        Port                       = $port
        UseSSL                     = $true
        Credential                 = $emailCred
    }
 
    # Invoke the PowerShell sendmail cmdlet
    try{
        invoke-expression "Send-MailMessage `@emailParams $(if($cc){-Cc $cc}) -ErrorAction Stop"
        write-host "Email has been sent to $to successfully"
        return $true;
        }
        catch{
            $errorMessage = $_.Exception.Message
            $failedItem = $_.Exception.ItemName
            write-host "Error: email has NOT been sent. Here are the errors:`r`n$errorMessage`r`n$failedItem"
            return $false
            }
    <# Note: this error would occur with Google mail if Block Unsecured Apps has not been disabled: https://myaccount.google.com/u/0/security?hl=en
    Send-MailMessage : The SMTP server requires a secure connection or the client was not authenticated. The server
    response was: 5.7.57 SMTP; Client was not authenticated to send anonymous mail during MAIL FROM
    [BYAPR07CA0055.namprd07.prod.outlook.com]
    At line:1 char:1
    + Send-MailMessage @mailParams
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (System.Net.Mail.SmtpClient:SmtpClient) [Send-MailMessage], SmtpExcept
       ion
        + FullyQualifiedErrorId : SmtpException,Microsoft.PowerShell.Commands.SendMailMessage
    #>
}
 
sendEmail -from $emailAccount -to "admin@kimconnect.com" -subject $subject -body $body -credential $emailCred -port $port

Simple Mail Relay

# Send-Email-Via-Relay.ps1
# PowerShell 2.0 Compatible
function sendEmailViaRelay{
# Define log location
$hostname=$ENV:computername
$dateStamp = Get-Date -Format "yyyy-MM-dd-hhmmss"
$scriptName=$MyInvocation.MyCommand.Path
$scriptPath=Split-Path -Path $scriptName
$logPath="$scriptPath`\filecopy_logs\$hostname"
$logFile="$logPath`\$dateStamp.txt"

# Define sendmail variables
$from = "tula@kimconnect.com"
$to = "bondo@kimconnect.com"
$cc = "wala@kimconnect.net"
$subject = "$hostname is ready at time stamp: $dateStamp."
$body = "Please check the log at this location: $logFile"
$smtpServer = "relay.kimconnect.com"

# This is the Windows' version of sendmail command
Send-MailMessage -From $from -to $to -Cc $cc -Subject $subject -Body $body -SmtpServer $smtpServer -DeliveryNotificationOption OnSuccess
}

sendEmailViaRelay;
# Change these values to reflect your liking
$from = "admin@kimconnect.com"
$to = "chuck.norris@kimconnect.com"
$cc = "bruce.lee@kimconnect.com"
$username="kim"
$attachment = "C:\users\$username\Desktop\project1.xlsx"
$subject = "Opening Our First Kung Fu Studio"
$body = "Blah blah... Enough talk. Let's fight!"
$smtpServer = "kimconnect.mail.protection.outlook.com"
$smtpPort = "587"

# This is the Windows' version of sendmail command
Send-MailMessage -From $from -to $to -Cc $cc -Subject $subject -Body $body -SmtpServer $smtpServer -port $smtpPort -UseSsl -Credential (Get-Credential) -Attachments $attachment –DeliveryNotificationOption OnSuccess
Troubleshooting notes:
Office365 Considerations:
  1. Authentication: username and password must be valid and correct
  2. Mailbox: a licensed Office 365 mailbox is required
  3. Transport Layer Security (TLS): TLS 1.1 is required
  4. Port: Port 587 is standard. StartTLS port 465 is not allowed. Relaying port 25, if used, must be unblocked as outbound from source networks
  5. Microsoft requires that each smart-host configuration or send connector setting must use only the assigned host record (e.g. domain-com12345.mail.protection.outlook.com). Verify this record at: https://admin.microsoft.com/AdminPortal/Home#/Domains
  6. To avoid emails being marked as spam when sending from source servers, edit the SPF TXT record to match this value: v=spf1 ip4:[SOURCE_IPs_HERE] include:spf.protection.outlook.com ~all
  7. Third-party filtering service should be routing to the correct smart-host record as shown in (a). For instance, barracudanetworks should route to the correct smtp server as ‘domain-com12345.mail.protection.outlook.com’. Here’s an example of allowance for ClickDimensions and Autotask at URL https://outlook.office365.com/ecp/Antispam/EditConnectionFiltering
  8. In email relaying cases, connectors should be setup to specify the source(s). This can be done by authenticating to Office.com using a Global Admin account > Navigate to Exchange Admin Center (Admin > Exchange) > Mail flow > Connectors > Check the list of connectors set up for your organization. If there are no connectors listed from your organization’s email server to Office 365, create one by clicking on the ‘+’ sign > Set From = Public IP address of the Email Relaying Computer (or source subnet), Set To = Office 365 > Next > Next > Save

Leave a Reply

Your email address will not be published. Required fields are marked *