Performing an SMTP test using PowerShell
Originally published July 3, 2007
When debugging mail transport issues (that is, what it takes to get a piece of e-mail from a source server to a destination server), one of the most common questions is: what happens when you do it manually?
Truthfully, doing it manually isn't difficult. But, like any machine-to-machine interface, the server you are testing expects you to be exact in the information you send - it won't be forgiving. And, if it isn't something you do often, you may forget the syntax that you should be using. Finally, the very high volume SMTP servers (such as GMail and AOL) are extraordinarily particular about spacing and capitalization.
So...we write a program to do this. Since PowerShell is my current fav, that's what I've used.
Given a particular SMTPserver, this script first ensures that the server is ping-able. Once that is verified, then a TCP port is opened to that server (by default, this is port 25, but you can change that too). Then, a small e-mail is delivered to the server, speaking the very precise SMTP dialect required by a strict interpretation of the relevant RFCs (2821 and 2822). You may completely control the source e-mail address, the destination e-mail address, the message subject and the message body.
So, while this script purports to only be a testing script, it is almost suitable for use as a full SMTP mailer, lacking only checking for return codes. That enhancement is left as an exercise to the reader. :-) (Although there are already easier ways to do this in PowerShell, some of us like the roll-your-own concept at times...)
Generally, two parameters are sufficient for your testing purposes: SMTPserver and destAddress. So, a typical call would be:
./test-smtp.ps1 -smtpserver mail.example.com -destaddress test@example.com
Although you can certainly change the script defaults to meet your own needs.
Without further ado, here is the script:
# test-smtp.ps1
# test a remote smtp server
#
param (
[string]$SMTPServer = "192.168.1.1",
[string]$destAddress = "postmaster@example.com",
[string]$strBody = "Subject: test`r`n`r`nSmall contents of some email.`r`n",
[string]$serverName = "test.example.com",
[string]$SMTPPort = "25",
[string]$srcAddress = "test@example.com",
[Switch]$help = $null
)
# stop executing the script on the first error
trap { break; }
#echo parameters to output
function e
{
$str = ""
foreach ($arg in $args)
{
$str += $arg
}
Write-Host $str
}
# network write
function n-write([System.Net.Sockets.NetworkStream]$n, [String]$s)
{
e ">>> " $s
$s += "`r`n"
$arr = $s.ToCharArray()
$n.Write($arr, 0, $arr.Length)
}
# network read, place input into $script:input
function n-read([System.IO.StreamReader]$n)
{
$script:input = $n.ReadLine()
e "<<< " $script:input
}
function test-ping (
[string]$SMTPserver
)
{
# if $SMTPserver doesn't resolve, we'd bomb without the 'trap' with a
# System.Net.NetworkInformation.PingException or System.Net.Sockets.SocketException
trap { return $false; }
$ping = New-Object System.Net.NetworkInformation.Ping
if ($ping)
{
$rslt = $ping.Send($SMTPserver)
if ($rslt -and ($rslt.Status.ToString() –eq “Success”))
{
$ping = $null
return $true
}
$ping = $null
}
return $false
}
###
### Main
###
if ($help.isPresent)
{
"
. $pwd\test-smtp.ps1
Which has the following parameters and defaults:
SMTPServer = 192.168.1.1
destAddress = postmaster@example.com
strBody = Subject: test\r\n\r\nSmall contents of some email.\r\n
serverName = test.example.com
SMTPPort = 25
srcAddress = test@example.com
help = not set
"
return
}
# see if we can get to the server first...
if (!(test-ping $SMTPserver))
{
e "Error: cannot access " $SMTPserver
return
}
$tcpServer = New-Object System.Net.Sockets.TcpClient($SMTPServer, $SMTPPort)
$netStream = $tcpServer.GetStream()
$streamRead = New-Object System.IO.StreamReader($tcpServer.GetStream())
e "SMTP connection initialized to $SMTPServer on TCP port $SMTPPort. Beginning SMTP conversation."
# get the startup message from the SMTP server
n-read $streamRead
# say hello to the remote server
n-write $netStream ("HELO " + $serverName)
n-read $streamRead
# tell the remote server who is sending e-mail
n-write $netStream ("MAIL FROM: <" + $srcAddress + ">")
n-read $streamRead
# tell the remote who the mail is destined to
n-write $netStream ("RCPT TO: <" + $destAddress + ">")
n-read $streamRead
# send the data
n-write $netStream "DATA"
n-read $streamRead
n-write $netStream $strBody
n-write $netStream "."
n-read $streamRead
# terminate the connection
n-write $netStream "QUIT"
n-read $streamRead
e "Mail was sent successfully."
# be nice and clean up
$streamRead.Close()
$netStream.Close()
$tcpServer.Close()
You can take this sample and modify it to do almost any SMTP task you can think of.