Saturday, October 3, 2015

How to create and share a school calendar using Google

Summary: A walk-through explaining how to create and share a public school calendar.
I have three kids enrolled in two schools that operate under different calendars. To keep track of their schedules, I set up the school calendars on my phone (and PC), so that I get event notifications and reminders. It works well, but comes at a cost: in the beginning of each school year I spend 1-2 hours manually entering the information about the school events (minimum days, holidays, days off, back to school nights, etc.) into my personal calendar. And so do other parents at my kids' schools, as well as millions of parents nationwide. Sooner or later the schools will learn how to make the process more efficient, but in the meantime, you -- a school administrator, a teacher, or a volunteer -- can help us. It won't be hard.

In this article, I'll explain how to make it easy for parents to see a school calendar on a mobile device (iPhone, Android device, Windows phone or tablet) and keep this calendar up-to-date. You will no longer need to print calendars on paper (yay, trees!) or have parents enter them manually. A public calendar allows you to add new events, update existing events, delete cancelled events, set reminders, provide helpful event details (such as instructions or locations of off-site events) and subscribers will see them instantly. It lets you share the same calendar online (at your school's website). And it does not limit you to a school schedule. You can follow the same approach to set up a calendar for any organization or activity, such as a sports team or a volunteering project.

Before we get down to the nitty-gritty, let me first clarify the requirements and limitations.

REQUIREMENTS: A user must have a Google account.
While you may keep a personal calendar locked to your your mobile phone or computer, inside of iCloud, or in Outlook, for a public calendar you better use Google Calendar. It supports all major platforms (iPhone, Android, Windows), has wide adoption, and offers most features.

If you (a school calendar administrator or creator) do not have a Google account, you can easily create one (it's free). A Google account gives you access to the Calendar features including ability to create public calendars. You may prefer to create a special Google account that would "own" the school calendar, so you can transfer it to someone else in case you no longer want to manage it (Google makes it really easy to switch between multiple accounts).

To get real time updates, Google calendar subscribers (parents) will also need Google accounts. As a subscriber, you do not need to actively use a personal Google calendar (if you prefer something else), but you will need it to sync the school calendar with your mobile device.

WARNING: If you have a fundamental objection to using Google, there may be a similar approach to sharing a school calendar using a different provider (say, Apple, Yahoo!, or Microsoft), but it'd be more complex (if possible at all), so let's assume that at this point both calendar administrators and subscribers have Google accounts.

Calendar creation process
To set up a school calendar, follow these steps:
  1. Create a public calendar
    First, you create a public Google calendar for your school online. Here is a short video explaining how to do this (if you prefer a written tutorial, read Create & manage a public Google calendar):

    Although you can change it later, try to give the calendar a meaningful name (or title). Keep in mind that a long name may not show completely on mobile devices, so place the most significant parts of the name in the beginning. Here are some examples of calendar names that I used, so you may want to follow a similar format:

    • CCHAT, Sacramento (School Calendar) (see preview)
    • CMP (School Calendar) (see preview)

    If you want to allow other administrators to maintain the calendar (create and make changes to the events), set appropriate sharing permissions (read the Control what others can see help section).

  2. Add events to the calendar
    To make sure you add an event to the wright calendar, when you work with your school calendar, temporarily turn off all other calendars. Here are some tips.

    When naming events, follow the same naming convention. To make it easier for subscribers identify which calendar an event belongs to, include a short name of the school in the name of event. Here are some examples of event names that I used for a California Montessori Project's school calendar (you can browse the calendar to see more examples and details):

    • Minimum Day (CMP)
    • School Break (CMP)
    • School Holiday (CMP)
    • School Closed (CMP)
    • Last Day of School/Minimum Day (CMP)

    As you may have noticed, I prefer to keep titles short and simple, but I also use the description field for additional information.

    Include details that parents may find useful in the event description. You can use description for special instructions or any type of information you would normally provide in a letter or email invitation.

    For off-site events, specify the address in the location field. The address will help parents find location on map and use navigation apps.

    For some events, such as minimum days, it may make sense to set up reminders. Keep in mind that that parents may need time to travel, so give them enough time (I'd say, for a full-day event, a 12-hour advanced reminder would be fine, while for all other events, an hour would be enough). Use pop-ups for reminders.

    For repeating or multi-day events (such as regular after school activities or school breaks), set up occurrence rules (do not duplicate events).

    Instead of creating each event from scratch, use the Duplicate Event feature, which allows you to copy an existing event, and then update all relevant information. For example, when defining school holidays, set up an even for the Labor Day (I think it's the first holiday in a typical school year), make sure you use proper naming convention and define event details. Then duplicate it to make an entry for Veteran's Day (don't forget to adjust event information), and continue doing the same for other school holidays.

  3. Advertise the calendar
    Once you get the calendar up and running, you send its address (the URL of the calendar's ICS file) to the parents. At the time of writing, you could locate the calendar address by following this steps:

    • Open your Google calendar page.
    • Click the settings (gear) button in the top right corner, and select the Settings option from the pop-up menu.
    • Under the Calendar Settings heading, click the Calendars tab link.
    • Click the name of your public school calendar displayed in the My Calendars section.
    • Scroll down to the Calendar Address section and click the iCal button (make sure you use the public iCal address and not the private one).

    A window will pop up showing the calendar address that looks like this:

    Make sure that the address ends with the .ics extension. This is the address of the calendar that you will need to share with parents.

  4. Maintain the calendar
    You can use the same calendar year over year and make changes to it at any time. All changes to the calendar will be automatically delivered to the subscribers. As a bonus, you can also add a Google calendar to your website.
Calendar subscription process
To see calendar events on your mobile device, such as phone or tablet, do the following:
  1. Subscribe to calendar
    Parents subscribe to the calendar using the provided address of the iCal file. The steps are outlined in the Add someone else's Google calendar article (see the Add using a link section).

  2. Sync calendar with phone
    If you do not see the school calendar events reflected on your phone (or other mobile device's) calendar, you may need to make a couple of adjustments. First, follow the instructions outlined in the Sync Calendar with a phone or tablet article. Depending on the device configuration, it may not be enough, so follow your device maker's recommendations. For example, here are instructions for Windows and iPhone users. For an Android device, you many need to clear calendar data and resync the calendar (if you are not sure how to do this, search for: <your device name> +clear +calendar +data and <your device name> +sync +calendar).

Easy? I think so.

I'm almost done, but before we part, one final though. In fact, this is the first question you need to address:
How many calendars do you want to keep?
Do you want to maintain a single district calendar or multiple calendars? Do you need a separate calendars for primary, middle, and high schools? Do you want the calendar to include the information relevant to teachers and staff (such as training)? Depending on the size of your district, differences in school schedules, and other factors, you may choose one approach over another. As a parent, I'd like a single school calendar with information relevant to students only and a class calendar reflecting class-specific events, such as field trips, project deadlines, and due dates. I do not care when and where teachers and staff attend training, but I do care when the school is closed, but others may have other preferences.

I hope this information was helpful. If you have questions or want to leave feedback, please leave a comment.

See also:
Embedding Google Calendar in eChalk
Can I get my Google Calendar shown in my iCloud Calendar on my PC?

Thursday, March 5, 2015

Build API documentation with Sandcastle Help File Builder

Summary: How to build awesome API documentation using Visual Studio, XML code comments, Microsoft Markup Assistance Language (MAML), and Sandcastle Help File Builder (SHFB).
A couple of weeks ago, I gave this presentation to the Sacramento .NET User Group:

The presentation explains how to build first-class API documentation using Visual Studio, Sandcastle, and other tools and technologies. The goal of the presentation is to show how to build documentation with less effort and more fun.

The PowerPoint file (with links and notes) and the demo project can be found here: The demo project page outlines the dependencies and requirements for building the solution and using the assemblies.

If you run into any issues or have questions, please post a comment below or contact me directly.

Friday, February 13, 2015

Technobrief #15

Summary: Recent (and not so recent) findings of software, articles, and more.
CSS Design Fonts Graphics JavaScript
  • Gridforms
    A tiny Javascript/CSS framework that helps you make forms on grids with ease.
  • SlabText
    A JQuery plugin for producing big, bold, and responsive headlines.
Programming Software Tools

Thursday, March 27, 2014

Build responsive websites with Visual Studio 2013 and Bootstrap 3.0

Summary: Materials for the Sacramento .NET User Group presentation.
Thanks a lot to everyone who attended my presentation yesterday. You have been an awesome audience. Also, many thanks to Josh Gurin (TEKsystems) for the pizzas and wings and Jason Singh (Microsoft) for the excellent meeting space. I was a bit concerned about parking, but there were lots of free after 6 PM parking spaces around the building.

Here are the materials I promised: In case you don't want to bother downloading the files, here is the presentation:

The PowerPoint slides provide the same information as the web application built by the demo project. If you only want to check references and resources mentioned at the session, just use the slides. If you intend to see how the concepts were applied in the actual application, get the project. When you run the demo app, you will see the same content available in the presentation, although in a slightly different format. Links should work from both the presentation and the demo app.

If you run into any issues or have questions, please post a comment below or contact me directly.

See also:
The ultimate guide to Bootstrap by By Cameron Chapman
25 Free & Premium Bootstrap Admin Templates

Friday, May 10, 2013

WiX woes: What is your installer doing?

Summary: How to detect different modes of installation.
When building an application installer, it's often necessary to distinguish between different modes of installation, i.e. initial installation, repair, upgrade, uninstall, etc. And as with everything important in MSI, detecting the mode of installation is a PITA (and by PITA, I do not mean flat bread of Mediterranean origin). To help you a little bit, here is a table adopted from a StackOverflow topic (and comments), that shows the values of various Windows Installer properties can help you determine the installation mode:

Install Uninstall Repair Modify Upgrade

You can use logical operators NOT, AND, OR to build complex conditions.

Here is how you can detect some common conditions:

First-time installation
  • NOT Installed
Any installation
  • NOT Installed AND NOT PATCH
Installation and repairs
First-time installation and repairs
  • NOT Installed OR MaintenanceMode="Modify"
Upgrades only (during uninstall phase)
  • Installed AND NOT REMOVE
Full uninstall (except when triggered by a major upgrade)
Any uninstall
If you notice errors or want to include some other conditions, please post a comment.

See also:
MSI Property Patterns: Upgrading, FirstInstall and Maintenance
Upgrading, FreshInstall, Maintenance and other MSI convenience properties
MSI Writing Guidelines: Installation Scenarios
How to execute custom action only in install (not uninstall)

Thursday, April 25, 2013

My Walmart account was hacked

Summary: Lessons from my Walmart account hacking incident.
Out of the blue, I get an email from Walmart:
Dear Alek Davis,

Personal information associated with your account - name, email address and/or password - has been successfully updated as requested. If the account change included an update to the email, for your added security this account update confirmation is sent to both the new and old email addresses. All future emails will be sent to the new address only.

If the account information update is correct, no further action is needed.

If you did not make these changes to your account, please call us immediately at 1-800-966-6546.

If you have any questions, please reply to this email and let us know how we can help.

We appreciate the opportunity to assist you and look forward to your next visit.


Your Customer Service Team
I try to log on to my Walmart account and fail to authenticate. I attempt to use the I Forgot My Password feature, but get a message stating that my email address is not registered with Walmart. It's obvious: someone hacked my Walmart account!

I call the above mentioned 1-800 number, but the customer support department is closed (it's around 10 PM PST, but apparently, the world's largest retailer cannot afford 24x7 customer support). There is no option to report the problem online. What's a girl to do?

The best thing I can do is send an email reply describing the problem. I get a canned response indicating that I will get a human response within 24 hours. Okay, what's next?

Results from a quick Google search suggest that a common pattern of Walmart hacking involves using saved credit card data to purchase digital goods. So, I log on to my credit card's account (for the card that I normally use at and see two unauthorized transactions: one in the amount of $60 (turns out to be 2 Straight Talk 1000-Minute, 1000-Text, 30MB Web Access Service Cards), and another in the amount of $50 (2 SKYPE $25 Prepaid eGift Cards). I call the credit card company to report fraud. I also checked other credit cards that could've been on file with Walmart, but do not notice anything suspicious.

I try logging on to again, and notice a strange address popping up in the email field of the Sign In form for a second just before it is overwritten by my original (and no longer good) address filled in by LastPass. Apparently, I have a low-security personalization cookie, that is not good for anything important (like checking or changing account info, or submitting orders), but it could give me some info about the hacker. I disable LastPass and reload the form. Get the email field populated with this address: Hello, hacker. How're you doing?

Silly idea: what if I try to log in with my original password? The hacker can't be that careless, but... One... two... three... I'm in! Dear,, thank you for failing Hacking 101. I change my email address back, change the password, and remove all credit card info from the account. I see the two orders in the processing state, and successfully cancel one of them. I use a form to send an order cancellation request for the second purchase, but apparently the Skype eGift cards have been already sent. Well, it's now between Walmart and my credit card company to dispute the charge.

What else can I do? I go to the Yahoo! Security Center and try to find an option to report fraudulent activity coming from a Yahoo! email, but Yahoo! does not provide any way to do this (via a form, email, or phone).

The next morning, I call Walmart (thank God Walmart can afford customer support during normal business hours) to report the incident to a human and have a short conversation with a nice woman (btw, have the companies started bringing customer support back from the foreign lands? talking to a motivated native speaker is so refreshing!). Now, it's time to get back to life, but first, lessons learned:
  1. Never save credit card information when shopping online! Yeah, it's convenient, but may eventually cause more hassles.
  2. Read #1.
And a couple of comments:
  • Walmart: No 24x7 customer support? Seriously? Even for security issues? Come on, you can do better!
  • Walmart: Good call on sending notification to old customer's email on personal profile changes. Have I not seen this message, it would have taken me much longer to realize that my account was hacked.
  • Walmart: Shouldn't user activity that starts with personal profile (and email) changes and is followed by an immediate purchase of digital goods raise a flag for suspicious activity? I know that you rush to get a payment, but you see: you lost $60 (which could've easily been $110), and I'm sure you need that money to hire more support people (at the very least, for security related issues).
  • Yahoo!: Would it be too much to ask for some way of reporting fraudulent activity originating from a Yahoo! email account? Just asking.
Have a nice day, everyone. Be safe!

Wednesday, March 27, 2013

Send HTML email from VBScript using CDO

Summary: VBScript to send HTML (and plain text) email.
It's 2013 and apparently there are no tools that would allow you to easily send HTML-formatted email.

Don't get me wrong: of course, Outlook, Thunderbird, Gmail, or whatever client you use, allows (and sometimes forces) you to send email in HTML format, but what if you want to test a format of a message that your application (code) and not you (person) sends? What do you use? Sure, both Outlook and Thunderbird allow you to insert an HTML file as the body of the text, but as soon as you do, they alter your HTML source in a way you you would not imagine, so that the delivered message will show little resemblance to the original.

After wasting several hours with existing email clients and trying available scripting samples (e.g. the ones written by Paul Sadowski and Rob Vanderwoude), I decided to do it the right way and wrote a little utility that would take your HTML file and send it without making any changes to the source code. Lo and behold, here is a VBScript file that you can use for sending email messages:
IMPORTANT: This script requires the helper Common.vbs file to be located in the same folder (or in the PATH).
SendMail.wsf script takes email body from command line or retrieves it from a file. It supports both HTML and plain text formats. You can send your message via a remote mail server or using the local SMTP service. You can also include attachments with the message.

Here is the usage info (you can get this output by running the script with the /? or /h command-line switch):
cscript //nologo SendMail.wsf /h


  Sends an email.


  cscript SendMail.wsf [/option[:parameter[;...]]] [...]


    Email From address.

    Email To address. Use comma to separate multiple addresses.

    [Optional] Email CC address. Use commas to separate multiple addresses.

    [Optional] Email BCC address. Use commas to separate multiple addresses.

    [Optional] Email Subject line. 
    [Default: TEST]

    Plain text, HTML text, or path to file containing email message.

    [Optional] SMTP server.
    [Default: localhost]

    [Optional] SMTP server port.
    [Default: 25]

    [Optional] Indicates whether email message format is HTML.
    [Values: yes|no|y|n|true|false|t|f]
    [Default: no]

    [Optional] Indicates whether the [/body] parameter points to a file.
    [Values: yes|no|y|n|true|false|t|f]
    [Default: no]

    [Optional] Specifies character set of the email text (or HTML text).
    [Default: utf-8]

    [Optional] Indicates paths to file attachments.
    Multiple files must be separated by semicolons.

This is how you would invoke the script:

Example 1: Send HTML-formatted email with email body defined in a file and with two JPEG file attachments using local SMTP service

cscript SendMail.wsf / / /cc:"," /subject:"Hey!" /body:Test.html /html:y /file:y /add:"a.jpg;b.jpg"
Example 2: Send plan text email using a remote SMTP server

cscript SendMail.wsf / / /cc:"," /subject:"Hey!" /body:"How are you?" /html:n /file:n /smtp:""

TIP: If you reference files when invoking the script (such as attachments or file containing message body), make sure that you use ether absolute path or path relative to the current directory; otherwise you may get the "File not found" error.
And here is the source code, in case you need to take a peek at the logic:
  --  Sends email message with optional attachments.
<job ID="SendMail">

<script Language="VBScript" Src="Common.vbs" />
<script Type="text/vbscript">
Option Explicit

Const SWITCH_FROM  = "from"
Const SWITCH_TO   = "to"
Const SWITCH_CC   = "cc"
Const SWITCH_BCC  = "bcc"
Const SWITCH_SUBJECT = "subject"
Const SWITCH_BODY  = "body"
Const SWITCH_SERVER  = "smtp"
Const SWITCH_PORT  = "port"
Const SWITCH_HTML  = "html"
Const SWITCH_FILE  = "file"
Const SWITCH_ADD  = "add"
Const SWITCH_CHARSET = "charset"



' Method
' Main
' Description
' Performs the main operation.
Function Main()

    Dim strFrom, strTo, strCc, strBcc, strSubject, strBody
    Dim strServer, nPort
    Dim bIsHtml, bIsFile
    Dim strCharset
    Dim arAttachments
    Dim i
    Main = -1
    ' Make sure the script is executed via cscript (not wscript).

    ' Show help if needed.
    If (IsHelpMode(False)) Then
        Main = 0
        Exit Function
    End If
    ' Initialize run-time parameters.
    If Not Initialize _
    ( _
        strFrom, strTo, strCc, strBcc, strSubject, strBody, _
        strServer, nPort, bIsHtml, bIsFile, strCharset, _
        arAttachments _
    ) Then
        Exit Function
    End If
    ' Make sure that all files (if any) exist.
    If (bIsFile) Then
        If Not FileExists(GetAbsolutePath(strBody)) Then
            WScript.Echo "File '" & strBody & "' does not exist."
            Exit Function
        End If
    End If
    If GetArraySize(arAttachments) > 0 Then
        For i = LBound(arAttachments) To UBound(arAttachments)
            If Not FileExists(GetAbsolutePath(arAttachments(i))) Then
                WScript.Echo "File '" & arAttachments(i) & "' does not exist."
                Exit Function
            End If
    End If
    ' Get contents of the message body from a file.
    If (bIsFile = True) Then
        strBody = ReadTextFromFileEx(GetAbsolutePath(strBody), strCharset)
    End If
    SendEmail _
        strFrom, strTo, strCc, strBcc, _
        strSubject, strBody, strServer, nPort, _
        bIsHtml, strCharset, arAttachments
    Main = 0
    WScript.Echo "Done."
End Function

' Method
' SendEmail
' Description
' Sends email using CDO.
' Parameters
' Self-explinatory
Sub SendEmail _
( _
    ByVal strFrom, _
    ByVal strTo, _
    ByVal strCc, _
    ByVal strBcc, _
    ByVal strSubject, _
    ByVal strBody, _
    ByVal strServer, _
    ByVal nPort, _
    ByVal bIsHtml, _
    ByVal strCharSet, _
    ByRef arAttachments _
   ' Standard housekeeping
    Dim i, oEmail

    ' Create an e-mail message object
    Set oEmail = CreateObject("CDO.Message")

    ' Fill in the field values
    With oEmail
        .From = strFrom
        .To = strTo
        If Not IsEmptyString(strCc) Then
            .Cc = strCc
        End If
        If Not IsEmptyString(strBcc) Then
            .Bcc = strBcc
        End If
        .Subject = strSubject
        If bIsHtml = True Then
            .HTMLBody = strBody
            .HTMLBodyPart.charset = strCharset
            .TextBody = strBody
            .TextBodyPart.charset = strCharset
        End If
        If GetArraySize(arAttachments) > 0 Then
            For i = LBound(arAttachments) To UBound(arAttachments)
                .AddAttachment Replace(GetAbsolutePath(arAttachments(i)), "\", "\\" ), "", ""
        End If

        If Not IsEmptyString(strServer) Then
            With .Configuration.Fields
                .Item( "")  = 2 ' cdoSendUsingPort
                .Item( "") = strServer
                .Item( "") = nPort
            End With
        End If
        ' Send the message
    End With 

    ' Release the e-mail message object
    Set oEmail = Nothing
End Sub

' Method
' Initialize
' Description
' Processes command-line switches and initializes run-time
' parameters.
' Returns
' True on success; otherwise, False.
' Parameters
' Self-explinatory
Function Initialize _
( _
    ByRef strFrom, _
    ByRef strTo, _
    ByRef strCc, _
    ByRef strBcc, _
    ByRef strSubject, _
    ByRef strBody, _
    ByRef strServer, _
    ByRef nPort, _
    ByRef bIsHtml, _
    ByRef bIsFile, _
    ByRef strCharset, _
    ByRef arAttachments _
    Initialize = False
    Dim strValue, strErrMsg
    strErrMsg = "Missing required parameter: " 
    strFrom = GetParamValue(SWITCH_FROM)
    If (IsEmptyString(strFrom)) Then
        WScript.Echo strErrMsg & SWITCH_FROM
        Exit Function
    End If
    strTo = GetParamValue(SWITCH_TO)
    If (IsEmptyString(strTo)) Then
        WScript.Echo strErrMsg & SWITCH_TO
        Exit Function
    End If
    strCc  = GetParamValue(SWITCH_CC)
    strBcc  = GetParamValue(SWITCH_BCC)
    strSubject = GetParamValue(SWITCH_SUBJECT)
    If (IsEmptyString(strSubject)) Then
        strSubject = "TEST"
    End If
    strBody = GetParamValue(SWITCH_BODY)
    If (IsEmptyString(strBody)) Then
        WScript.Echo strErrMsg & SWITCH_BODY
        Exit Function
    End If

    strServer = GetParamValue(SWITCH_SERVER)

    strValue = GetParamValue(SWITCH_PORT)
    If (IsEmptyString(strValue)) Then
        nPort = 25
        nPort = CInt(strValue)
    End If

    strValue = GetParamValue(SWITCH_HTML)
    If (IsEmptyString(strValue)) Then
        bIsHtml = False
        strValue = UCase(strValue)
        If (strValue = "YES") Or (strValue = "Y") Or _
            (strValue = "TRUE") Or (strValue = "T") Then
            bIsHtml = True
            bIsHtml = False
        End If
    End If

    strValue = GetParamValue(SWITCH_FILE)
    If (IsEmptyString(strValue)) Then
        bIsFile = False
        strValue = UCase(strValue)
        If (strValue = "YES") Or (strValue = "Y") Or _
            (strValue = "TRUE") Or (strValue = "T") Then
            bIsFile = True
            bIsFile = False
        End If
    End If

    strValue = GetParamValue(SWITCH_CHARSET)
    If (IsEmptyString(strValue)) Then
        strCharset = "utf-8"
        strCharset = strValue
    End If

    arAttachments = GetParamValues(SWITCH_ADD, SWITCH_ADD_DELIMETER)
    Initialize = True
End Function

' Function
' ShowHelp
' Description
' Displays help and usage info.
Sub ShowHelp()
    Dim strMsg          ' help message

    ' We know that user wants to see help, so generate help message.
    strMsg =    _
    "DESCRIPTION:" & vbCrLf &_
    vbCrLf &_
    "  Sends an email." & vbCrLf &_
    vbCrLf &_
    "USAGE:"  & vbCrLf &_
    vbCrLf &_
    "  cscript " & Wscript.ScriptName &_
    " [/option[:parameter[" & SWITCH_ADD_DELIMETER & "...]]] [...]" & vbCrLf &_
    vbCrLf &_
    "OPTIONS:" & vbCrLf &_
    vbCrLf &_
    "  " & SWITCH_FROM & vbCrLf &_
    "    Email From address." & vbCrLf &_
    vbCrLf &_
    "  " & SWITCH_TO & vbCrLf &_
    "    Email To address. Use comma to separate multiple addresses." & vbCrLf &_
    vbCrLf &_
    "  " & SWITCH_CC & vbCrLf &_
    "    [Optional] Email CC address. Use comma to separate multiple addresses." & vbCrLf &_
    vbCrLf &_
    "  " & SWITCH_BCC & vbCrLf &_
    "    [Optional] Email BCC address. Use comma to separate multiple addresses." & vbCrLf &_
    vbCrLf &_
    "  " & SWITCH_SUBJECT & vbCrLf &_
    "    [Optional] Email Subject line. " & vbCrLf &_
    "    [Default: TEST]" & vbCrLf &_
    vbCrLf &_
    "  " & SWITCH_BODY & vbCrLf &_
    "    Plain text, HTML text, or path to file containing email message." & vbCrLf &_
    vbCrLf &_
    "  " & SWITCH_SERVER & vbCrLf &_
    "    [Optional] SMTP server." & vbCrLf &_
    "    [Default: localhost]" & vbCrLf &_
    vbCrLf &_
    "  " & SWITCH_PORT & vbCrLf &_
    "    [Optional] SMTP server port." & vbCrLf &_
    "    [Default: 25]" & vbCrLf &_
    vbCrLf &_
    "  " & SWITCH_HTML & vbCrLf &_
    "    [Optional] Indicates whether email message format is HTML." & vbCrLf &_
    "    [Values: yes|no|y|n|true|false|t|f]" & vbCrLf &_
    "    [Default: no]" & vbCrLf &_
    vbCrLf &_
    "  " & SWITCH_FILE & vbCrLf &_
    "    [Optional] Indicates whether the [/" & SWITCH_BODY & "] parameter points to a file." & vbCrLf &_
    "    [Values: yes|no|y|n|true|false|t|f]" & vbCrLf &_
    "    [Default: no]" & vbCrLf &_
    vbCrLf &_
    "  " & SWITCH_CHARSET & vbCrLf &_
    "    [Optional] Specifies character set of the email text (or HTML text)." & vbCrLf &_
    "    [Default: utf-8]" & vbCrLf &_
    vbCrLf &_
    "  " & SWITCH_ADD & vbCrLf &_
    "    [Optional] Indicates paths to file attachments." & vbCrLf &_
    "    Multiple files must be separated by semicolons."

    Wscript.Echo strMsg
End Sub

I did use the script to complete my project, but I did not thoroughly test it, so if you run into problems, please let me know.