using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Management.Automation;
using System.Net.Mail;
using System.Net;
using System.IO;
using System.Web;
using Microsoft.Win32;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Security;
namespace CustomCmdlets
{
[Cmdlet( VerbsCommunications.Send, "Email", SupportsShouldProcess = true, DefaultParameterSetName = "SMTP" )]
public class SendEmail : PSCmdlet
{
private const int DefaultSmtpPort = 25;
private const string GMAIL_ADDRESS = "https://mail.google.com/mail/";
private const string GMAIL_LOGIN = "https://www.google.com/accounts/ServiceLoginAuth";
private CookieCollection cookieCollection;
private string multiPartBoundary;
private SmtpClient smtpClient;
private List<FileInfo> inputAttachments;
#region Parameters
private SwitchParameter gmail;
[Parameter( ParameterSetName = "Gmail" )]
public SwitchParameter Gmail
{
get
{
return gmail;
}
set
{
gmail = value;
}
}
private string gmailUsername;
[Parameter( ParameterSetName = "Gmail", Mandatory = true )]
[ValidateNotNullOrEmpty]
public string GmailUsername
{
get
{
return gmailUsername;
}
set
{
gmailUsername = value;
}
}
private SecureString gmailPassword;
[Parameter( ParameterSetName = "Gmail", Mandatory = true )]
[ValidateNotNullOrEmpty]
public SecureString GmailPassword
{
get
{
return gmailPassword;
}
set
{
gmailPassword = value;
}
}
private string smtpHost;
[Parameter( ParameterSetName = "SMTP" )]
[ValidateNotNullOrEmpty]
public string SmtpHost
{
get
{
return smtpHost;
}
set
{
smtpHost = value;
}
}
[Parameter( ParameterSetName = "SMTP" )]
public int PortNumber
{
get
{
return portNumber ?? DefaultSmtpPort;
}
set
{
portNumber = value;
}
}
private int? portNumber;
private string from;
[Parameter( ParameterSetName = "SMTP" )]
[ValidateNotNullOrEmpty]
public string From
{
get
{
return from;
}
set
{
from = value;
}
}
private string[] toArray;
[Parameter( Mandatory = true )]
[ValidateNotNullOrEmpty]
public string[] To
{
get
{
return toArray;
}
set
{
toArray = value;
}
}
private string[] ccArray = new string[ 0 ];
[Parameter]
[ValidateNotNull]
public string[] Cc
{
get
{
return ccArray;
}
set
{
ccArray = value;
}
}
private string[] bccArray = new string[ 0 ];
[Parameter]
[ValidateNotNull]
public string[] Bcc
{
get
{
return bccArray;
}
set
{
bccArray = value;
}
}
private string subject;
[Parameter( Mandatory = true )]
public string Subject
{
get
{
return subject;
}
set
{
subject = value;
}
}
private string body;
[Parameter( Mandatory = true )]
public string Body
{
get
{
return body;
}
set
{
body = value;
}
}
private SwitchParameter bodyIsHtml;
[Parameter]
public SwitchParameter BodyIsHtml
{
get
{
return bodyIsHtml;
}
set
{
bodyIsHtml = value;
}
}
private FileInfo[] attachments = new FileInfo[ 0 ];
[Parameter( ValueFromPipeline = true )]
[ValidateNotNull]
public FileInfo[] Attachment
{
get
{
return attachments;
}
set
{
attachments = value;
}
}
private int timeout = 60 * 1000;
[Parameter]
[ValidateRange( 0, Int32.MaxValue )]
public int Timeout
{
get
{
return timeout;
}
set
{
timeout = value;
}
}
#endregion
protected override void BeginProcessing()
{
inputAttachments = new List<FileInfo>();
if ( gmail.IsPresent )
{
if ( gmailUsername == null )
{
gmailUsername = (string)GetVariableValue( "GmailUsername", null );
}
if ( gmailPassword == null )
{
gmailPassword = (SecureString)GetVariableValue( "GmailPassword", null );
}
if ( string.IsNullOrEmpty( gmailUsername ) || gmailPassword == null )
{
this.WriteError( new ErrorRecord(
new Exception( "You must provide a username and password." ),
"Send-Email", ErrorCategory.PermissionDenied, this ) );
}
}
else
{
smtpHost = (string)GetVariableValue( "EmailSmtpHost" );
portNumber = (int?)( GetVariableValue( "EmailSmtpPort" ) ?? DefaultSmtpPort );
from = (string)GetVariableValue( "EmailFrom" );
smtpClient = new SmtpClient( smtpHost );
smtpClient.Port = portNumber.Value;
smtpClient.Timeout = timeout;
smtpClient.Credentials = CredentialCache.DefaultNetworkCredentials;
}
}
protected override void ProcessRecord()
{
if ( attachments != null )
{
foreach ( FileInfo attachment in attachments )
{
if ( ShouldProcess( attachment.FullName ) )
{
inputAttachments.Add( attachment );
}
}
}
}
protected override void EndProcessing()
{
try
{
if ( gmail.IsPresent )
{
SendGmailMessage();
}
else
{
SendSmtpMessage();
}
}
finally
{
inputAttachments = null;
}
}
private void SendGmailMessage()
{
IntPtr bstr = Marshal.SecureStringToBSTR( gmailPassword );
string plainGmailPassword = Marshal.PtrToStringAuto( bstr );
Marshal.ZeroFreeBSTR( bstr );
string loginPostDataString = "&continue=" + HttpUtility.UrlEncode( GMAIL_ADDRESS ) +
"&service=mail" +
"&hl=en" +
"&Email=" + HttpUtility.UrlEncode( gmailUsername ) +
"&Passwd=" + HttpUtility.UrlEncode( plainGmailPassword );
this.cookieCollection = new CookieCollection();
this.WriteVerbose( "Sending Gmail login request..." );
string loginResponse = MakeHttpWebRequest( GMAIL_LOGIN, true, false, Encoding.UTF8.GetBytes( loginPostDataString ) );
// if we don't have this cookie, something went wrong
if ( this.cookieCollection[ "GMAIL_AT" ] == null )
{
this.WriteError( new ErrorRecord(
new Exception( "Could not log in to Gmail. Please check your username and password." ),
"Send-Email", ErrorCategory.PermissionDenied, this ) );
}
else
{
this.multiPartBoundary = DateTime.Now.Ticks.ToString( "x" );
string messageUrl = string.Format( "{0}{1}", GMAIL_ADDRESS, "?ui=1" );
MemoryStream postDataStream = new MemoryStream();
BinaryWriter postDataWriter = new BinaryWriter( postDataStream );
Dictionary<string, string> variableHash = new Dictionary<string, string>();
variableHash.Add( "view", "sm" );
variableHash.Add( "at", this.cookieCollection[ "GMAIL_AT" ].Value );
variableHash.Add( "to", string.Join( ", ", toArray ) );
variableHash.Add( "cc", string.Join( ", ", ccArray ) );
variableHash.Add( "bcc", string.Join( ", ", bccArray ) );
variableHash.Add( "subject", subject );
variableHash.Add( "ishtml", bodyIsHtml.IsPresent ? "1" : "0" );
variableHash.Add( "msgbody", body );
foreach ( string key in variableHash.Keys )
{
postDataWriter.Write( Encoding.UTF8.GetBytes( string.Format(
"--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n",
this.multiPartBoundary, key, variableHash[ key ] ) ) );
}
// add the attachments
if ( inputAttachments.Count > 0 )
{
messageUrl = string.Format( "{0}&newattach={1}", messageUrl, inputAttachments.Count );
byte[] attachmentData = new byte[ 0 ];
for ( int i = 0; i < inputAttachments.Count; i++ )
{
FileInfo attachment = inputAttachments[ i ];
FileStream fileStream = null;
try
{
fileStream = File.OpenRead( attachment.FullName );
byte[] fileData = new byte[ fileStream.Length ];
fileStream.Read( fileData, 0, fileData.Length );
postDataWriter.Write( Encoding.UTF8.GetBytes( string.Format(
"--{0}\r\nContent-Disposition: form-data; name=\"file{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n",
this.multiPartBoundary, i, attachment.Name, GetMimeType( attachment ) ) ) );
postDataWriter.Write( fileData );
postDataWriter.Write( Encoding.UTF8.GetBytes( "\r\n" ) );
}
catch ( Exception ex )
{
this.WriteError( new ErrorRecord( ex, "Send-Email", ErrorCategory.InvalidData, this ) );
}
finally
{
if ( fileStream != null )
{
fileStream.Close();
}
}
}
}
postDataWriter.Write( Encoding.UTF8.GetBytes( string.Format( "--{0}--\r\n", this.multiPartBoundary ) ) );
postDataWriter.Flush();
byte[] messagePostData = postDataStream.ToArray();
postDataStream.Close();
postDataWriter.Close();
this.WriteVerbose( "Sending Gmail message request..." );
string messageResponse = MakeHttpWebRequest( messageUrl, true, true, messagePostData );
// parse the message response
messageResponse = Regex.Replace( messageResponse, "\n", "" );
Match responseMatch = Regex.Match( messageResponse, @"D\(\[(?<Data>""sr"",[^)]+)\]\);" );
if ( responseMatch.Success )
{
string[] responseParts = Regex.Split( responseMatch.Groups[ "Data" ].Value, "," );
bool sent = ( responseParts[ 2 ] == "1" );
string message = Regex.Unescape( responseParts[ 3 ].Substring( 1, responseParts[ 3 ].Length - 2 ) );
if ( sent )
{
this.WriteObject( message );
}
else
{
this.WriteError( new ErrorRecord( new Exception( message ), "Send-Email", ErrorCategory.NotSpecified, this ) );
}
}
}
}
private string GetMimeType( FileInfo file )
{
string mimeType = "application/octet-stream";
try
{
mimeType = Registry.ClassesRoot.OpenSubKey( file.Extension ).GetValue( "Content Type" ).ToString();
}
catch
{
// default to "application/octet-stream"
}
return mimeType;
}
private string MakeHttpWebRequest( string requestUrl, bool post, bool multiPart, byte[] postData )
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create( new Uri( requestUrl ) );
// we need to do this ourselves
webRequest.AllowAutoRedirect = false;
webRequest.KeepAlive = false;
webRequest.Credentials = CredentialCache.DefaultNetworkCredentials;
webRequest.Timeout = timeout;
webRequest.CookieContainer = new CookieContainer();
webRequest.CookieContainer.Add( cookieCollection );
if ( post )
{
webRequest.Method = "POST";
if ( multiPart )
{
webRequest.ContentType = string.Format( "multipart/form-data; boundary={0}", multiPartBoundary );
}
else
{
webRequest.ContentType = "application/x-www-form-urlencoded";
}
webRequest.ContentLength = postData.Length;
Stream requestStream = null;
try
{
requestStream = webRequest.GetRequestStream();
requestStream.Write( postData, 0, postData.Length );
}
catch ( Exception ex )
{
this.WriteError( new ErrorRecord( ex, "Send-Email", ErrorCategory.InvalidData, this ) );
}
finally
{
if ( requestStream != null )
{
requestStream.Close();
}
}
}
else
{
webRequest.Method = "GET";
webRequest.ContentType = "text/html";
}
HttpWebResponse webResponse = null;
string responseString = "";
try
{
webResponse = (HttpWebResponse)webRequest.GetResponse();
cookieCollection.Add( webResponse.Cookies );
StreamReader streamReader = new StreamReader( webResponse.GetResponseStream() );
responseString = streamReader.ReadToEnd();
streamReader.Close();
// redirect if we have a Location header or a <meta> refresh tag
if ( webResponse.Headers[ "Location" ] != null )
{
responseString = MakeHttpWebRequest( webResponse.Headers[ "Location" ], false, false, null );
}
else
{
Match urlMatch;
if ( ( urlMatch = Regex.Match( responseString, @"<meta\s*(http-equiv\s*=\s*""refresh"")?\s*content\s*=\s*""\s*0;\s*url\s*=\s*'(?<URL>[^']+)'""" ) ).Success )
{
responseString = MakeHttpWebRequest( urlMatch.Groups[ "URL" ].Value.Replace( "&", "&" ), false, false, null );
}
}
}
catch ( Exception ex )
{
this.WriteError( new ErrorRecord( ex, "Send-Email", ErrorCategory.InvalidResult, this ) );
}
finally
{
if ( webResponse != null )
{
webResponse.Close();
}
}
return responseString;
}
private void SendSmtpMessage()
{
try
{
MailMessage message = new MailMessage();
message.From = new MailAddress( from );
foreach ( string recipient in toArray )
{
message.To.Add( recipient );
}
foreach ( string recipient in ccArray )
{
message.CC.Add( recipient );
}
foreach ( string recipient in bccArray )
{
message.Bcc.Add( recipient );
}
if ( !string.IsNullOrEmpty( subject ) )
{
message.Subject = subject;
}
if ( !string.IsNullOrEmpty( body ) )
{
message.Body = body;
}
message.IsBodyHtml = bodyIsHtml.IsPresent;
foreach ( FileInfo attachment in inputAttachments )
{
message.Attachments.Add( new Attachment( attachment.FullName ) );
}
if ( ShouldProcess( message.Subject ) )
{
smtpClient.Send( message );
}
}
catch ( Exception ex )
{
this.WriteError( new ErrorRecord( ex, "Send-Email", ErrorCategory.InvalidOperation, this ) );
}
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<helpItems xmlns="http://msh" schema="maml">
<command:command xmlns:maml="http://schemas.microsoft.com/maml/2004/10" xmlns:command="http://schemas.microsoft.com/maml/dev/command/2004/10" xmlns:dev="http://schemas.microsoft.com/maml/dev/2004/10">
<command:details>
<command:name>Send-Email</command:name>
<maml:description>
<maml:para>The Send-Email Cmdlet uses the System.Net.Mail.MailMessage class or a Gmail account to send an email message.</maml:para>
</maml:description>
<command:verb>Send</command:verb>
<command:noun>Email</command:noun>
</command:details>
<maml:description>
<maml:para>
The Send-Email Cmdlet uses the System.Net.Mail.MailMessage class or a Gmail account to send an email message. Preferences for SMTP host name, port number, and from email address can be specified in your PowerShell profile by creating the following variables in the global scope:
$EmailSmtpHost = "smtphost"
$EmailSmtpPort = 527
$EmailFrom = "name@email.com"
When using the -Gmail switch parameter, it may be helpful to create these variables:
$GmailUsername = "username"
$Gmailpassword = <SecureString password>
</maml:para>
</maml:description>
<command:syntax>
<command:syntaxItem>
<maml:name>Send-Email</maml:name>
<command:parameter required="false">
<maml:name>gmail</maml:name>
</command:parameter>
<command:parameter required="true">
<maml:name>gmailUsername</maml:name>
<command:parameterValue required="true">string</command:parameterValue>
</command:parameter>
<command:parameter required="true">
<maml:name>gmailPassword</maml:name>
<command:parameterValue required="true">SecureString</command:parameterValue>
</command:parameter>
<command:parameter required="false">
<maml:name>smtpHost</maml:name>
<command:parameterValue required="true">string</command:parameterValue>
</command:parameter>
<command:parameter required="false">
<maml:name>portNumber</maml:name>
<command:parameterValue required="true">int</command:parameterValue>
</command:parameter>
<command:parameter required="false">
<maml:name>from</maml:name>
<command:parameterValue required="true">string</command:parameterValue>
</command:parameter>
<command:parameter required="true">
<maml:name>to</maml:name>
<command:parameterValue required="true">string []</command:parameterValue>
</command:parameter>
<command:parameter required="false">
<maml:name>cc</maml:name>
<command:parameterValue required="true">string []</command:parameterValue>
</command:parameter>
<command:parameter required="false">
<maml:name>bcc</maml:name>
<command:parameterValue required="true">string []</command:parameterValue>
</command:parameter>
<command:parameter required="false">
<maml:name>subject</maml:name>
<command:parameterValue required="true">string</command:parameterValue>
</command:parameter>
<command:parameter required="false">
<maml:name>body</maml:name>
<command:parameterValue required="true">string</command:parameterValue>
</command:parameter>
<command:parameter required="false">
<maml:name>bodyIsHtml</maml:name>
</command:parameter>
<command:parameter required="false">
<maml:name>attachment</maml:name>
<command:parameterValue required="true">FileInfo []</command:parameterValue>
</command:parameter>
<command:parameter required="false">
<maml:name>timeout</maml:name>
<command:parameterValue required="true">int</command:parameterValue>
</command:parameter>
</command:syntaxItem>
</command:syntax>
<command:parameters>
<command:parameter required="false" position="named" globbing="false" pipelineInput="false">
<maml:name>Gmail</maml:name>
<maml:description>
<maml:para>Indicates whether a Gmail account will be used to send the email message.</maml:para>
</maml:description>
<command:parameterValue required="true">SwitchParameter</command:parameterValue>
</command:parameter>
<command:parameter required="true" position="named" globbing="false" pipelineInput="false">
<maml:name>GmailUsername</maml:name>
<maml:description>
<maml:para>The Gmail username.</maml:para>
</maml:description>
<command:parameterValue required="true">string</command:parameterValue>
</command:parameter>
<command:parameter required="true" position="named" globbing="false" pipelineInput="false">
<maml:name>GmailPassword</maml:name>
<maml:description>
<maml:para>The Gmail password.</maml:para>
</maml:description>
<command:parameterValue required="true">SecureString</command:parameterValue>
</command:parameter>
<command:parameter required="false" position="named" globbing="false" pipelineInput="false">
<maml:name>SmtpHost</maml:name>
<maml:description>
<maml:para>The SMTP host to use to send the email.</maml:para>
</maml:description>
<command:parameterValue required="true">string</command:parameterValue>
</command:parameter>
<command:parameter required="false" position="named" globbing="false" pipelineInput="false">
<maml:name>PortNumber</maml:name>
<maml:description>
<maml:para>The port to use on the SMTP host.</maml:para>
</maml:description>
<command:parameterValue required="true">int</command:parameterValue>
<dev:defaultValue>25</dev:defaultValue>
</command:parameter>
<command:parameter required="false" position="named" globbing="false" pipelineInput="false">
<maml:name>From</maml:name>
<maml:description>
<maml:para>The sender of the email.</maml:para>
</maml:description>
<command:parameterValue required="true">string</command:parameterValue>
</command:parameter>
<command:parameter required="true" position="named" globbing="false" pipelineInput="false">
<maml:name>To</maml:name>
<maml:description>
<maml:para>The recipient(s) of the email.</maml:para>
</maml:description>
<command:parameterValue required="true">string []</command:parameterValue>
</command:parameter>
<command:parameter required="false" position="named" globbing="false" pipelineInput="false">
<maml:name>Cc</maml:name>
<maml:description>
<maml:para>The carbon copy recipient(s) of the email.</maml:para>
</maml:description>
<command:parameterValue required="true">string []</command:parameterValue>
</command:parameter>
<command:parameter required="false" position="named" globbing="false" pipelineInput="false">
<maml:name>Bcc</maml:name>
<maml:description>
<maml:para>The blind carbon copy recipient(s) of the email.</maml:para>
</maml:description>
<command:parameterValue required="true">string []</command:parameterValue>
</command:parameter>
<command:parameter required="true" position="named" globbing="false" pipelineInput="false">
<maml:name>Subject</maml:name>
<maml:description>
<maml:para>The subject of the email.</maml:para>
</maml:description>
<command:parameterValue required="true">string</command:parameterValue>
</command:parameter>
<command:parameter required="true" position="named" globbing="false" pipelineInput="false">
<maml:name>Body</maml:name>
<maml:description>
<maml:para>The body of the email.</maml:para>
</maml:description>
<command:parameterValue required="true">string</command:parameterValue>
</command:parameter>
<command:parameter required="false" position="named" globbing="false" pipelineInput="false">
<maml:name>BodyIsHtml</maml:name>
<maml:description>
<maml:para>Indicates if the body of the email is HTML.</maml:para>
</maml:description>
<command:parameterValue required="true">SwitchParameter</command:parameterValue>
</command:parameter>
<command:parameter required="false" position="named" globbing="false" pipelineInput="true">
<maml:name>Attachment</maml:name>
<maml:description>
<maml:para>The attachment(s) for the email.</maml:para>
</maml:description>
<command:parameterValue required="true">FileInfo []</command:parameterValue>
</command:parameter>
<command:parameter required="false" position="named" globbing="false" pipelineInput="false">
<maml:name>Timeout</maml:name>
<maml:description>
<maml:para>The timeout value for the SMTP server.</maml:para>
</maml:description>
<command:parameterValue required="true">int</command:parameterValue>
<dev:defaultValue>60 seconds</dev:defaultValue>
</command:parameter>
</command:parameters>
<command:inputTypes>
<command:inputType>
<dev:type>
<maml:name>FileInfo []</maml:name>
<maml:uri/>
<maml:description>
<maml:para>
Files to be added as attachments to the email.
</maml:para>
</maml:description>
</dev:type>
<maml:description></maml:description>
</command:inputType>
</command:inputTypes>
</command:command>
</helpItems>