Skip to content

Instantly share code, notes, and snippets.

@dxdxdt
Last active February 20, 2023 11:19
Show Gist options
  • Save dxdxdt/14445420def5f804e9fa2df58feb651f to your computer and use it in GitHub Desktop.
Save dxdxdt/14445420def5f804e9fa2df58feb651f to your computer and use it in GitHub Desktop.
Sending Mail using Powershell

Sending Mail using Powershell

Turns out, Powershell can be used to send emails through harnessing the power of C#. I made this script as a POC as to show how far .Net and Powershell have come.

Should work on all platforms that support Powershell.

Usage

Copied from the script.

echo 'Hi! it going? Testing my Powershell script.' | \
  smtp_host=smtp.gmail.com \
  smtp_username=example@gmail.com \
  smtp_password='0123456789' \
  mail_from=alice@gmail.com \
  mail_to=bob@example.com \
  mail_subject='Sent using Powershell' \
   sendmail.ps1 \
     doc.pdf

Few Tips

Password

Services like Gmail will require you to get a separate password for external apps. Google calls this "App password". Refer to the links below.

Even if the normal password for the account can be used, a separate password should always be used for program access. Always check if your email provider supports this.

TLS

Most services will refuse to serve on unsecure connections. Use smtp_tls=O only as the last resort.

smtp_tls_cert is for TLS CN SASL authentication. if authenticating using this method, smtp_username and smtp_password are not required.

CC and More

Didn't think about CC and all the advanced composition. Feel free to add more feature to the script that is already monstrous!

#!/usr/bin/env pwsh
# Send an email using Powershell.
# Usage: sendmail.ps1 [attachment 1 [attachment 2 [... attachment N]]]
#
# This is a POC on how to send an email using Powershell. The script should work
# on all platforms. The information required for the script to work is all
# supplied via environment variables.
#
# Env Vars
# smtp_host
# smtp_port (best if you let the implementation decide)
# smtp_tls:
# 'F' to insist on secure connection (default)
# 'O' for opportunistic
# 'N' to disable (default if $smtp_host is "localhost")
# smtp_tls_cert
# smtp_username
# smtp_password
# mail_from (required)
# mail_to (required)
# mail_subject (required)
#
# Example
#```pwsh
# echo 'Hi! it going? Testing my Powershell script.' | \
# smtp_host=smtp.gmail.com \
# smtp_username=example@gmail.com \
# smtp_password='0123456789' \
# mail_from=alice@gmail.com \
# mail_to=bob@example.com \
# mail_subject='Sent using Powershell' \
# sendmail.ps1 \
# doc.pdf
#```
using namespace System
using namespace System.Net
using namespace System.Security.Cryptography
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PSDefaultParameterValues['*:ErrorAction'] = 'Stop'
<#
.SYNOPSIS
Get an environment variable and unset it
.PARAMETER Name
The name of the environment variable to read and unset
.PARAMETER Required
If set, throw FileNotFoundException if the environment variable requested is not
set
.NOTES
The purpose of the function is to get and scrub off the password passed as an
env var in one go. To preserve the env var, use `GetEnvironmentVariable()`
directly.
#>
function FetchEnv ([string]$Name, [bool]$Required = $false) {
$RetVal = [Environment]::GetEnvironmentVariable($Name)
[Environment]::SetEnvironmentVariable($Name, '')
if ( $RetVal ) {
return $RetVal
}
else {
if ($Required) {
throw New-Object IO.FileNotFoundException ("${Name}: unset env var")
}
else {
return $null
}
}
}
<#
.SYNOPSIS
Read data from STDIN until EOF and return data as decoded string
#>
function ExhaustStdin () {
$stream = New-Object IO.StreamReader ( [Console]::OpenStandardInput() )
return $stream.ReadToEnd()
}
######################################################################
# Execution starts here
######################################################################
# Compose a message
$mail = New-Object Mail.MailMessage (
(FetchEnv "mail_from" $true),
(FetchEnv "mail_to" $true),
(FetchEnv "mail_subject" $true),
(ExhaustStdin)) # This is the part where the mail body is read from STDIN
# Add attachments
foreach ($file in $args) {
[string]$file = $file
$a = New-Object Mail.Attachment (
$file,
# Treat all attachments as binary
[System.Net.Mime.MediaTypeNames+Application]::Octet)
# Timestamp support
$a.ContentDisposition.CreationDate = [IO.File]::GetCreationTime($file)
$a.ContentDisposition.ModificationDate = [IO.File]::GetLastWriteTime($file)
$a.ContentDisposition.ReadDate = [IO.File]::GetLastAccessTime($file)
$mail.Attachments.Add($a)
}
# Set up credentials
$client_cred = New-Object NetworkCredential (
(FetchEnv "smtp_username"),
(FetchEnv "smtp_password"))
# Set up client TLS cert
$tls_cert = FetchEnv("smtp_tls_cert")
if ($null -ne $tls_cert) {
$cert_chain = New-Object X509Certificates.X509Certificate ( $tls_cert )
}
else {
$cert_chain = $null
}
# Read target SMTP host
$smtp_host = FetchEnv "smtp_host"
if (!$smtp_host) {
$smtp_host = "localhost"
}
# Set up SMTP client object
$smtp = New-Object Mail.SmtpClient ($smtp_host)
if ($cert_chain) {
$smtp.ClientCertificates.Add($cert_chain)
}
$smtp.Credentials = $client_cred
$smtp_port = FetchEnv "smtp_port"
if ($smtp_port) {
$smtp.Port = [int]$smtp_port
}
$tlsmode = FetchEnv "smtp_tls"
if (!$tlsmode) {
# Determine the "tlsmode" to default to
if ($smtp_host -eq "localhost") {
# No need to waste computing power on TLS.
# Unless you're paranoid and don't trust the hosts file.
$tlsmode = "N"
}
else {
# Transmitting plain text data on the internet nowadays is no-brainer.
# Most email services will refuse anyway.
$tlsmode = "F"
}
}
# Set `$smtp.EnableSsl` based on `$tlsmode`
if ($tlsmode -eq "F" -or $tlsmode -eq "O") {
$smtp.EnableSsl = $true
}
elseif ($tlsmode -eq "N") {
$smtp.EnableSsl = $false
}
try {
$smtp.Send($mail)
}
catch {
if ($tlsmode -eq "O") {
# Opportunistic tlsmode. Try again with TLS disabled.
# Please think twice and fix the problem before resorting to this bit.
$smtp.EnableSsl = $false
$smtp.Send($mail)
}
else {
# Let the script die
throw $_
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment