65.9K
CodeProject is changing. Read more.
Home

Command Line Emailer

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (32 votes)

Sep 25, 2003

4 min read

viewsIcon

156093

downloadIcon

1472

A console application for sending email from the command line

Introduction

This article describes a .NET console application to send email using a command line. It shows methods for parsing command line arguments, loading and parsing a simple parameter file, and using the System.Web.Mail namespace to construct and send emails.

Background

I continue to use batch files for a number of administrative tasks. Recently when working with a batch file of NTBACKUP commands, I was interested in having the log file text sent through email and developed this application for that purpose.

Using the code

The application receives all arguments as either command line switches, or as supplied through a parameter file. The following shows the usage of the console command and its available switches.

Usage:
  CommandLineEmailer [/p:parameterFile] [/smtp:smtpserver] [/f:from]
                     [/to:recipients] [/cc:courtesyCopies]
                     [/bcc:blindCourtesyCopies] [/s:subject] [/b:body]
                     [/a:attachment] [/i:insertFile] [/?] 

Switches:
  /p:     file containing message parameters such as the Smtp server, 
          recipients lists, and the message subject.

  /smtp:  smtp server

  /f:     message sender (from)

  /to:    semicolon-delimited list of message recipients

  /cc:    semicolon-delimited list of message courtesy-copy recipients 

  /bcc:   semicolon-delimited list of message blind courtesy-copy recipients

  /s:     message subject

  /b:     message body

  /a:     file attachment
  
  /i:     insert the supplied text file in the body

  /?      display help text

The parameter file that may be supplied with the /p: switch is an ASCII file with lines in the form:

   key = value

The following describes the keys that may be used in a parameter file:

      smtpserver = the SMTP mail server used to send the message
      from       = the message sender
      to         = a semicolon-delimited list of message recipients
      cc         = courtesy copies (semicolon-delimited list)
      bcc        = blind courtesy copies (semicolon-delimited list)
      subject    = the message subject
      body       = A line of the message body; multiple body= lines
                   may appear in the parameter file to include multiple
                   lines in the message body
      attachment = A full or partial path to a file that will be attached
                   to the email message; multiple attachment= lines
                   may appear in the parameter file to attach multiple files
      insertfile = A full or partial path to a text file whose contents will
                   be inserted into the body of the email message; multiple
                   insertfile= lines may appear in the parameter file
This shows the text of an example parameter file:
      smtpserver = myserver.com
      from       = me@myserver.com
      to         = recipient1@myserver.com; recipient2@myserver.com
      cc         = cc1@myserver.com
      subject    = Test Message
      body       = This should appear as the first line of the body.
      body       = This should appear as the second line of the body.
      body       = This should appear as the third line of the body.
      body       = 
      body       = The text of file "myprocess.log" follows:
      body       = --------------------------------------
      insertfile = myprocess.log
      body       = --------------------------------------
      body       = 
      body       = Finally, some attachments:
      attachment = c:\files\FirstAttachment.txt
      attachment = c:\files\SecondAttachment.txt
If the above parameter file is saved as myParms.txt, then the CommandLineEmailer program may be executed with the following at the command line or in a batch file:
    CommandLineEmailer /p:myParms.txt
As an alternative, all required values may be supplied as command line switches. Only one line of body text may be added this way.
    CommandLineEmailer /smtp:myserver.com /from:me@myserver.com 
                       /to:recipient@myserver.com
                       /subject:Test Message /b:This is the message body.
A combination of switches and a parameter file may be also used. For example, with this parameter file defining the "smtpserver" and "from" values:
      smtpserver = myserver.com
      from       = me@myserver.com
values for "to", "subject", and "insertfile" may be supplied on the command line:
    CommandLineEmailer /p:myParms.txt /to:recipient@myserver.com 
                       /subject:Log Report /i:myprocess.log

Describing the code

Three code samples from CommandLineEmailer are described here to show approaches for command line parsing, line by line text file parsing, and email sending.

CommandLineParser Class

This class shows one approach to parsing the command line. For this application, all arguments are supplied through switches. The class constructor is defined with the command line as a string argument, which is converted to an array of strings using the .Split('/') method. Each individual string in the array is inspected to see if a colon exists, intended to separate the switch from its supplied value. If the colon exists, the switch and value are parsed out and added to a local StringDictionary object, with the switch being the key. The StringDictionary class, exposed through the System.Collections.Specialized namespace, represents a collection of string values associated with string keys. If the colon does not exist, the switch and a blank string "" as its associated value are added to the StringDictionary.

    public CommandLineParser(string sCommandLine)
    {
        // given the command line setup an ArrayList of switch items
        // switches in the form /s:xxx are supported

        _switches = new StringDictionary();
            
        // create an array of items split by the / symbol
        string[] arr = sCommandLine.Split('/');

        // loop through each string in the array
        foreach (string s in arr) 
        {
            string st = s.Trim();
            if (st != "") 
            {
                // if the switch has a value it will follow a 
                // colon after the switch
                int i = st.IndexOf(":");
                if (i > 0) 
                {
                    // we have a switch and a value; 
                    // add to the StringDictionary
                    // as a convenience, strip out any double quotes
                    string k = st.Substring(0, i);
                    string v = st.Substring(i + 1);
                    _switches.Add(k.Trim(), v.Trim().Replace("\"", ""));
                }
                else
                {
                    // we have a switch without a value; 
                    // add to the StringDictionary with a blank string
                    _switches.Add(st, "");
                }
            }
        }

    }

The class also defines HasSwitch and SwitchValue methods, and an AllSwitches property:

    private StringDictionary _switches;

    public bool HasSwitch(string sSwitch) 
    {
        // return TRUE if the given switch (without the slash) exists
        return (_switches.ContainsKey(sSwitch));            
    }

    public string SwitchValue(string sSwitch) 
    {
        // return the value associated with the given switch, 
        // or null if the switch doesn't exist
        if (HasSwitch(sSwitch) )
        {
            return _switches[sSwitch];
        } 
        else 
        {
            return null;
        }
    }

    public StringDictionary AllSwitches
    {
        get {return _switches;}
    }
    

The class is then used in the application's main() function like this:

    static void Main(string[] args)
    {

        // parse the command line
        CommandLineParser cp = new CommandLineParser(
            System.Environment.CommandLine);
            .
            .
            .
        // if a parameter file has been supplied, process it first
        if (cp.HasSwitch("p")) ProcessParameterFile(cp.SwitchValue("p"));

        // then process any other switches that may 
        // override values or add to values
        if (cp.HasSwitch("smtp")) _smtpserver = cp.SwitchValue("smtp");
        if (cp.HasSwitch("f"))    _from       = cp.SwitchValue("f");
            .
            .
            .
    }

This approach to command line parsing is limited in that arguments are expected to be passed as /switches. However, if that is suitable for the application at hand, the class may be used as is.

Line by line text file parsing

Given the parameter files used by this application are typically short, the approach used here is to read the entire file into a string and again use the string's .Split() method to create a string array of lines that are individually parsed. Reading the file into a string is accomplished with the following:

    public static string LoadTextFile(string f) {
        FileStream fs = File.OpenRead(f);
        StreamReader sr = new StreamReader(fs);
        String contents = sr.ReadToEnd();
        sr.Close();
        fs.Close();
        return contents;
    }
Then the array of individual lines is created with the following function:
    public static string[] GetLinesFromString(string contents)
    {
        return contents.Split('\n');
    }

Each line is then parsed with the following code to set message variables.

    public static void ParseLine(string line)
    {
        string[] a = line.Split('=');
        string key = (a.Length == 0 ? "" : a[0].Trim());
        string val = (a.Length == 1 ? "" : a[1].Trim());

        switch (key.ToLower()) 
        {
            case "smtpserver"   : _smtpserver = val;     break;
            case "from"         : _from = val;           break;
            case "to"           : _to = val;             break;
            case "cc"           : _cc = val;             break;
            case "bcc"          : _bcc = val;            break;
            case "subject"      : _subject = val;        break;
            case "body"         : _textblocks.Add(val);  break;
            case "attachment"   : _attachments.Add(val); break;
            case "insertfile"   : 
                try 
                {
                    _textblocks.Add(LoadTextFile(val)); 
                } 
                catch (Exception ex) 
                {
                    _textblocks.Add("<Error inserting text file " + val + 
                          ": " + ex.Message + ">");
                }
                break;
                    
            // everything else is ignored;
        }

    }

Most of the message variables are simple strings. Body text (supplied through either 'body=' or 'insertfile=' lines) is handled as an ArrayList of string objects and enumerated prior to sending to construct the message body. This allows multiple body lines to be identified and/or multiple text files to be inserted if desired. The try/catch block in the case "insertfile:" case attempts to load the text file and insert its contents; if there is an error, the message body is still constructed with the error text inserted instead.

Sending the Email

The System.Web.Mail namespace exposes three classes used here for constructing and sending the email. The SmtpMail class is used both to set the mail server (through the SmtpMail.Server property), and to send the message itself (through the SmtpMail.Send method). The MailMessage class represents the message, identifying the sender, recipients, subject, and body text. The MailAttachment class represents a file attachment in a MailMessage.Attachments collection.

The beginning of the SendEmailMessage function in the CommandLineEmailer application sets up the basic message properties with application variables:

    public static void SendEmailMessage() 
    {
        SmtpMail.SmtpServer = _smtpserver;
        MailMessage mm = new MailMessage();
        mm.From = _from;
        mm.To = _to;
        mm.Cc = _cc;
        mm.Bcc = _bcc;
        mm.Subject = _subject;

The body of the email message is constructed using a StringBuilder object by iterating through each string in the _textblocks ArrayList:
        // add all textblocks to the body
        StringBuilder sb = new StringBuilder();
        foreach(string s in _textblocks) 
        {
            sb.Append(s + System.Environment.NewLine);
        }
        mm.Body = sb.ToString() + System.Environment.NewLine;
Attachments are then added to the message's Attachments collection. A try/catch block is used to insert error text in the message body if an attachment cannot be added.
        // attempt to add any attachments; on an error here, 
        // add error text to the body
        foreach(string s in _attachments) 
        {
            try 
            {
                string fullPath = System.IO.Path.GetFullPath(s);
                mm.Attachments.Add(new MailAttachment(fullPath));
            }
            catch (Exception ex) 
            {
                mm.Body += System.Environment.NewLine;
                mm.Body += "<Error attaching file " + s + ": " + 
                     ex.Message + ">";
                mm.Body += System.Environment.NewLine;
            }
        }

Finally, the message is sent through the SmtpServer object:

        SmtpMail.Send(mm);
    }

Summary

This article demonstrated how to use the CommandLineEmailer console application for sending mail from a command line or batch file. It also described a method for parsing command line arguments and encapsulating such functionality in a reusable class. An approach for parsing a simple parameter file line by line was explored, as were the SmtpServer, MailMessage, and MailAttachment objects from the System.Web.Mail namespace.