Aspfaq 021215
Aspfaq 021215
Aspfaq 021215
Database
#2201 Where can I get basic info about using stored procedures? (updated 04/15/2002)
#2188 How do I deal with MEMO, TEXT, HYPERLINK, and CURRENCY columns? (updated 08/23/2002)
#2168 How do I connect to an Access database / text file on another web server? (updated 08/23/2002)
#2035 How do I deal with an apostrophe (') in a SQL statement? (updated 03/06/2002)
#2080 What are reserved Access, ODBC and T-SQL keywords? (updated 07/08/2002)
#2062 How do I solve 'Operation must use an updateable query' errors? (updated 08/11/2002)
#2214 What do I need to know about the differences between Access and SQL Server? (updated 07/01/2002)
#2178 Schema: How do I get the tables out of a database? (updated 09/19/2001)
#2177 Schema: How do I get the column names (and their datatype) out of a table? (updated 06/21/2002)
#2154 Why do I get General error Unable to open registry key 'DriverId'? (updated 10/02/2002)
#2057 How do I know which version of MDAC I'm running? (updated 10/14/2000)
#2010 How do I hide system tables in SQL Server's Enterprise Manager? (updated 07/02/2002)
#2186 How do I connect to SQL Server on a port other than 1433? (updated 10/04/2001)
#2121 Why does ASP give me ActiveX errors when connecting to a database? (updated 08/23/2002)
#2194 How do I prevent my ASP pages from waiting for backend activity? (updated 08/14/2002)
#2150 How do I prevent NULLs in my database from mucking up my HTML? (updated 07/17/2002)
#2086 Why do I get 'Syntax Error in INSERT INTO Statement' with Access? (updated 07/08/2002)
#2142 Why does Access give me 'unspecified error' messages? (updated 05/23/2001)
#2159 How do I access MIN, MAX, SUM, COUNT values from SQL statements? (updated 07/08/2002)
#2082 How do I get rid of Named Pipes / DBNMPNTW errors? (updated 09/12/2002)
#2190 Can I compact / repair / synchronize an Access database from ASP code? (updated 09/05/2002)
#2118 How do I sort out an AND/OR query with an unknown number of parameters? (updated 04/01/2001)
#2083 How do I solve 'ADO Could Not Find The Specified Provider'? (updated 08/17/2002)
#2123 Schema: How do I get the stored procedures out of a database? (updated 04/15/2001)
#2138 I get "Login failed for user '\'." in SQL Server, why? (updated 02/07/2002)
#2152 How can I make my SQL queries case sensitive? (updated 08/22/2002)
#2105 Schema: How do I show all the triggers in a database? (updated 06/30/2002)
#2104 Schema: How do I show all the primary keys? (updated 04/30/2002)
#2160 How do I know which version of SQL Server I'm running? (updated 10/18/2002)
#2206 Why doesn't SQL Server allow me to separate DATE and TIME? (updated 10/31/2001)
#2041 Why do I get the error 'Command text was not set for the command object'? (updated 10/28/2001)
#2148 Why do I get weird results when using both AND and OR in a query? (updated 05/30/2001)
#2231 Should I index my database table(s), and if so, how? (updated 01/07/2002)
#2288 What is this 'Multiple-step OLE DB operation generated errors' message? (updated 09/23/2002)
#2273 How do I get the number of rows in a table, or all tables? (updated 05/19/2002)
#2229 Where can I get this 'Books Online' that people keep telling me about? (updated 01/07/2002)
#2220 Why do I get SQLSetConnectAttr Failed errors? (updated 09/21/2002)
#2259 How do I solve 'Could not find installable ISAM' errors? (updated 02/22/2002)
#2061 Why do I get 'Argument data type text is invalid for argument [...]'? (updated 09/14/2002)
#2423 Where else can I learn about SQL Server? (updated 11/10/2002)
#2342 How do I get the latest version of the JET OLEDB drivers? (updated 08/11/2002)
#2244 Schema: how do I retrieve the description property of a column? (updated 07/19/2002)
#2279 How do I convert columns of values into a single list? (updated 04/16/2002)
#2306 How do I limit the number of records returned in my resultset? (updated 06/27/2002)
#2345 What are the capacity specifications for Access, SQL Server, and MSDE? (updated 08/16/2002)
#2282 How do I search for special characters (e.g. %) in SQL Server? (updated 08/28/2002)
#2239 Why does Enterprise Manager crash when I get an error in a stored procedure? (updated 02/19/2002)
#2307 Why do I get 'Operation is not allowed when the object is closed' errors? (updated 11/08/2002)
#2272 Why is Query Analyzer only returning 255 characters of my VARCHAR / TEXT column? (updated 07/22/2002)
#2319 How do I deal with multiple resultsets from a stored procedure? (updated 07/08/2002)
#2312 Why can't I use LIKE '%datepart%' queries for dates against SQL Server? (updated 07/01/2002)
#2354 What datatype should I use for my character-based database columns? (updated 10/17/2002)
#2284 Why do I get 'object could not be found' or 'invalid object name', when the object exists? (updated 04/16/2002)
#2426 Which database should I use for my ASP application? (updated 11/08/2002)
#2292 How do I solve 'Cannot open a database created with a previous version...' errors? (updated 04/30/2002)
#2394 Can I use the NZ() function without getting 80040E14 errors? (updated 08/26/2002)
#2301 Why do I get 'Not enough space on temporary disk' errors? (updated 07/10/2002)
#2293 Why do I get script errors in Enterprise Manager's 'taskpad' view? (updated 05/02/2002)
#2428 How do I get a list of tables and their record counts? (updated 12/10/2002)
Components
#2087 DLL: How do I avoid 'Permission Denied' when re-compiling? (updated 10/28/2001)
#2185 Why does Browscap give me 'unknown' or tell me IE is Netscape? (updated 12/04/2002)
#2225 Where can I get a shopping cart for my web site? (updated 04/11/2002)
#2203 How do I pass server-side values to a client-side ActiveX control? (updated 10/30/2001)
#2216 Can I code ISAPI filters / extensions with Visual Basic? (updated 07/25/2002)
#2336 Should I instantiate my COM object with CreateObject or Server.CreateObject? (updated 07/24/2002)
#2395 How do I generate RTF documents from ASP? (updated 08/27/2002)
Forms
#2189 How do I upload files from the client to the server? (updated 04/03/2002)
#2052 How do I change the target frame or window of a response.redirect? (updated 07/16/2002)
#2153 How can I mimic a client-side POST from ASP? (updated 06/08/2001)
#2106 How do I validate forms using server side script? (updated 02/25/2001)
#2019 Why does my input type=text value get truncated? (updated 09/10/2002)
#2008 How do I retrieve the name of the form that was submitted? (updated 07/09/2000)
#2020 Why does my form variable become 'value, value' instead of 'value'? (updated 07/11/2000)
#2077 What is the size limit of a posted FORM field? (updated 02/21/2002)
#2116 How do I cause/prevent ENTER being used to submit a form? (updated 03/26/2001)
#2222 What is the limit on QueryString / GET / URL parameters? (updated 11/13/2001)
#2166 When I'm uploading files, why can't I access the request.form collection? (updated 03/07/2002)
#2215 How do I perform spell checking from a web page? (updated 12/09/2002)
#2242 When I have multiple submit buttons, how do I tell which was clicked? (updated 01/25/2002)
#2264 How do I retrieve the text and the value from a <SELECT> element? (updated 04/15/2002)
#2234 How do I make form fields read-only? (updated 01/07/2002)
#2255 Why won't my <TEXTAREA> display the data I passed to it from ASP? (updated 02/20/2002)
#2235 How can I programmatically interfere with the INPUT TYPE=FILE element? (updated 01/07/2002)
#2204 How do I pass x-y coordinates to ASP, after the user clicks an image? (updated 10/30/2001)
#2353 How do I submit a form to a new window, with the control of window.open()? (updated 08/11/2002)
#2253 Why do I get 'HTTP 405 - Resource Not Allowed' errors? (updated 08/19/2002)
Filesystem
#2039 Where can I find info on working with files and FileSystemObject? (updated 08/15/2000)
#2129 How do I make the filename correct for the client, when using binaryWrite? (updated 03/10/2002)
#2072 How do I get the name of the current URL / page? (updated 08/15/2002)
#2090 Why do I get 'Permission Denied' errors with FileSystemObject? (updated 01/20/2001)
#2089 Why do I get 'Path not found' errors with FileSystemObject? (updated 06/14/2002)
#2236 Can I place a file on a user's hard drive without bothering them with a prompt? (updated 01/07/2002)
#2205 Why do I get permissions errors after upgrading to Windows XP? (updated 02/04/2002)
#2208 Can I include a file in both server-side script and client-side script? (updated 01/30/2002)
#2088 Why do I get 'Disk not ready' errors with FileSystemObject? (updated 01/20/2001)
#2277 How do I prevent people from 'leeching' my CSS or JS files? (updated 04/15/2002)
#2278 How do I prevent that ugly red x when an image is missing from my server? (updated 04/15/2002)
#2425 How do I use FileSystemObject to create a file on the client? (updated 10/17/2002)
#2316 Why is 'the operation completed successfully' an error message? (updated 08/19/2002)
#2268 Why does my CDONTS mail hang out in the queue or pickup folders? (updated 07/19/2002)
#2252 Can I get CDO messages to return a read receipt? (updated 11/18/2002)
#2254 Why do CDONTS messages end up in the badmail folder? (updated 07/19/2002)
#2308 Where can I get more information about configuring and using CDO / CDONTS? (updated 07/19/2002)
#2339 Can I use a remote SMTP server with CDONTS.NewMail? (updated 07/30/2002)
Dates
#2049 SQL Server: How do I select time only from a DATETIME field? (updated 09/17/2000)
#2024 Why does JavaScript's document.lastModified() not work in ASP files? (updated 10/28/2001)
#2218 How do I convert local time to UTC (GMT) time? (updated 11/12/2001)
#2313 How do I format a date in ways not offered by FormatDateTime? (updated 07/22/2002)
#2280 Should I use 'BETWEEN' for date queries? (updated 04/15/2002)
#2219 How do I determine the number of seconds since 1/1/1970? (updated 11/12/2001)
#2347 Why do I have problems inserting NOW() into a database? (updated 08/10/2002)
General
#2059 How do I execute a DOS command / batch file / exe from ASP? (updated 08/27/2002)
#2173 How do I read the contents of a remote web page? (updated 09/21/2002)
#2109 Why do I get a 500 Internal Server error for all ASP errors? (updated 08/12/2002)
#2081 How do I make sure my ASP question gets answered? (updated 01/13/2001)
#2012 How do I get the user's IP address or browser information? (updated 07/30/2002)
#2162 How can I give them a better 404 message? (updated 04/15/2002)
#2011 Why do I get an HTTP Header or Object Moved error when trying to redirect? (updated 09/11/2002)
#2181 How do I run ASP on other web servers besides IIS? (updated 09/12/2002)
#2045 Does order matter when using <%%>, <script runat=server>, and different languages? (updated 09/04/2000)
#2071 What's the deal with IIS 5.0 and ASP 3.0? (updated 12/13/2000)
#2002 How do I make sure people go to page x before page y? (updated 07/09/2000)
#2046 How do I get the login name / username from the person visiting my page? (updated 07/26/2002)
#2136 Why do I get script errors on one machine but not another? (updated 12/12/2002)
#2064 Where can I host ASP pages for free (or at least cheap)? (updated 11/18/2002)
#2167 How do I get IntelliSense to see ASP 3.0 methods? (updated 10/31/2001)
#2084 When I run a page in my browser, why does the ASP code not execute? (updated 09/05/2002)
#2251 How can I track when my site is added to a user's favorites? (updated 04/16/2002)
#2003 How can I stop Photoshop from opening ASP files? (updated 07/09/2000)
#2004 Why does my page render (properly) in IE and not in Netscape? (updated 07/09/2000)
#2044 Why does REMOTE_HOST return an IP address instead of a name address? (updated 07/29/2002)
#2161 How do I prompt a "Save As" dialog for an accepted mime type? (updated 07/08/2002)
#2006 Why am I having problems with Server.Execute and/or Server.Transfer? (updated 09/06/2002)
#2079 Can I run IIS on Windows Millennium or Windows XP Home? (updated 12/06/2002)
#2147 Why does IIS 5.0 stop serving ASP pages? (updated 10/28/2001)
#2027 How do I make Visual InterDev's debugging features work? (updated 01/30/2002)
#2021 How do I access my server's registry from an ASP page? (updated 10/28/2001)
#2054 How do I persist a shopping cart without session variables? (updated 10/01/2000)
#2065 How do I embed apostrophes (') and quotes (") in a string? (updated 12/10/2000)
#2070 Why can't I pass querystring information AND links to #bookmarks? (updated 12/13/2000)
#2033 How do I execute a ping command from ASP, and retrieve the results? (updated 08/27/2002)
#2060 How do I use extensions other than .ASP for ASP files? (updated 10/26/2000)
#2144 How do I refresh global.asa without restarting the application? (updated 05/27/2001)
#2095 How do I access all active sessions on the server? (updated 01/23/2001)
#2099 What is this error 'An unhandled data type was encountered'? (updated 08/11/2002)
#2085 Should I use sessionID to uniquely identify users (e.g. primary key)? (updated 01/20/2001)
#2030 Why won't QueryString values work with Server.Execute? (updated 08/06/2000)
#2034 Can I run IIS 5.0 / ASP 3.0 on Windows NT 4.0 or Windows 9x? (updated 10/30/2001)
#2227 Why does DLLHOST.EXE take all my memory and/or CPU? (updated 08/20/2002)
#2051 What is this 'Cannot detect OS type' error with NT 4.0 Option Pack? (updated 09/18/2000)
#2125 How do I solve 'The server failed to load the application' errors? (updated 04/17/2001)
#2068 How do I get the lbound/ubound of the nth element in a multi-dimensional array? (updated 12/10/2000)
#2100 How do I embed ASP delimiters (<% or %>) in a string? (updated 09/04/2002)
#2094 Can I dictate the load order of files on the client from ASP? (updated 01/23/2001)
#2018 Where can I find out about running Perl in IIS? (updated 07/11/2000)
#2265 How do I warn people when their session is about to expire? (updated 03/10/2002)
#2140 How do I determine which version of IIS / ASP I'm running? (updated 05/20/2001)
#2200 How do I turn a KB Article #, like Q191987, into a usable URL? (updated 12/02/2002)
#2232 How do I make the search engines index my ASP pages with QueryStrings? (updated 07/09/2002)
#2107 How do I host multiple web sites on one IIS box? (updated 02/26/2001)
#2281 How do I set session variables from client-side script? (updated 06/14/2002)
#2133 How do I know which version of VBScript my server is running? (updated 05/07/2001)
#2226 What is Event ID 36, and how can I get IIS running again? (updated 09/15/2002)
#2269 Should I use the .inc extension for my include files? (updated 03/13/2002)
#2156 How do I detect the browser's encryption level / cipher strength? (updated 06/10/2001)
#2048 Why do I get 'HTTP/1.0 Invalid Application Name' errors? (updated 09/14/2000)
#2127 What do I do when IIS 5.0 will not start? (updated 04/25/2001)
#2196 I have plenty of RAM, why do I get an 'Out of memory' error? (updated 09/11/2002)
#2137 How do I solve 'The specified procedure could not be found' errors? (updated 05/10/2001)
#2228 How do I display the Euro symbol ( ) in my ASP pages? (updated 03/22/2002)
#2335 How do I log / track ASP errors on my web site? (updated 08/06/2002)
#2122 Why do I get an error about a 'Smart HTML interpreter'? (updated 04/11/2001)
#2221 Why do I get 'Type Mismatch' when using the Session object? (updated 07/08/2002)
#2263 How do I get the computer name / IP address of the server? (updated 09/25/2002)
#2266 Why do I get 'HTTP 500-12 Application Restarting' errors? (updated 08/16/2002)
#2240 How do I protect my images and other visual content? (updated 06/30/2002)
#2257 Why are people telling me to fix my clock / timezone? (updated 02/21/2002)
#2247 How do I change a list into a set of table rows and columns? (updated 06/14/2002)
#2286 Why does session.abandon not take effect right away? (updated 04/16/2002)
#2261 Why can't I retrieve custom header information from Request.ServerVariables()? (updated 02/25/2002)
#2063 How do I embed a TAB character into source code? (updated 07/08/2002)
#2267 Why do I get 'The RPC Server is Unavailable' messages? (updated 03/04/2002)
#2283 Why do I get the error Object Required: ''? (updated 09/06/2002)
#2202 How can I increase the amount of connections in Workstation / Professional? (updated 10/30/2001)
#2389 Why do I get errors in the 800A0001 -> 800A000F range? (updated 09/17/2002)
#2262 Why can't I turn buffering off using Response.Buffer in IIS 5.0 and up? (updated 10/17/2002)
#2250 How do I find out the amount of space left on my server? (updated 02/20/2002)
#2290 How do I make sure the client is still connected before processing? (updated 04/22/2002)
#2298 How do I perform a Whois / DNS lookup from ASP? (updated 10/30/2002)
#2210 How do I change the default server scripting language in InterDev? (updated 11/01/2001)
#2321 How do I redirect a user to https:// if they access a page with only http://? (updated 07/11/2002)
#2390 Why does IsNumeric() return true for some strings that contain characters? (updated 11/26/2002)
#2330 Why do I get 80020005 errors? (updated 08/12/2002)
#2369 How do I make hyperlinks out of plain text URLs and e-mail addresses? (updated 08/19/2002)
#2346 What could cause all of my session or application variables to disappear? (updated 08/15/2002)
#2258 Why am I having problems installing Visual Studio.NET RTM? (updated 02/21/2002)
#2310 Why do I get 'Object doesn't support this property or method' errors? (updated 08/19/2002)
#2333 Can I mimic VBScript's trim, ltrim, rtrim in server-side JScript / JavaScript? (updated 07/19/2002)
#2294 Why do I get 'A script block cannot be placed inside another script block' errors? (updated 05/15/2002)
#2356 How do I solve 'The Requested Resource is in Use' errors? (updated 08/12/2002)
#2366 Why do I get ASP 0113 / Script timed out errors? (updated 08/17/2002)
#2399 How do I make sure an entered string contains only valid characters? (updated 09/04/2002)
#2338 How do I convert from Hex to Decimal and back? (updated 07/29/2002)
#2398 How do I print the first n characters of a large block of text? (updated 09/04/2002)
#2414 Why do I get errors in the 800A0400 -> 800A0415 range? (updated 09/16/2002)
#2401 How do I parse the domain name out of a URL? (updated 09/09/2002)
#2358 Why does GUID not work correctly with response.write? (updated 08/14/2002)
#2402 How do I parse the file name out of a path or URL? (updated 09/09/2002)
#2415 How do I make sure my servers have the same time? (updated 09/16/2002)
#2329 How do I convert old IDC / HTX pages to ASP? (updated 07/17/2002)
#2371 Why do I get 'Invalid Default Script Language' errors? (updated 08/18/2002)
#2422 Why do I get errors in the 800A0030 -> 800A003A range? (updated 09/23/2002)
#2412 Why do I get ASP 0130 / ASP 0131 errors? (updated 09/15/2002)
#2430 How do I count the number of times x occurs in string y? (updated 12/14/2002)
When should I use a recordset object (ADODB.Recordset)?
There has been much discussion about when a person should use the various objects associated with ADO. What
follows are some loose guidelines for the correct usage of these objects. This article is by no means the total picture,
but rather will attempt to give the programmer the information to decide which method is 'right' for the purpose they
A quick note: NEVER store a recordset in the session object. For a discussion of this see:
Q176056
Most data access tasks can be implemented by using the execute method of the connection object. Why would we
want to do this? Well, for one, there is extra overhead in using a recordset object for UPDATE and INSERT
functionality. This is because the provider has to translate your code into an equivalent T-SQL statement anyway
(the database itself has no knowledge of "addNew" and similar methods). Also, there are many dangerous locks
associated with recordsets... most of which are not necessary (especially when performing an INSERT or UPDATE).
Your goal should be to get in, tweak your data, and get out as quickly as possible. Using direct statements is the
quickest way to do this, since there is much less overhead and no locks associated with your activity.
Another benefit of using an INSERT or UPDATE statement is that it is much easier to debug. You can change
conn.execute(sql) to response.write(sql) and immediately see why your statement is throwing an error. With a multi-
line transaction using a recordset object, it is translated to an INSERT or UPDATE statement (inefficient!) on the DB
To use the connection object, simply design a transact-SQL statement for the action you want to use and implement
it like so:
<%
sql = "INSERT INTO <table> (fields) VALUES (values)"
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
conn.execute sql, , &H00000080
...
%>
No recordset object is needed for this, because there is no need to return data (and in this case, your performance
will increase if you add '&H00000080' as the third parameter - the constant for adExecuteNoRecords). If there were
a need for returned data in the form of a recordset, we would do it like so:
<%
sql = "SELECT field1, field2 FROM <table>"
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
set rs = conn.execute(sql)
...
%>
The command object is also unnecessary overhead that does little, aside from bloat code, create confusion, and
require constant declaration (ADOVBS.INC = bad). Even for executing stored procedures, you can do it with the
<%
sql = "EXEC SP_doSomething @param1=1, @param2='" & var & "'"
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
set rs = conn.execute(sql)
...
%>
Even a recordcount can be obtained without having to create an entire instance of an object (see Article #2193).
There are a few exceptions to this rule, of course. When certain ADO methods need to be used, or the cursorType
needs to be changed for any other reason (e.g. for paging through a resultset), it may be necessary to use a
recordset object.
What are the limitations of Access?
It's no secret: I hate Access. I don't think it should be used in a production environment for anything more than a
personal website, and an unpopular one at that. If you're building a web site that you expect will be even remotely
successful, you're only delaying the inevitable by using Access now. You will eventually be forced to upgrade to SQL
I have many reasons for this, most from first- or second-hand experience. I'm not going to get into all of those right
now, because it is somewhat biased by my experience, and the type of applications I specialize in (which likely
My primary reason, however, is that Access (and the Jet drivers) can only manage a handful of users at any given
time (see Q154869 and this interesting discussion for some background on this). I've received plenty of criticism
from people who expect there to be a hard-lined, cover-all-your-bases, magic number for how many users an Access
database can support. Sorry to disappoint you, but there is no such magic number. There are far too many variables
■ database structure
■ size of tables
■ number of records
■ number of fields
■ size of fields
■ bandwidth
■ processor speed
■ disk subsystem
■ RAM
■ behavior of users
Microsoft doesn't even publish hard numbers; they just use off-hand references such as "ten or fewer" and never
For some people, a handful of concurrent users is fine. If you have a site with a guestbook, and you get a few dozen
entries a week, Access should be sufficient. But if you have a site with database-driven navigation, full search
functionality and megs of data flowing each day, you may want to read this article in its entirety before settling on
Access.
======================
From Q222135
"While Microsoft Jet is consciously (and continually) updated with many quality, functional, and performance
improvements, it was not intended (or architected) for the high-stress performance required by 24x7 scenarios,
ACID transactions, or unlimited users, that is, scenarios where there has to be absolute data integrity or very high
concurrency."
"ASP also supports using the Microsoft Jet database engine as a valid data source. The Access ODBC Driver and
Microsoft OLE DB Provider for Microsoft Jet are not intended to be used with high-stress, high-concurrency, 24x7
server applications, such as web, commerce, transactional, messaging servers, and so on."
======================
From Q225048
"First, Microsoft Jet can only handle a limited number of sessions. If your application uses a large number of ADO
======================
From Q240317
"Microsoft Jet has a read-cache that is updated every PageTimeout milliseconds (default is 5000ms = 5 seconds). It
also has a lazy-write mechanism that operates on a separate thread to main processing and thus writes changes to
disk asynchronously. These two mechanisms help boost performance, but in certain situations that require high
From http://msdn.microsoft.com/library/en-us/dnmsde/html/msdeforvs.asp
"Jet can support up to 255 concurrent users, but performance of the file-based architecture can prevent its use for
many concurrent users. In general, it is best to use Jet for 10 or fewer concurrent users."
======================
From http://msdn.microsoft.com/library/en-us/dnduwam/html/d2dbase.asp
"Phase 2 of the Duwamish Books sample includes a Microsoft® Access .mdb database, although this technology
======================
If you're going to try Access, you'll need to test your application under stress and heavy load. I would max out at
two to three times your expected volume (because your volume will have peaks and valleys). See Article #2319 for
a list of tools and services designed to stress test your web site(s).
You're not going to get hard numbers from anyone else that apply specifically to your application. It is YOUR
responsibility to benchmark YOUR application and make sure that Access can handle the maximum number of
simultaneous users YOU expect (or hope for). Not performing this kind of test is doing a disservice to you, your
With all that aside, if you're stuck with Access, here is a list of links to ASPFAQ and KB articles revolving around
specific Access error messages. This isn't a completely exhaustive list, but it should serve to be a good starting
point.
Article #2128, Q162980, Q171850, Q172898, Q178070, Q181209, Q181832, Q184233, Q191619, Q205972,
Q207586, Q216425, Q237994
80040e14 / 80040e37 - The Microsoft Jet Database engine cannot find the table or query 'tablename'. Make sure it
Q154869
80004005 - *
Article #2009
==================
Aside from the concurrent user and permissions problems, Access lacks many other qualities of a mission-critical,
enterprise-level database.
==================
Scalability
==================
I don't believe it can be stressed enough that Access will simply not stand up to traffic. I realize it is tough to
simulate TRUE load testing in a development environment, but if it can prevent you from launching an inadequate
database, it will be worth the trouble. Mission-critical databases should laugh at traffic; database servers should
buckle under bandwidth and RAM contsraints before they're ever stopped by the database itself. As an added point,
SQL Server has the flexibility of actually using multiple processes on an SMP machine.
See Article #2345 for a comparison of the capacities of Access, SQL Server and MSDE. If you can deal with a 2 GB
size limitation and a built-in limitation of 5 concurrent workloads, you will be much better off starting with MSDE
==================
Integrity
==================
From Q300216:
"Unlike a file-based database engine, a server-based database engine such as Microsoft SQL Server processes all of
the multiple client requests to a database at the server. The server keeps track of these requests in a transaction
log. If, for any reason, a request cannot be fulfilled, the server rolls back or does not process the request. This
reduces the possibility that the database will be left in an incomplete or corrupted state."
Access doesn't have a good backup scheme; in fact, it doesn't have a backup scheme at all. Being file-based, there
are two problems with attempting to back up an Access database: (1) if records are modified while the backup is
being performed, the backup may become corrupt; and, (2) many backup programs won't even touch a file that is in
use. Most capable database systems have a variety of configurable backup schemes, and SQL Server is no
exception. SQL Server also has comprehensive locking facilities, making it much more difficult to corrupt a backup.
Similarly, it is near impossible to modify an Access database while it is 'live', e.g. while any user has a page opened
that is accessing any table within, or if another user has the MDB file open in the Access GUI. You have to copy the
database, make your changes, and replace the 'live' version - waiting until nobody is on your site. Not something
==================
Replication
==================
A decent database system has at least one way to replicate / transfer content from one database, or one server, to
another. SQL Server has multiple options for Data Transformation Services (DTS), and can have external tools
"plugged in" to perform similar tasks. With versions of Access prior to 2000, it was always right-click, copy .mdb file,
paste. Yuck.
==================
Security
==================
Capable database platforms have multiple levels of configurable security, down to the object level. A user can also
be permitted across databases and across servers. Through the context of ASP, Access only has the ability to
password protect a database on a single file basis, so you can't have custom permissions per query, table or view.
Another element of security is that even password-protected MDB files are easily compromised simply by importing
==================
Transactions
==================
SQL Server is a transactional database. Aside from remote stored procedures, any set of operations within a
transaction can be rolled back. With Access, you would have to either (a) use transactions from an external
application, e.g. COM+ or MTS; or (b) revert to a previous copy of the database.
==================
Triggers
==================
In SQL Server, triggers allow you to perform operations in response to certain events without slowing down the
calling application. For example, you could have SQL Mail send you an e-mail after every five inserts to the ORDERS
table where the order total is greater than $50. With Access, you would have to create a table, store a count for the
number of times such an insert occurs, and code the application to send mail at insert time (which slows down the
==================
SQL Mail
==================
SQL Server supports a native mail format; as long as there is an Exchange Server within reach, you can tell the
database to e-mail specific users on certain events... e.g. within a trigger, or when certain database tasks fail. With
a custom add-in, you can also use any SMTP server (see Article #2403). With Access, you would have to code this
stuff up yourself - assuming you find some way to trap the event(s) in the first place.
==================
Jobs
==================
SQL Server supports jobs, allowing you to schedule database tasks and have the system execute them automatically
(instead of a user having to invoke them). You can schedule jobs to be performed when the CPU is idle, or at certain
times during the day. We use this for number-crunching at the end of each day, and for archiving stats throughout
==================
Stored Procedures
==================
Yes, Access supports stored queries. But IMHO, these are nowhere near as powerful as stored procedures. For one,
it is difficult to have stored queries access data from different databases. With stored procedures, this is trivial at
worst. SQL Server stored procedures support cursors and temporary tables, both of which are very powerful tools in
sorting data and performing queries. T-SQL also supports conditional logic, such as if / then and case blocks, as well
as index hints, locking hints and join hints. Most of these things are either extremely cumbersome or simply not
Additionally, SQL Server comes with several system and extended stored procedures, which you can plug into your
existing logic to do all sorts of things (such as retrieve a directory file listing (exec xp_cmdshell 'dir c:\'), list all of
the users currently accessing your database (exec sp_who2), or iterate through all DSNs on the server (exec
==================
Syntax Consistency
==================
Many Access developers are confused when they create a stored query that works fine within the Access
environment, but does not work from ASP or VB. For example, the function NZ() only works within Access. If you try
to use it from ASP or VB, you get an error that the function is not supported (see Article #2394). If you don't
discover this problem early on, you may have a lot of code to re-write.
==================
Again, if you want to use Access for your personal photos page or your CD collection, and you're not going to publish
it for the world to see, then Access is more than capable. I strongly recommend not using Access in any application
for which a 3rd party is relying on you, especially an e-commerce or other 24/7 operation.
And I'm not saying you have to use Microsoft's SQL Server, or even the latest version (I prefer 2000 over 7.0).
While it is the database that is the natural 'next step' - and many upsizing tools and tutorials are available (see
Article #2182); there are several other database packages you can look at, each with their own strengths and
weaknesses. These include SQL Server Desktop Edition, Sybase, DB2, Oracle, Informix... my preference, as you
might guess, is SQL Server... but I do have some level of faith in all of these products.
Finally, if you're stuck with Access, make sure you use the latest version of MDAC
(http://www.microsoft.com/data/), use a JET connection string (see Article #2126 for connection strings and Article
#2342 for JET downloads), and keep your database in good shape (see Q300216 - HOW TO: Keep a Jet 4.0
Database in Top Working Condition). Also, make sure your server has all the latest updates (see Article #2151).
Where can I get basic info about using stored procedures?
In this article, I will step through some INSERT, UPDATE and DELETE stored procedures, from the ground up. We will
explore a few speed bumps that I have experienced, and which you will most likely experience also.
This article assumes you're familiar with SQL Server and ASP, and have dbo-level privileges in Enterprise Manager.
Please be sure you have the latest version of Microsoft Data Access Components (MDAC) installed. If you're running
NT 4.0 or Windows 9x, you can download new versions at http://www.microsoft.com/data/. See Article #2057 to
These samples were tested using the version of MDAC that ships with Windows 2000 (MDAC 2.5), as well as the
1. Performance - stored procedures are pre-compiled by SQL Server, so performance is fast. The
increase in speed is similar to the benefits of porting lengthy blocks of resource-intensive ASP code
2. Isolation of Logic - stored procedures allow you to separate your data tier logic from presentation
code. This way, your ASP experts don't have to be SQL experts - and vice-versa. The SQL gurus can
often change the way a stored procedure works with data, without even having to touch the ASP
gurus' code. Granted, there are some times when this can add complexity to a project; for those
Our company policy is to encapsulate ALL data logic into stored procedures, and eliminate all direct SQL calls
(whether from ISAPI, COM, ASP, Servlets, or Java). For quick and dirty tests, you don't need to be 100%
formal about this. But for anything in production -- if you care about performance -- you should be using
Stored procedures are created within SQL Server's Enterprise Manager GUI. You can create them from Query
Analyzer, or even from ASP code. Personally, I prefer the GUI for its "Check Syntax" button, but many
people think this is gratuitous and also find the space in the procedure GUI limiting. In any case, before we
get into the process, let's show a simple example of a stored procedure.
Note that your instinct may be to name a stored procedure with the sp_ prefix. Please do not do this, as this
prefix is reserved for system stored procedures. In addition to making your procedures confusing and
possibly opening the doors for pre-empting critical procedures used elsewhere in your code; even if you
choose a unique name, this causes a performance hit as the engine checks for that procedure name in the
Master database first. Accordingly, the procedures demonstrated in this article will be given the prefix FAQ_.
This simply does a SELECT statement, returning authors' last and first names, ordered alphabetically by last
name. Notice the only syntactical difference between a typical SELECT statement and an equivalent stored
procedure is the CREATE PROCEDURE wrapper (and the BEGIN/END statements, which aren't altogether
necessary).
This was a fairly trivial example; I'd like to go much deeper than this today. But first, let's examine some
To force TCP/IP over named pipes, and to avoid superfluous layers, here is a fictitious
<%
cs = "provider=SQLOLEDB; network=DBMSSOCN; server=1.1.1.1;"
' if you use a port other than 1433, use server=1.1.1.1,1500;
' (where 1500 is the port number SQL Server is listening on)
cs = cs & "database=pubs; uid=myUsername; pwd=myPassword;"
set conn = Server.CreateObject("ADODB.Connection")
conn.open cs
' ...
%>
The command object brings overhead that is rarely necessary when executing any stored
procedure. This object has very stringent guidelines as far as creating parameters (including
strictly matching any and all data types being passed), and almost always forces you to
include ADOVBS.inc for constants (which is inefficient - see Article #2112). Some texts will
claim "it's faster, and simpler to use." While the former might be true in the case of a single
output parameter and no records, I've seen no proof of it otherwise... and I outright disagree
with the latter. Command object code is monstrous and very error-prone.
I avoid ADODB.Recordset for pretty much for the same reasons as avoiding
ADODB.Command. It has all kinds of overhead I very rarely require, and uses more complex
code to accomplish the same results. For more information, see Article #2191(and recall
that, in earlier versions of MDAC, an RS.Open call would tend to crash if it was an empty
resultset).
While retrieving recordset elements by name is more intuitive, it is actually much less
efficient than retrieving them by index number. Also, this almost forces you to assign them to
local variables, which aids in reuse and in retrieving text/memo fields. This, however, is one
of the few scenarios where you have to be in tune with the stored procedure developers... if
they change the SELECT field order, they have to tell you. When you work alone, this danger
isn't as great... but it might be one of those preference issues where readability outweighs
performance.
Let's move on to a more concrete example. We'll start with a fairly simple table. I created a table in pubs
Field Name Data Type / Length Field Name Data Type / Length
I used one column to represent each datatype (and/or property) that I will be covering in this article.
DATETIME and VARCHAR(>255), in particular, seem to cause many problems for ASP developers. Here is the
script used to create the table from Query Analyzer, so you don't have to muck with the GUI:
We make the id field an identity, so that it can be a unique, auto-incrementing value. Replace "myUsername"
with the true username you'll be using to connect with - the last line applies appropriate permissions (it
should have INSERT, SELECT, UPDATE and DELETE). Depending on the version of SQL Server you're using,
you may have to hit F5 before you see the new table in the Tables list.
INSERT
Now that we have a table created, we can start writing stored procedures around it. The first one we'll need,
of course, is an INSERT statement. After all, we need to get data in there somehow! A typical INSERT
One of the problems with running an INSERT statement directly is that you need to find some other means of
retrieving the IDENTITY value of the record you inserted. This is one of the most common questions asked in
Microsoft's public newsgroups, and it seems to cause people a lot of trouble (both in initially doing it, and - in
concurrent user scenarios - making sure that they are getting the proper IDENTITY value back, not someone
else's). A stored procedure makes this much more reliable, though admittedly not perfect. @@IDENTITY is a
*global* variable SQL Server uses to keep track of the IDENTITY value last inserted. While I have never
personally experienced a mistaken identity, I'm not going to go out on a limb and say it will never happen.
[Note: if you are running SQL Server 2000, replace all further instances of @@IDENTITYwith
SCOPE_IDENTITY(). This is a new method which makes sure you only get the IDENTITY value that *your*
connection generated.]
Changing this statement to a stored procedure will be pretty simple. Open Query Analyzer, copy the following
Again, replace "myUsername" with the name of the account that will be accessing this application.
Let's review our code before we do anything with it. Always prefix stored procedures with dbo. This will
prevent permissions issues later on, and will prevent the awkward situation where two people, connected as
two different users, create stored procedures with the same name (yes, this is possible). We take in four
parameters: @title, @pubdate, @synopsis, and @salesCount. You'll notice I changed the datatype of
@pubDate; this is because we're only interested in the CHAR(10) equivalent of the date (10 characters, e.g.
"01/01/2000"). I've found that I run across far fewer problems when I avoid passing explicit datetime values
After the BEGIN command, we SET NOCOUNT ON -- this prevents interim statements from being returned as
recordsets.
I strongly recommend you always use SET NOCOUNT ON at the beginning of your stored procedures. This is
because, as you might learn soon enough, statements that return RowsAffected results (or any message, for
that matter) in Query Analyzer can also be interpreted as recordsets in client code -- which means the
recordset you think you're on isn't necessarily the right one. One example that dogged me for a few hours
went as follows:
INSERT ...
DELETE ...
SELECT ...
When I would call this stored procedure from an ActiveX DLL, I would try and obtain some fields from the
recordset... only to receive the "ordinal name not found" error. It took me a while to realize that using
NextRecordset() would eventually get me to the results I was after, but I didn't like this at all (the code was
inefficient, because it was returning much more data than it needed to be). Once I changed it to the
SET NOCOUNT ON
INSERT ...
DELETE ...
SELECT ...
I have quickly become accustomed to wrapping non-result-returning code inside of SET NOCOUNT ON... to
avoid being bitten by this in the future. So, if you decide not to use the NOCOUNT options, at least (having
read this) you'll know what to look for if you experience similar problems.
Next we use the DECLARE command to create a temporary INT value called @newBookID. (While we could
do this without a temporary variable, this is a good habit to have - you will eventually be dealing with
multiple IDENTITY values in one stored procedure.) This value will store the IDENTITY number for the record
inserted by the stored procedure. The next 14 lines make up the INSERT statement, utilizing the four
parameters passed to the query - but otherwise looking like most INSERT statements you've seen before.
Note that we don't have to pass a value for the inprint column, since it has a default value of 1 - and we are
assuming that all books are, in fact, in print (we'll deal with overriding this value shortly).
The first SELECT statement in this query applies the global @@IDENTITY value (the latest insert) to the local
variable @newBookID.
All right, let's test this stored procedure before we move to ASP. In Query Analyzer, copy the following
query:
EXEC dbo.FAQ_InsertBook
@title='Seven Minute Stored Procedures',
@pubdate='01/01/2000',
@synopsis='This book helps you trim the fat, and feel good about coding again!',
@salesCount=0
When you execute the query, you should see the number 1 under the newBookID column in the results pane.
Next you'll see how we return this value, within a recordset, to our ASP page.[Footnote 2]
First, to be sure the record was inserted properly, change your query to the following:
After executing this query, you should see the new record intact, with all of the values you inserted. Now
that we know our stored procedure is running correctly, let's move to the fun part. The goal here, of course,
is to have ASP scripts running these things -- not to be fiddling with Query Analyzer and Enterprise Manager
We'll start script similar to the ASP code above. I will populate local variables with values, but you can
assume that these values come from anywhere (e.g. the Form or QueryString collection). I have also
included my "FixDate" and "FixString" functions, which clean up dates and escape apostrophes, respectively.
<%
FUNCTION FixDate(str)
datestr = cdate(str)
mStr = month(datestr): dStr = day(datestr): yStr = year(datestr)
if (Clng(mStr)<10 and len(mStr)=1) then mStr = "0" & mStr
if (Clng(dStr)<10 and len(dStr)=1) then dStr = "0" & dStr
FixDate = mStr & "/" & dStr & "/" & yStr
END FUNCTION
FUNCTION FixString(str)
FixString = replace(str,"'","''")
END FUNCTION
title = FixString(title)
pubdate = FixDate(pubdate)
synopsis = FixString(synopsis)
salesCount = cLng(salesCount)
If you've gotten this far and are getting errors from the SQL Server driver, a good idea to try and debug is to
Response.Write the sql variable, so you can review on-screen EXACTLY what you're passing to the database
engine. It is often easier to spot a missing single quote or comma in a resulting string than in concatenated
code like the above sample. See Article #2145for more info.
UPDATE
Okay, so now we have data in there, what if we want to update it? Let's say the book 'Seven Minute Stored
Procedures' (ID = 1) reached a sales count of 4,023 units, and was shortly thereafter discontinued. We
would start with the same principle -- a basic UPDATE statement in T-SQL might look as follows:
A stored procedure that does this kind of update is extremely similar to the one we wrote earlier for inserting
The biggest difference here is that we have to pass the ID number as a parameter, so that SQL knows which
record we want to apply that change to (without a WHERE clause, it would update every record in the table).
Also note that if you DON'T pass a new inprint value, it will not change. The input param @inprint has a
default value of 1, so the value in the table will only change if you override it (which we will do in a moment).
You can test this new stored procedure by issuing the following command to Query Analyzer:
EXEC dbo.FAQ_UpdateBook
@id=1,
@title='Seven Minute Stored Procedures',
@pubdate='01/01/2000',
@synopsis='This book helps you trim the fat, and feel good about coding again!',
@salesCount=4023,
@inprint=0
And from ASP, the code for this new stored procedure would be almost identical to the previous example.
Let's assume, from now on, that you have placed the FixDate() and FixString() functions into an #INCLUDE
file called sqlFunctions.asp. The following file will update the synopsis of the book to reflect the fact that it is
no longer in print:
<!--#INCLUDE FILE='sqlFunctions.asp'-->
<%
id = 1
title = "Seven Minute Stored Procedures"
pubdate = "1/1/00"
synopsis = "This book was removed from circulation effective 6/1/2000."
salesCount = 4023
inprint = 0
id = cLng(id)
title = FixString(title)
pubdate = FixDate(pubdate)
synopsis = FixString(synopsis)
salesCount = formatnumber(cLng(salesCount),0,-1,-1,0)
inprint = cLng(inprint)
You're probably asking "what's that &H00000080 for?" This is the constant for adExecuteNoRecords. It tells
the SQL engine that you won't be returning or processing any rows, and executes the query more efficiently -
providing a bit more performance boost to your application. My testing yielded gains better than 20% simply
by adding this constant to my execute calls in ASP and VB. Since this stored procedure didn't return any
data, we didn't need to create a recordset object at all; we simply passed the SQL statement directly to the
execute method of the connection object. One issue you'll want to watch for is passing larger integer values
to SQL statements. Because a large portion of T-SQL syntax relies on commas, you have to be careful not to
pass numbers formatted with comma separators (which is why the above salesCount assignment has the
additional formatnumber command - in case you're passing to it a value with comma separators).
DELETE
Now we'll get into a slightly more complex example. Let's say we want to delete all records where the word
"seven" appears in the title (hey, I didn't say these examples had to be logical!). The typical DELETE
A common requirement for this kind of query is that it alerts the user how many records were actually
deleted. When using a normal DELETE statement, you would have to do a differential between SELECT
COUNT(id) before and after the DELETE query is run. This makes for messy and inefficient code, and is not
entirely intuitive either. Luckily, SQL Server has another global variable which, like @@IDENTITY, is used to
keep track of specific values. This variable, called @@ROWCOUNT, reflects the number of rows affected by a
query.
And this brings us to one of the challenges with using @@ROWCOUNT. Any value returned to it is
automatically set to 0 when SET NOCOUNT ON is used in conjunction with statements that don't return any
rows (such as IF or DELETE statements). Ultimately, this means we're going to have to examine returning
multiple recordsets from a stored procedure. Don't get me wrong, it is not a bad thing to learn. But it is one
of the reasons this example is slightly more complex than the previous examples.
Before we get too far, let's see what the stored procedure looks like. It is actually quite similar to the
Executing this query from Query Analyzer would look like this:
You can use FAQ_InsertBook in the Query Analyzer to populate the BOOKS table with a bunch of identical
records, then run FAQ_DeleteBook to make sure the proper number of rows are being returned. When you
are sure the stored procedure is working as advertised, we can move to ASP. The biggest difference in the
following code, compared to the resultset example from the INSERT stored procedure, is that we call the
<!--#INCLUDE FILE='sqlFunctions.asp'-->
<%
title = "seven"
The first "recordset" doesn't return any data (but is defined as a recordset by the provider), so we don't need
to care about any results. In fact, trying to access any element in the first recordset will result in an ADO
error. Once you move to the second recordset, you can safely retrieve the number of records that the query
deleted (and this is why, in this case, we don't use the adExecuteNoRecords constant).
Note that most DELETE queries would delete by ID number or some other unique identifier; all it takes is
someone passing a criteria value of 'a' to delete just about every book in your database. These are just
simple examples that try to cover all the little issues that invariably come up when using stored procedures.
Conclusion
As you can see, there are many pros and cons to using stored procedures. The main advantage are
performance, additional functionality and encapsulation of code; while a significant disadvantage is that there
are a few more considerations to make when writing code. Overall, it is the author's opinion that stored
procedures are worth the trouble - several times over. You owe it to yourself to try them out, and see for
yourself how much they can improve your database's performance, as well as your development schedule.
Footnotes
[1] One thing you'll want to make sure of is that your ASP pages are actually creating dates in MM/DD/YYYY format. Server setup registers MM/DD/YY
as the default system locale (as ridiculous as this is after that whole Y2K thing); so, even if you have changed the format to MM/DD/YYYY for a specific
user, if nobody is logged in to the box, it reverts to the default. I have yet to find a reliable way, short of reinstalling from scratch, of reversing that
setting (and I have argued with my fair share of WPP "tech support" people about it). To be sure my ASP scripts are always using proper CHAR(10)
date format no matter who is logged in, I wrote the following VBScript function - which I place in all of my top-level includes (I've also included a
' As a test:
[2] Yes, using an output parameter can be slightly more efficient than returning a recordset. However, this is one of those cases where ease of use
often outweighs performance concerns, IMHO (output parameters, like ADODB.Command, can be a pain to use). And further to that, I find it more
often than not the case that I'm returning multiple recordsets from a stored procedure (as opposed to the rare case of only one value being returned),
(We split up this article into two - one involving database-related issues, the other encompassing everything else. If
your error does not involve a database, please see Article #2413.)
First off, test connecting to your database *without* any sort of DSN. See Article #2126 for sample DSN-less
connection strings. Removing a flawly configured DSN might solve any 80004005 errors, but if nothing else, it will
Next, make sure you have the most recent MDAC (http://www.microsoft.com/data/). If you are using Access, make
sure your database is in 2000 or 2002/XP format, and that your servers have the most recent JET drivers (see
Article #2342). In addition, make sure the anonymous user on your machine (IUSR_machineName) has both read
and write access to the folder in which the MDB file(s) reside. Finally, make sure your server is up-to-date (see
Article #2151 for recent information on service packs and security fixes).
That said, in the remainder of the article we will highlight some of the more common 80004005 errors, and attempt
or
or
Microsoft OLE DB Provider for ODBC Drivers error '80004005'
[Microsoft][ODBC Microsoft Access 97 Driver] Couldn't use '(unknown)'; file
already in use.
This usually means that the MDB file is open in a copy of Access on the server, or Visual InterDev is open and has an
active connection to the database. For more information, see Q174943. This can also mean you are attempting to
connect to an MDB file on a remote share; if so, see Article #2168 and Q189408 for more information. See Q315456
and Q251254 for information on correcting the permissions on your %TEMP% folder. Finally, see Q166029 for
information on configuring credentials for authenticated users connecting to an Access database file. For other
or
One possibility is that the file is corrupt (Access databases have a tendency to do that -- see Article #2190 for a way
to compact / repair the database through code). A more common reason is that you are trying to connect to an
Access 97 database with Access 2000 or better drivers, or vice-versa. Make sure the server has the most recent
MDAC (http://www.microsoft.com/data/) and the most recent JET drivers (see Article #2342). Also, try upgrading
■ you referenced your connection incorrectly (e.g. spelled the DSN name, or one of the DSN-less string
components wrong);
■ the user connecting to the DSN or DSN-less connection doesn't have access to the information stored in the
■ you used an English or localized driver detail for your connection string when your system is not set up in
■ you are missing the connection string entirely (this can happen if you maintain your connection string in a
session variable, and your sessions aren't working; see Article #2157).
or
This is almost always a permissions issue, where IUSR_machineName or the autheticated user doesn't have read or
write access to the MDB file, the folder in which it is located, or the TEMP/TMP folders. For more information, see
Article #2062.
Microsoft OLE DB Provider for ODBC Drivers error '80004005'
[Microsoft][ODBC Microsoft Access 97 Driver] Couldn't lock file.
This usually happens because the anonymous or authenticated user does not have write permissions to create the
LDB file (this is Access' 'scratch' file). Make sure IUSR_machineName (or the user(s)/group(s) accessing the
application) have write permissions on the folder where the MDB file located.
Make sure you are using the most recent MDAC (from http://www.microsoft.com/data/) and, if using Access, the
This is actually often due to a corrupt database, when a table has long value columns (which are stored off-row). To
fix, either run a compact / repair on the MDB file (see Article #2190 for information on doing so from ASP), or
import all of its objects into a new MDB file and replace the existing file.
Microsoft OLE DB Provider for ODBC Drivers (0x80004005)
[Microsoft][ODBC Microsoft Access Driver]General error Unable to open
registry key 'Temporary (volatile) Jet DSN for process 0x9ac Thread 0xa0c
DBC 0x15d1024 Jet'.
or
or
or
or
or
Again, IUSR_machineName must have read and write permissions not only on the MDB file, but also on the folder in
which it resides, and in some cases the system %TEMP% folder. See Article #2154 and Q315456 for more
information. If you are trying to access a network drive, see Article #2168 for information on setting up
IUSR_machineName to connect to an Access database via a UNC share. You should also check out Article #2142 for
or
or
or
or
simply uses DBNMPNTW, which is the protocol name for named pipes. What the error means is that the SQL Server
you defined in your connection string is invalid - either because it is currently down, you named it wrong (either by
hostname, DNS/domain or IP address), or you don't have permission to connect. If you are expecting to connect by
IP address, make sure you override named pipes (see Article #2082). If you are using SQL Server authentication,
make sure both ends are set up for it (see Article #2138). If the error is 'SQL Server does not exist or access
denied', see Q328306, which has a large variety of possible causes and solutions. If the ASP page and SQL Server
are on the same machine, then it may be a loopback problem - try using 127.0.0.1 or LOCALHOST (instead of the
This usually happens with Oracle, when using a DSN or a misconfigured OLEDB connection string. To solve, drop-
kick the DSN. Try using the OLEDB providers instead of using ODBC. First, try the Microsoft-provided OLEDB
drivers:
If that doesn't work, download the Oracle OLEDB provider and use this connection string:
If you are trying to use Windows Authentication to connect to SQL Server, make sure the account being used has
been mapped to a valid SQL user or role, and that the user has been authenticated correctly (see Article #2126 and
Q306586).
This can occur if you use an isolation level of READ COMMITTED or READ UNCOMMITTED and have multiple
concurrent connections attempting to run the same procedure or batch. Consider using REPEATABLE READ or even
SERIALIZABLE, realizing that this will be a performance hit (usually sending lock blocks through the roof).
Otherwise, try and make your T-SQL code more efficient, so that there is less chance that two unique users will trip
or
or
This is often a firewall / router issue. Test that your web server can ping the SQL Server (both by name and IP) and
make sure that your connection string points to a valid IP address or network name.
or
or
or
or
Invalid cursor state
or
or
or
This is usually an intermittent error... it will happen every once in a while, and can be attributed to perhaps a flaky
network connection or an isolated network jam. If the error message(s) persist, see the following suggestions:
If you are using an ODBC connection, see Q176256 - and consider using OLEDB provider instead of the native SQL
If there is a specific stored procedure causing this problem, try adding these lines to the beginning of the proc,
running it once, and then commenting out or removing the NO_BROWSETABLE line:
If you are querying system tables, use a forward-only cursor (to be safest, avoid ADODB.Recordset altogether).
If you are using SQL Server 7.0: if you have ANSI_WARNINGS set to OFF, see Q259775; if you are using the
ROUND() function, make sure that the parameters you are sending are not null (see Q199105)
If you are using SQL Server 2000, and are using SELECT DISTINCT on a table with a LEFT JOIN on a view, upgrade
If you are using MTS transactions, try to change your approach by using transactions within SQL Server.
If none of the above solves your issue, see Q243899.
or
Unable to insert record...error number -2147467259: MS ODBC SQL Server Driver dbnmpntw.
ConnectionWrite (getoverlappedresults(). This may be caused by an attempt to update a
non-primary table in a view.
or
One potential solution, according to Q178040, is to use TCP/IP instead of named pipes. Or, according to Q186726,
use a local connection string, instead of a network string. Personally, I've never found any reason to use Named
Pipes in a web environment. See Article #2126 for sample DSN-less connection strings that avoid using Named
Pipes.
This usually means that the object name was misspelled or doesn't exist, or the user connecting to the database
cannot see the object because they do not have appropriate object-level permissions. For more information, see
Article #2164.
Microsoft OLE DB Provider for ODBC Drivers error '80004005'
[Microsoft][ODBC Microsoft Access Driver] Syntax error in UPDATE statement.
or
Make sure you are not using any reserved words (e.g. Date or Password) as column names ... if you cannot change
the column names, use [] brackets around them (see Article #2080 for a list of reserved words). Ensure that your
datatypes are appropriately delimited (e.g. no delimiters for numbers, ' for strings (and dates in SQL Server), # for
dates in Access. Also, try to avoid using rs.AddNew / rs.Update -- use a direct INSERT or UPDATE statement instead.
This usually happens when your parameters are empty, or strings aren't properly delimited, e.g. in the following
code:
<%
myString = ""
sql = "INSERT INTO table(column) VALUES('" & myString & "')"
conn.execute(sql)
myString = "foo"
sql = "INSERT INTO table(column) VALUES(" & myString & ")"
conn.execute(sql)
%>
Of course, you'll want to fix this problem by making sure you're not inserting empty or non-delimited strings.
Microsoft OLE DB Provider for SQL Server (0x80004005)
Invalid connection string attribute
Double-check your connection string. This is usually caused by invalid characters or empty parameters in the
connection string. See Article #2126 for examples of valid connection strings.
This is the most verbose error message I've seen from Microsoft to date. It seems pretty self-explanatory; you need
to look at your query, and look at your data, and determine why one of these relationships or constraints would be
This error message indicates that the spid attempted to execute an instruction that is currently not allowed. Check
your SQL Server error logs for access violations and other errors. Disable any startup procedures and make sure the
server starts cleanly. Check the log folder for .dmp files that may have happened around the same time.
Microsoft OLE DB Provider for Analysis Services (0x80004005)
Cannot connect to the server '<server>'.
The server is either not started or too busy.
Make sure the services MSSQLServer and MSSQLOLAPServices are both running.
You are either attempting to access Analysis Server while it is preparing the cube, or you are referencing a cube that
You are either attempting to connect to AS while a a database is being restored, or the initial catalog in your
You are attempting to access a cube that has been processed but not yet had roles applied; or, you are accessing a
Finally, Q306518 contains a listing of many 80004005 error messages, the most frequent causes of the error
AS400 imposes a limit (through QQRYTIMLMT) on queries... and it will refuse to run any query which appear,
Increase the commandTimeout on the connection object to some value higher than x (give yourself some buffer
because the estimated query plan might change). For information on changing the commandTimeout value, see
Article #2066.
http://tinyurl.com/p0k
(We will be covering MySQL and Oracle 80004005 articles in a forthcoming update.)
If you know of any other 80004005 errors that we haven't covered here, please let us know...
How do I upload images to a database?
While I *strongly* recommend storing the file in the filesystem and its LOCATION in the database, there are several
components out there that make this really easy. My preference is ASPUpload from Persits Software. You can see
You can also handle this type of task without a component, but it's a lot more code. There are good examples
Keep images on the filesystem, and the location and other data in the database
1. if there is any chance that you will migrate to a different database platform, your current BLOB format might
be incompatible with, or at least a pain to convert to, the new format -- since, like web browsers, each
2. when your database really goes south, to the point where even the backup is useless, you still have the files
on the filesystem (though their usefulness is questionable, depending on how much related data was kept in
the database). Which is arguably better than losing all of your data *and* all of your files.
3. having the images in the file system allows you to access the images from many different standard
applications (FTP, web browser, etc) without having to write application code to pull the data out of the
database, since you can't just 'SELECT image FROM table' and have the image appear in Enterprise Manager
or Query Analyzer.
4. with some databases, e.g. Access and MSDE, the data inside is limited to 2 GB, whereas the file system is
not as restricted. Also, most hosts charge a premium for SQL Server storage space, so in that case it would
5. in SQL Server, when a table has an IMAGE or TEXT column, a page is reserved for every row, whether or not
that column has any data... so if you don't have an image for each record, there is potential to have a lot of
wasted space. There are workarounds to this, of course, such as splitting the image data into its own table
with a foreign key to the main table. Also, in SQL Server 2000, if your images are less than 8kb, you can use
6. performance wise, including an <IMG SRC> tag generated by the database and pointing to a file that
already exists is going to be faster than pulling the file out of the database, generating a temp file on the
web server, and streaming that to the user. Also, table scans take more resources when there is an image
datatype as opposed to a varchar that simply holds a 'pointer' to the file's location.
7. can be quite complicated extracting images, say from an Access database, since it adds OLE header info to
the file (see Q175261). With SQL Server it's not so bad; see Q173308 for an example that works right out of
the box. You can also see Q194975 for samples that use ADO's GetChunk and AppendChunk methods.
1. you can protect your images better with SQL Server and file system security, as opposed to simply file
system.
2. it can be challenging to keep database and filesystem in sync with each other - if the record is deleted in
SQL Server (e.g. through an ad hoc query, or a rollback) how does the file system portion of the application
know to also delete the image in the file system? One approach is to use extended stored procedures in SQL
Server to encompass the file system operations into the database transactions, however this can become as
complex, or more so, than storing the BLOB directly in the database - which gives you greater transactional
Unless you can log into the ISP's machine with remote software (which is typically only allowed when you're leasing
an entire box), you can't. You need to have your ISP set up your DSN(s), which certainly takes time, and sometimes
costs money (depending on the host). Also, you can only manage so many DSNs reasonably (see Q252475). Using a
DSN-less connection (for example, those found in this article), you can avoid this hassle *and* make your data
access faster.
SQL Server
<%
cst = "Provider=SQLOLEDB;Server=<ip address>;database=<dbname>;"
cst = cst & "network=DBMSSOCN;uid=<uid>;pwd=<pwd>"
set conn = Server.CreateObject("ADODB.Connection")
conn.open cst
' // Use of Network=DBMSSOCN is to avoid Named Pipes errors
%>
To use SQL Server authentication, SQL Server must be set up in 'mixed mode' (both SQL Server and Windows
Authentication) and you must have a SQL Server login with appropriate permissions on the database(s) you are
connecting to.
<%
cst = "Provider=SQLOLEDB;Data Source=<Server Name>;"
cst = cst & "Initial Catalog=<dbname>;Integrated Security=SSPI"
set conn = Server.CreateObject("ADODB.Connection")
conn.open cst
%>
Note that to use Windows Authentication, IUSR_<machineName> must be in the domain, and given proper access
to the SQL Server; or, you must disable anonymuos access on the site / application - which will allow IIS to pass the
users' credentials to SQL Server. Not doing either of these things will result in the following error:
For information about configuring SQL Server for access through IIS, see Q247931.
<%
strASConn = "PROVIDER=MSOLAP;" & _
"DATA SOURCE=<ip>;" & _
"INITIAL CATALOG=<database>"
or
or
Oracle
If you are using the Microsoft OLEDB provider for Oracle:
<%
cst = "Provider=MSDAORA.1;User ID=<uid>;Password=<pwd>;"
cst = cst & "Data Source=<server>.<dbname>;"
set conn = Server.CreateObject("ADODB.Connection")
conn.open cst
%>
If you are using the Oracle OLEDB Provider (which you can download after registering here):
<%
cst = "Provider=OraOLEDB.Oracle;Server=<server>;Data "
cst = cst & "Source=<dbname>;User ID=<uid>;Password=<pwd>"
set conn = Server.CreateObject("ADODB.Connection")
conn.open cst
%>
Microsoft Access
<%
cst = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source="
cst = cst & server.mappath("/<pathtofile.mdb>")
set conn = Server.CreateObject("ADODB.Connection")
conn.open cst
%>
If you don't have JET 4.0 installed, consider installing it (see below). Otherwise, you can use the native ODBC driver
for Access:
<%
cst = "Driver={Microsoft Access Driver (*.mdb)};DBQ="
cst = cst & server.mappath("/<pathtofile.mdb>")
set conn = Server.CreateObject("ADODB.Connection")
conn.open cst
%>
In each case, of course, filling in the <variables> with the proper values.
"When running Microsoft Jet in an IIS environment, it is recommended that you use the native Jet OLE DB Provider
in place of the Microsoft Access ODBC driver. The Microsoft Access ODBC driver (Jet ODBC driver) can have stability
issues due to the version of Visual Basic for Applications that is invoked because the version is not thread safe. As a
result, when multiple concurrent users make requests of a Microsoft Access database, unpredictable results may
occur. The native Jet OLE DB Provider includes fixes and enhancements for stability, performance, and thread
See Article #2342 for information on obtaining the latest version of the JET OLE-DB drivers.
Others
For a more exhaustive list of connection string types, see http://www.able-consulting.com/ADO_Conn.htm, where
Carl Prothman has compiled several different techniques of connecting to just about any data source -- from DB2 to
Keep in mind that using an invalid connection string can lead to the following errors:
Microsoft OLE DB Service Components error '80040e73'
Format of the initialization string does not conform to the
OLE DB specification.
or
Depending on the version of your MDAC driver, and the database you are connecting to, these columns can either
(a) not show up at all, (b) only show up the first time they're called, or (c) cause 'Unspecified Error', 'Exception
Occured' or 'Errors Occurred' runtime errors, if the following recommendations are not observed:
■ Avoid SELECT * notation; NAME your columns in a list, and name the offending column(s) LAST.
■ Assign the value of the column to a variable IMMEDIATELY, and only use this variable from that point on.
Another problem people have is retaining line breaks from a textarea, once the value is returned to the screen.
Access and SQL Server DO store this information, it's just not in a format that a browser's HTML engine
understands. You need to replace line feeds and carriage returns with HTML tags.
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open <connection string>
sql = "SELECT idcolumn,intcolumn,memocolumn FROM table"
set rs = conn.execute(sql)
do while not rs.eof
memocolumn = rs("memocolumn")
response.write(replace(memocolumn, CHR(10), CHR(10) & " <br>"))
rs.movenext
loop
%>
clickable and that a currency column starts with a dollar sign and is followed by two decimal places, the browser
<%
response.write(rs("hyperlink_column"))
response.write(rs("currency_column"))
%>
And expect the browser to automatically wrap the hyperlink in an anchor tag, and format the currency column
appropriately. Unfortunately, this isn't how it works. HTML doesn't know what a hyperlink is, unless you tell it to use
an <A> tag. Similarly, HTML doesn't automatically format your numbers and assume you want thousands separators
DO NOT use the hyperlink datatype in Access. This is designed to allow users to click on links in the GUI. It is NOT
meant for presentation-layer formatting (e.g. HTML). Use a VARCHAR column for hyperlinks. I would also
recommend using a numeric data type with two decimal places for currency; in SQL Server, use the MONEY
datatype. Storing dollar signs and #link# values in the table aren't going to help you in HTML, and only add
Based on that, to format these values in your HTML, you need to do something like this (from Access)):
<%
hl = rs("hyperlink_column")
response.write "<a href='" & hl & "'>" & hl & "</a>"
curr = rs("currency_column")
response.write formatcurrency(curr,2)
%>
To get a hyperlink formatted properly right out of SQL Server, you can use:
SELECT
'<a href='+hyperlink_column+'>'+hyperlink_column+'</a>'
AS hyperlink_column
FROM table
To get a money value formatted properly right out of SQL Server, you can use:
SELECT
'$'+CONVERT(VARCHAR(100), money_column, 1)
AS money_column
FROM table
Some people have reported that this doesn't properly insert commas (I assume it has to do with collation or regional
settings on the server). So, to force it using a bunch of wacky string manipulation, you can try something like this:
SELECT
'$'+REVERSE(SUBSTRING(REVERSE(CONVERT(VARCHAR(100),CONVERT(MONEY,money_column),1)),1,100))
AS money_column
FROM table
Recordcount is not supported with the default forward-only cursor. If you open a recordset in the following manner,
<%
numrecords = 0
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
set rs = Server.CreateObject("ADODB.Recordset")
sql = "SELECT whatever FROM table WHERE [...]"
rs.open sql,conn,1,1
if not rs.eof then numrecords = rs.recordcount
response.write("There were " & numrecords & " matches.")
...
However I think this is more efficient (even though it looks like more code):
<%
numrecords = 0
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
sql = "SELECT COUNT(field) FROM table WHERE [...]"
set rs = conn.execute(sql)
if not rs.eof then numrecords = rs(0)
sql = "SELECT whatever FROM table WHERE [...]"
set rs = conn.execute(sql)
response.write("There were " & numrecords & " matches.")
...
Keep in mind that the condition in both queries [...] should be identical!
How do I connect to an Access database / text file on another web server?
Access isn't really designed for this type of application (see Article #2195 for more info), and if you need your
database server to reside on a separate machine from your web server, it might be time to look for a more scalable
database (e.g. SQL Server or Oracle). However, there is a way to connect to an Access database over the web,
assuming you know the absolute file structure of the remote server (and know this won't change). Keep in mind that
Access is not the most prudent performer on the same machine as the web site, and is likely to be more problematic
over the web (which has many more variables). Here is a sample connection string:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "Provider=MS Remote;" &_
"Remote Server=http://<ip address or server name>;" &_
"Remote Provider=Microsoft.Jet.OLEDB.4.0;" &_
"Data Source=c:\inetpub\wwwroot\file1.mdb;"
%>
I have received feedback that this "doesn't work" -- while I have no idea what "doesn't work" means, I can say that
along with the performance issues of using this kind of solution, there is also a painful configuration process that
must be followed. NTFS permissions, firewalls, existence of MSADC, and several other issues might prevent this from
Error Type:
Microsoft ADO/RDS (0x800A20FF)
Internet Server Error.
If you are getting this error, please refer to the following KB articles: Troubleshooting Common Problems with
Remote Data Services (Q251122) and HOWTO: Use RDS From an IIS 4.0 Virtual Server (Q184606).
designed for performing an HTTP "tear" - see Article #2173 for more info and a detailed example.
If you are using Windows Authentication, see Q197964 (PRB: Cannot Access Remote Files with the
FileSystemObject).
If you are using anonymous access, you can accomplish this by synchronizing the anonymous user accounts on the
Let's say you have MachineA and MachineB. MachineB has a text file or Access database, in a share, that you want
to have control over from your ASP application on MachineA. With a stock server setup, you should get one of many
To give MachineA access to MachineB's shares, you need to fuss with the Anonymous User employed by IIS
question).
Under the Directory Security tab, click 'Edit...' under 'Anonymous Access and Authentication Control.'
On Windows 2000, you will have to click 'Edit...' again under the Anonymous Access checkbox.
Uncheck "Allow IIS to control password" and enter a new password. When you click OK, you will be asked to
confirm.
Click OK, Apply, OK, and close out of ISM. If prompted to save console settings, say Yes / OK.
On MachineB, go into Local Users and Groups -- under Computer Management, add a user named IUSR_MachineA
In Windows Explorer on MachineB, right-click the share in question, hit Properties, and on the SECURITY tab Add...
the new local user, and give appropriate permissions. Hit Apply, Apply, OK.
Now run your ASP file from the original server and all should be fine!
If you are still getting the error, check that you haven't exceeded the connection limit of the machine with the share.
If it is Windows 2000 / XP Pro, only 10 anonymous users can be connected to it at a time. Consider using a server
operating system for hosting such a share, or hosting the share on the web server itself (that way IIS will be able to
grab the files locally, and interactive users will simply have to refer to the share on a different machine).
See Q308150 for walk-throughs of configuring virtual directories from remote shares.
How do I page through a recordset?
This article demonstrates how to page through a recordset WITHOUT using an explicit ADODB.Recordset object. We
have updated this sample to use a stored procedure in addition to straight ASP. There are two benefits to the stored
procedure version. One is that it is compiled after first execution, so much of the code only goes through overhead
once. More importantly, the stored procedure does its subset calculation within the database, so the entire recordset
is not sent over the wire. (We will be experimenting with getRows() soon.)
Let's assume we have the following table in SQL Server 2000 (don't worry, the non-stored procedure version will
Let's also assume that someone went through the painstaking task of documenting their entire CD collection (which
I did, since I was sick of receiving duplicates as gifts <G>). So imagine around 1000 INSERT statements populating
the table above. If I were to publish this CD collection on the Internet (which I did, to accompany my Christmas
wish list <G>), I clearly wouldn't want people to have to go through all the CDs in one page. Perhaps they're only
interested in the U2 album missing from my U2 set. So, let's write some code to break this up at 50 CDs per page.
Plain ASP
This version uses ASP code to grab all the records, and only display the subset currently requested. It's a little
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection_string>"
' set up a query to get the count, and another to get the data
countSQL = "SELECT count(artist) FROM CDs"
dataSQL = "SELECT artist,title,cdn FROM CDs"
' start getting the data, first we need to know the count
' this will tell us how many pages there will be
set rs = conn.execute(countSQL)
counter = clng(rs(0))
rs.close: set rs = nothing
' let's get the data, move to the first record if necessary
' loop through, returning records until we meet perpage or EOF
set rs = conn.execute(dataSQL)
'if counter > perpage and startrecord > 1 then
rs.move(startrecord-1)
for x = 1 to perpage
if rs.eof then exit for
response.write "<tr valign=middle><td>"
artist = rs(0)
if artist <> prevArtist then
prevArtist = artist
if rs(2) then
artist = "<img src=leaf.gif><b>" & artist & "</b>"
else
artist = "<b>" & artist & "</b>"
end if
else
artist = " <span style='color:#999999'>" & artist & "</span>"
end if
response.write "<nobr>" & artist & "</nobr></td><td>"
response.write "<nobr>" & rs(1) & "</nobr></td></tr>"
rs.movenext
next
response.write "</table>"
' clean up
rs.close: set rs = nothing
conn.close: set conn = nothing
%>
There is some extraneous formatting in there that you'll probably do differently, and there is some data missing
(e.g. style sheet parameters). Essentially, the above should work for any generic query. Obviously, if your SQL
statement will vary based on a search or other criteria, you will want to pass the countSQL and dataSQL statements
in a session variable instead of hard-coding them into the top of the page.
Stored Procedure
All right, so now you've seen the kids' example, let's move on to a bigger boy. Again, hopefully the inline comments
So now, the ASP code to retrieve this information is similar to the previous example, ony a wee bit shorter:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection_string>"
prevArtist = ""
set rs = rs.nextrecordset()
' clean up
rs.close: set rs = nothing
conn.close: set conn = nothing
%>
Comparing Methods
Sometimes it takes a lot to convince people that a stored procedure is faster than ASP recordset processing, and
often it takes even more to show that using a temporary table can sometimes be worth the trouble. Finally, it is
impossible to convince people that there are too many variables to say that one method will ALWYAS be faster than
another. To show these facts, I have posted these examples online, with timer() code, so you can compare for
yourself. Run each page, apples to apples, and scroll down to compare execution times. You will find, like I did, that
while the stored procedure method often outran the ASP code, the reverse was occasionally true as well. This could
be due to various things, including network traffic (at both server and client ends), other database activity, etc.
Recordset paging
External Information
http://www.15seconds.com/Issue/010308.htm
http://www.15seconds.com/issue/010607.htm
How do I get the ID number of a just-inserted record?
SQL Server
With SQL Server 2000, there are a couple of new functions that are better than @@IDENTITY. Both of these
functions are not global to the connection, which is an important weak point of @@IDENTITY. After doing an
PRINT IDENT_CURRENT('table')
This will give the most recent IDENTITY value for 'table' - regardless of whether you created it or not (this
PRINT SCOPE_IDENTITY()
This will give the IDENTITY value last created within the current stored procedure, trigger, etc.
If you are not using SQL Server 2000, the best way with SQL Server is to use a single stored procedure that
handles both the INSERT and the IDENTITY retrieval using @@IDENTITY. Here is sample code for the stored
procedure:
CREATE PROCEDURE myProc
@param1 INT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO someTable
(
intField
)
VALUES
(
@param1
)
SET NOCOUNT OFF
SELECT NEWID = @@IDENTITY
END
<%
fakeValue = 5
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<conn string>"
set rs = conn.execute("EXEC myProc @param1=" & fakeValue)
response.write "New ID was " & rs(0)
rs.close: set rs = nothing
conn.close: set conn = nothing
%>
If you are using SQL Server 2000, simply change the line in the stored procedure from ...
... to ...
SELECT NEWID = SCOPE_IDENTITY()
One of the problems with the global nature of @@IDENTITY is that if you do an INSERT, and that table has a
TRIGGER ON INSERT which then, in turn, inserts into another table with an IDENTITY field, @@IDENTITY is
populated with the second table's value. So, if you are stuck using SQL Server 7.0 and need a workaround to
retrieving the @@IDENTITY value because you have a trigger that also inserts into another IDENTITY-bound
table, you're in luck. You can add this code to the first line of the trigger, but you will have to update all of
your application and stored procedure code to deal with this new SELECT:
Access
With Access, you should be able to get away with something like this:
<%
fakeValue = 5
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<conn string>"
conn.execute "Insert into someTable(intField) values(" & fakeValue & ")"
set rs = conn.execute("select MAX(ID) from someTable")
response.write "New ID was " & rs(0)
rs.close: set rs = nothing
conn.close: set conn = nothing
%>
Now I say "should" because it is (though very obscurely) possible for two people to "cross" inserts, and
receive the wrong autonumber value back. To be frank, if there is a possibility of two or more people
simultaneously adding records, you should already be considering SQL Server (see Article #2182). However,
if you're stuck with Access and need more security that this won't happen, you can use a Recordset object
with an adOpenKeyset cursor (this is one of those rare scenarios where a Recordset object actually makes
<%
fakeValue = 5
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<conn string>"
set rs = Server.CreateObject("ADODB.Recordset")
rs.open "select [intField] from someTable where 1=0", conn, 1, 3
rs.AddNew
rs("intField") = fakeValue
rs.update
response.write "New ID was " & rs("id")
rs.close: set rs = nothing
conn.close: set conn = nothing
%>
You can also look at Manohar's excellent articles at Active Server Corner:
http://www.kamath.com/tutorials/tut004_autonum.asp
http://www.kamath.com/tutorials/tut007_identity.asp
http://support.microsoft.com/?kbid=221931
How do I deal with an apostrophe (') in a SQL statement?
This has got to be the most-often asked question in all ASP forums. The apostrophe is an illegal character in SQL
because it is interpreted as a string delimiter. To allow a ' mark to be inserted into a database, simply double-up all
<%
criteria = replace(criteria,"'","''")
%>
Inside the parentheses, for clarity, that's criteria, comma, quote, apostrophe, quote, comma, quote, apostrophe,
<%
mycrit = replace(mycrit,"'","''")
response.write("INSERT table VALUES('" & mycrit & "')<p>")
response.write("SELECT column FROM table WHERE column LIKE '%" & mycrit & "%'<p>")
%>
In JScript, you could use the Replace() method also, however it behaves differently. Each call to .Replace() only
affects the *first* instance it comes across. You can use RegExp to remind JScript to replace globally:
SELECT * is inefficient, particularly when you are only using a few of the columns in the table. This is because it
actually makes TWO queries to the database: before it runs your query, it has to query the system tables to
determine the name and datatypes of the columns. It is much more efficient to NAME your columns in the SQL
query, and this will also help in having your column names right there... so you don't have to keep flipping back and
forth between ASP page and database. In addition, this will prevent ambiguous column names in your resultset (in
the case where both are all tables in the JOIN statement have columns with the same name).
And finally, here's another reason to avoid SELECT * : Memo/Text columns, as well as columns containing BLOB
data. Microsoft recommends to put BLOB/text columns at the end of the SELECT statement, in order of appearance
in the table. This is also applicable to VARCHAR columns in SQL Server with a length greater than 255 characters.
Article #2188
Q175239
What are reserved Access, ODBC and T-SQL keywords?
The following three tables document reserved words that should not be used as names for columns, tables, aliases,
or other user-defined objects. If you are getting syntax errors of any kind, these are often due to using one of these
reserved words.
Note that 'password' causes 0x80040E14 errors when used in an Access table, though it is not referenced directly in
any of the 'official' reserved words documentation (from which the following was adapted)...
It has been proven time and time again that accessing recordset elements by index number is several times faster
than by name. A string lookup is much more expensive, resource-wide, than an integer lookup (this is true in
virtually all computer-based applications). Now, it might be difficult to keep track of name->ordinal pairs when
dealing with larger resultsets. There is definitely a trade-off for maintainability... but as long as you control the order
of the elements (by avoiding SELECT *, which you shouldn't be using anyway), you should be able to stay on top of
it. To make the transition easier, you should insert a little key for yourself when generating a resultset, such as the
following:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
set rs = conn.execute("EXEC sp_getRecords")
if not rs.eof then
do while not rs.eof
rs.movenext
loop
end if
' ... clean up etc.
%>
The only danger here is when somebody changes the order of fields in the SELECT list for the SQL statement or
stored procedure.
How do I solve 'Operation must use an updateable query' errors?
WHen using Microsoft Access, you may have come across one of the following errors:
or
or
or
or
This is almost always a permissions issue. Be sure that the MDB file is in a folder where IUSR has read/write access
(because IUSR_<machine_name> needs to create an .LDB file when modifying the database). Also be sure that the
MDB file itself isn't marked as read-only, and that you don't have the MDB file open (particularly in exclusive mode)
while trying to reach the DB from ASP. See Article #2326 and Q175168 for more information.
Another possible reason is that the column actually can't be updated, for example because of a constraint
Of course this error could also happen if you're storing your Access database on a floppy disk that has write
protection enabled (or that is full). Please don't do this; using Access is inefficient enough. You don't want to add
Joe Celko said it best: "NULLs confuse people..." (SQL For Smarties, ISBN 1558605762). McGoveran and Date add:
"NULLs...are far more trouble than they are worth and should be avoided; they display very strange and inconsistent
behavior and can be a rich source of error and confusion." (Guide to Sybase and SQL Server, ISBN 020155710X).
My sentiments exactly. Of course, I don't expect to convince you by flashing a few quotes from very reputable
authors in front of you. Let's talk for a minute about what exactly NULLs do that cause this type of reaction. The first
problem is that the definition of NULL is "unknown." So, one problem is determining whether one value is (not)
equal to another value, when one or both values are NULL. This trickles down to many problems for a database
engine and any associated applications. The following list details some of those problems:
■ they are interpreted differently depending on compatibility level and ANSI settings;
For example, let's consider two values, x and y, that are both NULL. Since the definition of NULL is
unknown, then you can't say x = y. However, with the ANSI setting ANSI_NULLs, this can be
different. When this setting is FALSE, x = y ... however, when TRUE, x <> y. Confusing, no?
■ the storage engine has to do extra processing for each row to determine if the NULLable column is in fact
NULL -- this extra bit field affects storage and indexing, and obviously has performance implications for
general queries;
■ they produce weird results when using calculations, comparisons, sorting and grouping;
■ they create problems with aggregates and joins, such as different answers for COUNT(*) vs.
COUNT(fieldname);
■ they produce unpredictable results in statistics computations, particularly WITH ROLLUP and WITH CUBE;
■ applications must add extra logic to handle inserting and retrieving results, which may or may not include
NULL values;
■ they cause unpredictable results with NOT EXISTS and NOT IN subqueries (working backwards, SQL
determines that NULL columns belong or do not belong to the result set, usually for the wrong reasons);
■ no language that supports embedded SQL has native support for NULL SQL values.
Why the difference in count results? You would *think* that the count would give a rowcount regardless of the
contents of the column. It is often recommended that "*" be avoided because it is inefficient (causing an extra call
to the syscolumns table) -- however in this case, if you allow NULL values in your fields, you run the risk of basing
your count on a field which contains NULLs... leading to an inaccurate count. So, because of your design, you're
almost forced to use an inefficient method to obtain count (whereas, if you didn't allow NULLs, a default value --
Here is another more involved example. Let's say you're running a stats program, and someone has to enter things
manually. What if they don't know the adid and/or siteid when they enter the data, and you're doing rollups against
it? If you haven't used it before, WITH ROLLUP groups by your GROUP BY fields, then adds summary rows. It adds
flags to each column when you're at a summary row, so that you can identify WHICH summary row it is. Guess what
USE pubs
CREATE TABLE fakeStats
(
id INT IDENTITY NOT NULL,
adid INT,
siteid INT,
hitcount INT
)
INSERT INTO fakeStats(adid,siteid,hitcount) VALUES(1,1,40)
INSERT INTO fakeStats(adid,siteid,hitcount) VALUES(1,1,20)
INSERT INTO fakeStats(adid,siteid,hitcount) VALUES(1,2,30)
INSERT INTO fakeStats(adid,siteid,hitcount) VALUES(1,3,40)
INSERT INTO fakeStats(adid,siteid,hitcount) VALUES(2,1,40)
INSERT INTO fakeStats(adid,siteid,hitcount) VALUES(2,2,60)
INSERT INTO fakeStats(adid,siteid,hitcount) VALUES(2,2,20)
INSERT INTO fakeStats(adid,siteid,hitcount) VALUES(2,2,30)
INSERT INTO fakeStats(adid,siteid,hitcount) VALUES(2,3,10)
You'll see that the results clearly identify the summary rows with NULL flags. Unfortunately, if you have NULLs *in
the data*, this becomes very difficult to process automatically. For example, run this now:
See the difference? Which rows are the summary rows now? Easy enough to figure out, if you have a small result
set and time to straighten out the mess. However, if you've got a system that automatically (or on-demand) creates
reports against a data warehouse, I think you can see how NULL values will put up some roadblocks.
The only datatype where NULLs are unavoidable in certain scenarios are DATETIME columns -- some dates are
clearly unknown. But for all the reasons cited above, my opinion is that a flag, non-sensical date is better. In either
case, you have to use logic to omit flagged records from result sets, but you can avoid all the other problems
So, my suggestion is to always use a DEFAULT value, and declare all columns explicitly as NOT NULL. The default in
DDL for column creation, at least in SQL Server, is NULL. One thing you should keep in mind is that, whether you
decide to use NULL or NOT NULL, is that this can change per SQL Server (and can even change between DDL
executions). So, you should always explicitly define NULL or NOT NULL for every column in your CREATE TABLE
Sadly, VBScript does not understand ADO constants, such as AdLockReadOnly, the way VB does. Luckily (or
unluckily) for the developer, the immense number of constants for use with ADO is compiled in a file called
ADOVBS.inc. When starting out with database development, everyone is told to use the constant names (as opposed
to their integer counterparts), and to include ADOVBS.inc. This recommendation is given by many people, even if
you are only using 2 or 3 of the 393 constants that are listed in ADOVBS.inc.
These 393 constants result in an overall raw read size of 14,639 bytes. Never mind the amount of memory allocated
to holding all of those variables, most of which you have no intention of using. On a small, single-user app, you
won't see a difference. However, when you have 300 people on your site at once, every single user has to load that
15kb file, and every user has 393 extra page-level variables in memory... this can really add up.
1. Write your own adovbs.inc file, with only the handful of constants you need;
3. Place the relevant constant/value pairs in a comment, and use the integer equivalent in the actual code; or
<!--METADATA
TYPE="TypeLib"
NAME="Microsoft ActiveX Data Objects 2.7 Library"
UUID="{EF53050B-882E-4776-B643-EDA472E8E3F2}"
VERSION="2.7"-->
<!--METADATA
TYPE="TypeLib"
NAME="Microsoft ActiveX Data Objects 2.5 Library"
UUID="{00000205-0000-0010-8000-00AA006D2EA4}"
VERSION="2.5"-->
If you're using a version of MDAC prior to 2.5, it's time to upgrade to the most recent version.
Note that this same argument holds true if you are using JScript and its equivalent include file, ADOJAVAS.INC.
What is wrong with 'LIKE *'?
ADO uses % for wildcard translation, not *. Therefore, if you use * as a wildcard, the query will actually be
searching for records that have a literal * in the field data. Also, you should use _ instead of ?. Access natively
supports % and _ (in addition to * and ?), so I recommend using those everywhere instead of being forced to worry
Assuming there is a unique identifier for each row, and that there is at least one record in the table, retrieving a
random record can be quite easy. This method will work in SQL Server 7.0 and above (running on Windows 2000),
<%
SELECT TOP 1 someColumn
FROM someTable
ORDER BY NEWID()
%>
If you are not running on Windows 2000, then the following code will work if your table has a unique identifier (e.g.
IDENTITY) column:
<%
SELECT TOP 1 someColumn
FROM someTable
ORDER BY RAND((1000*IDColumn)*DATEPART(millisecond, GETDATE()))
%>
Note that both of these methods also allow you to select 10 or 50 or 5000 random records, simply by altering the
TOP parameter.
Now, if you're stuck using Access or SQL Server 6.5, you may have to use some logic on the application end to deal
with this. Here are the ordered steps your code must take:
1. retrieve a recordcount, so you know how many records you need to "randomize"
2. place the ID numbers in an array, one for each "hit" in your recordcount
3. run the recordcount through a randomizer, and figure out which ID you're going to pick
4. create a select statement matching the random number to its ID in the array
Here is some code that will do this:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<conn string>"
randomize
currentRR = cLng(rnd*rCount+0.5)
ID = RRs(currentRR)
With SQL Server, this would be much faster with a stored procedure... I just wanted to provide syntax here that
demonstrates the concept, and will work on either Access or SQL Server.
How do I upsize from Access to SQL Server?
Access is not truly meant for concurrent use. Microsoft made it easy to use Access over the web so that small
companies who couldn't afford (or were intimidated by) SQL Server would have some database platform to use for
their LANs (great way to sell more copies of the higher-end Office package, as well). Unfortunately for Internet
developers, Access does not handle more than a handful of simultaneous users very well at all. So if you've got a
family site that only your friends and such are looking at, Access is probably fine. For anything more than that, you'll
Here is a KB article, two tools and a white paper that will help you upsize your Access 97 or 2000 database to SQL
http://office.microsoft.com/downloads/9798/aut97.aspx
http://office.microsoft.com/downloads/2000/Accsql.aspx
If you know you are converting from Access 2000 to SQL Server 2000, you may want to pick up the SQL Server
2000 Resource Kit. It has an updated version of the upsizing wizard that fixes some problems, and makes Access
2000 projects (.adp) much more fluent in the changes to SQL Server 2000.
There are also some 3rd party products out there that help the migration. One is SSW Upsizing Pro and another is
DataConvert.
Finally, here are some Knowledge Base articles you may want to glance over before proceeding:
Now that you see how much work is involved, and that there are plenty of potential issues, wouldn't it make sense
to just prototype your application using SQL Server in the first place?
What do I need to know about the differences between Access and SQL Server?
This article will try to explain some of the differences between Access and SQL Server. It is not an exhaustive list,
and in no means should be considered an ultimate authority. If you have anything to add or correct, please let us
know...
DATA TYPES
Here is a list of data types in each environment, and how they are different. Some datatypes from SQL Server were
In Access, you could use integers or TRUE/FALSE keywords to determine the value of the field. In SQL
Server, and especially during migration, you should use integer values only. So here are some sample
queries; note that the SQL Server queries will work Access as well.
-- DETERMINING TRUE
-- Access:
[...] WHERE ynColumn = TRUE
[...] WHERE ynColumn = -1
-- SQL Server:
[...] WHERE ynColumn <> 0
------------------------------
-- DETERMINING FALSE
-- Access:
[...] WHERE ynColumn = FALSE
[...] WHERE ynColumn = 0
-- SQL Server:
[...] WHERE ynColumn = 0
You will no longer be able to use cute VBA functions like FORMAT to add dollar signs, thousand separators,
and decimal places to your numbers. In fact, in Access, some of this data is actually stored along with the
value. With SQL Server, this extraneous data is not stored, reducing disk space and making calculations
more efficient. While you can apply this formatting in SQL Server, as explained in Article #2188, it's messy --
and better handled, IMHO, by the client application. In ASP, you can use built-in functions like
Like Currency, Access uses internal formatting to make the values stored in the application clickable. This is
partly because Access is a client application, and this feature makes it easier to use. However, when you're
not physically in the application, you may not want the URL to be clickable (it may just be a display value, or
you may want to wrap alternate text -- or an image -- inside the <a href> tag). In SQL Server, use a
VARCHAR column (likely 1024 or greater, depending on the need) and apply <a href> tags to it in the client
application. Don't expect the database to maintain HTML for you... this only increases storage size, and hurts
When passing dates into Access from ASP or an application, you use pound signs (#) for surrounding dates.
SQL Server, on the other hand, uses apostrophes ('). So the following query conversion would be required:
-- Access:
[...] WHERE dtColumn >= #11/05/2001#
-- SQL Server:
[...] WHERE dtColumn >= '11/05/2001'
In addition, Access allows you to store date and time alone. SQL Server does not allow this (see Article
#2206for more info). To see if a date equals 11/05/2001 in SQL Server, you would have to convert the
stored value (which includes time) to a 10-character date. Here is how a typical query would have to
change:
-- Access:
[...] WHERE dtColumn = #11/05/2001#
-- SQL Server:
[...] WHERE CONVERT(CHAR(10), dtColumn, 101) = '11/05/2001'
If you want to retrieve the current date and time, the syntax is slightly different:
-- Access:
SELECT Date() & " " & Time()
-- SQL Server:
SELECT GETDATE()
SELECT CURRENT_TIMESTAMP
-- Access:
SELECT DateAdd("d",1,date())
-- SQL Server:
SELECT CONVERT(CHAR(10), DATEADD(day, 1, GETDATE()), 101)
To get the date and time 24 hours from now:
-- Access:
SELECT cstr(DateAdd("d",1,date())) & " " & cstr(time())
-- SQL Server:
SELECT DATEADD(day, 1, GETDATE())
-- Access:
SELECT DateAdd("d",1-day(date()),date())
-- SQL Server:
SELECT CONVERT(CHAR(10),GETDATE()+1-DAY(GETDATE()),101)
-- Access:
SELECT DAY(DATEADD("m", 1, 1-DAY(date()) & date())-1)
-- SQL Server:
SELECT DAY(DATEADD(MONTH, 1, 1-DAY(GETDATE())+GETDATE())-1)
-- Access:
SELECT "Pick a number between 1 and 1000"
-- SQL Server:
SELECT DATEPART(millisecond, GETDATE())
-- SQL Server:
SELECT DATENAME(WEEKDAY, GETDATE())
Not much difference here, except for how you define the column in DDL (CREATE TABLE) statements:
-- Access:
CREATE TABLE tablename (id AUTOINCREMENT)
-- SQL Server:
CREATE TABLE tablename (id INT IDENTITY)
Handling Strings
There are many changes with string handling you will have to take into account when moving from Access to
SQL Server. For one, you can no longer use double-quotes (") as string delimiters and ampersands (&) for
string concatenation. So, a query to build a string would have to change as follows:
-- Access:
SELECT "Foo-" & barColumn FROM TABLE
-- SQL Server:
SELECT 'Foo-' + barColumn FROM TABLE
(Yes, you can enable double-quote characters as string delimiters, but this requires enabling
QUOTED_IDENTIFIERS at each batch, which impacts many other things and is not guaranteed to be forward
compatible.)
Built-in CHR() constants in Access change slightly in SQL Server. The CHR() function is now spelled slightly
differently. So, to return a carriage return + linefeed pair:
-- Access:
SELECT CHR(13) & CHR(10)
-- SQL Server:
SELECT CHAR(13) + CHAR(10)
This one is confusing for many people because the CHAR keyword doubles as a function and a datatype
definition.
String Functions
There are many VBA-based functions in Access which are used to manipulate strings. Some of these
functions are still supported in SQL Server, and aside from quotes and concatenation, code will port without
difficulty. Others will take a bit more work. Here is a table of the functions, and they will be followed by
examples. Some functions are not supported on TEXT columns; these differences are described in Article
#2061.
This function converts NUMERIC data that may be stored in string format to INTEGER format for comparison
and computation. Remember that SQL Server is much more strongly typed than VBA in Access, so you may
-- Access:
SELECT CINT(column)
-- SQL Server:
SELECT CAST(column AS INT)
This function returns an integer representing the character where the search expression is found within the
-- SQL Server:
SELECT CHARINDEX('goes','franky goes to hollywood')
ISDATE(data)
This function returns 1 if the supplied parameter is a valid date, and 0 if it is not. Aside from delimiters, the
syntax is identical.
-- Access:
SELECT ISDATE(#12/01/2001#)
-- SQL Server:
SELECT ISDATE('12/01/2001')
ISNULL(data)
This function works a bit differently in the two products. In Access, it returns 1 if the supplied parameter is
NULL, and 0 if it is not. In SQL Server, there are two parameters, and the function works more like a CASE
statement. The first parameter is the data you are checking; the second is what you want returned IF the
first parameter is NULL (many applications outside the database haven't been designed to deal with NULL
values very gracefully). The following example will return a 1 or 0 to Access, depending on whether 'column'
is NULL or not; the code in SQL Server will return the column's value if it is not NULL, and will return 1 if it is
NULL. The second parameter usually matches the datatype of the column you are checking.
-- Access:
SELECT ISNULL(column)
-- SQL Server:
SELECT ISNULL(column,1)
ISNUMERIC(data)
This function returns 1 if the supplied parameter is numeric, and 0 if it is not. The syntax is identical.
SELECT ISNUMERIC(column)
LEFT(data, n)
This function returns the leftmost n characters of data. The syntax is identical.
SELECT LEFT(column,5)
LEN(data)
This function returns the number of characters in data. The syntax is identical.
SELECT LEN(column)
-- Access:
SELECT LCASE(column)
-- SQL Server:
SELECT LOWER(column)
LTRIM(data)
This function removes white space from the left of data. The syntax is identical.
SELECT LTRIM(column)
This function scans through data, replacing all instances of expression1 with expression2.
This function returns the rightmost n characters of data. The syntax is identical.
SELECT RIGHT(column,8)
RTRIM(data)
This function removes white space from the right of data. The syntax is identical.
SELECT RTRIM(column)
-- Access:
SELECT CSTR(column)
-- SQL Server:
-- if column is NUMERIC:
SELECT STR(column)
-- if column is not NUMERIC:
SELECT CAST(column AS VARCHAR(n))
-- Access:
SELECT MID("franky goes to hollywood",1,6)
-- SQL Server:
SELECT SUBSTRING('franky goes to hollywood',1,6)
-- SQL Server:
SELECT UPPER(column)
StrConv
This function converts a string into 'proper' case (but does not deal with names like O'Hallaran or
vanDerNeuts). There is no direct equivalent for StrConv in SQL Server, but you can do it per word manually:
-- Access:
SELECT StrConv("aaron bertrand",3)
-- SQL Server:
SELECT LEFT(UPPER('aaron'),1)
+ LOWER(RIGHT('aaron',LEN('aaron')-1))
+ ' '
+ LEFT(UPPER('bertrand'),1)
+ LOWER(RIGHT('bertrand',LEN('bertrand')-1))
There is a thread stored at Google dealing with proper casing an entire block of text; you could likely
TRIM(data)
This function combines both LTRIM() and LTRIM(); there is no equivalent in SQL Server. To mimic the
-- Access:
SELECT TRIM(column)
SELECT LTRIM(RTRIM(column))
-- SQL Server:
SELECT LTRIM(RTRIM(column))
String Sorting
Access and SQL Server have different priorities on string sorting. These differences revolve mostly around
special characters like underscores and apostrophes. These might not change the way your application
works, but you should be aware of the differences. Let's take this fictional example (SQL Server):
Now, insert identical data into a similar table in Access 2000, and compare the SELECT results:
SQL Server Access 2K
---------- ---------
andy andy
bob bob
'andy _andy
'bob _bob
-andy =andy
-bob =bob
=andy andy
=bob 'andy
_andy -andy
_bob andy-bob
andy bob
andy-bob 'bob
bob -bob
bob-andy bob-andy
Notice the inconsistencies - Access (like Windows) treats underscore (_) as the highest non-alphanumeric
character. Also, it ignores apostrophe (') and hyphen (-) in sorting. You can see the other slight differences
in sorting this otherwise identical list. At least they agree on which names are first and last... if only all of our
queries used TOP 1! Add on top of this that both database engines' concepts of sort order are sensitive to
changes in the underlying operating system's regional settings. SQL Server is also variable in its server-level
(and in SQL Server 2000, table- and column-level) collation options. So, depending on all of these variables,
your basic queries that sort on a text/char/varchar column will potentially start working differently upon
migration.
NULL Comparisons
SQL Server handles NULL values differently. Access assumes NULL = NULL, so two rows where a column is
<NULL> would match a JOIN clause comparing the two. By default, SQL Server treats NULLs correctly as
UNKNOWN, so that, depending on the settings within SQL Server, it cannot state that NULL = NULL. If you
are trying to determine whether a field contains a NULL value, the following query change should be made:
-- Access:
[...] WHERE column = NULL
[...] WHERE column <> NULL
-- SQL Server:
[...] WHERE column IS NULL
[...] WHERE column IS NOT NULL
If you set ANSI_NULLS OFF and are trying to compare two columns, they won't equate. A column that
contains a NULL will equate with an expression that yields NULL, as will two expressions that yield NULL. But
two columns that contain NULL will never be considered equal, regardless of ANSI_NULLS settings or the
ANSI standards. As a workaround, use the following comparison to determine that two fields are equal AND
both contain NULL (without the extra AND condition, these two would also evaluate as equal if they both
Yes, it's not pretty. For more information on how SQL Server handles NULLs (and why you should avoid
There are possibly dozens of other slight syntax changes that may have to be made when moving from Access to
IIF() is a handy inline switch comparison, which returns one result if the expression is true, and another
result if the expression is false. IIF() is a VBA function, and as such, is not available in SQL Server.
Thankfully, there is a more powerful function in SQL Server, called CASE. It operates much like SELECT CASE
-- SQL Server:
SELECT alias = CASE WHEN Column<>0 THEN 'Yes' Else 'No' END
FROM table
DISTINCTROW
OBJECTS
When creating tables and other objects, keep the following limitations in mind:
■ Access uses MAKE TABLE, while both platforms support CREATE TABLE;
■ SQL Server 6.5 object names were limited to 30 characters and no spaces; and,
STORED QUERIES
Stored queries in Access are a way to store query information so that you don't have to type out ad hoc SQL all the
time (and update it throughout your interface everywhere you make a similar query). Being a non-GUI guy, the
easiest way I've found to create a stored query in Access is to go to Queries, open "Create query in Design View",
Be careful not to use any reserved words, like [name], as parameter names, or to give your parameters the SAME
name as the column -- this can easily change the meaning of the query.
Once you have the same schema within SQL Server, when moving to stored procedures, the basic difference you'll
You can create this stored procedure using this code through QUery Analyzer, or you can go into the Enterprise
Manager GUI, open the database, open the Stored Procedures viewpane, right-click within that pane and choose New
> Stored Procedure. Paste the above code (or a query that might make a bit more sense given *your* schema), click
Check Syntax, and if it all works, click Apply/OK. Don't forget to set permissions!
Now in both cases, you can call this code from ASP as follows:
<%
productID = 5
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
set rs = conn.execute("EXEC MyQuery " & productID)
do while not rs.eof
' process recordset here
' ...
%>
SECURITY
Access is limited to security in terms of username / password on the database. It also is subject to Windows security
on the file itself (as well as the folder it resides in). Typically, ASP applications must allow the anonymous Internet
guest account (IUSR_<machine_Name>) to have read / write permissions on file and folder. Username / password
SQL Server has two authentication modes, and neither are much like Access security at all. You can use Windows
Authentication, which allows you direct access to domain Users and Groups from within the interface. You can also
use Mixed Mode, which allows SQL Server to maintain usernames and passwords (thereby negating the need for a
Once you have determined an authentication mode, users have three different levels of access into the database:
login (at the server level), user (at the database level), and object permissions within each database (for tables,
views, stored procedures, etc). Just to add a layer of complexity, SQL Server makes it easy to "clone" users by
defining server-wide roles, and adding users to that role. This is much like a Group in a Windows domain; in SQL
Server, you can use the built-in definitions (and customize them), or create your own. Alterations to a role's
Microsoft has a thorough whitepaper you should skim through before jumping into SQL Server. If you're going to
deploy your own SQL Server box (as opposed to leasing a dedicated SQL Server, or a portion of one), by all means
Article #2182 has a list of tools and tutorials that will aid in the migration process. Also be sure to read Microsoft's
migration whitepaper for some helpful info from the vendors themselves. Finally, if you're into books, APress has a
Here are two ways to retrieve table names from a database. The first uses ADOX, and the second uses a system
stored procedure called sp_tables (which is therefore SQL Server only). In addition, check out sp_MSForEachTable
... this is an *undocumented* system procedure which does an enumeration of all tables in a database.
Here is the code that uses ADOX. This was tested with MDAC 2.5, and will work with either MS Access or SQL
Server.
<%
dbname = "databasename"
And here is the code that uses a stored procedure (note that <uid> needs to have at least 'datareader' access to
<dbname>).
<%
dbname = "databasename"
ConnStr = "provider=SQLOLEDB;network=DBMSSOCN;"
ConnStr = ConnStr & "uid=<uid>;pwd=<pwd>;server="
ConnStr = ConnStr & "<x.x.x.x>;database=" & dbname
Finally, for SQL Server, if you have access to Query Analyzer (and just want a table list, not necessarily for code),
EXEC sp_tables
-- or
Here are three ways to retrieve column names from a table. The first uses ADOX, the second uses simple properties,
the third uses a system stored procedure called sp_help, and the fourth uses a system stored procedure called
sp_columns. The last two are therefore SQL Server only, and should not be relied upon in production code... future
Here is the code for retrieving field names using ADOX, which will work with both MS Access and SQL Server:
<%
dbname = "databasename"
tablename = "tablename"
dim columnTypes(203)
columnTypes(2) = "SmallInt"
columnTypes(3) = "Integer"
columnTypes(6) = "Currency"
columnTypes(11) = "Boolean"
columnTypes(14) = "Decimal"
columnTypes(16) = "TinyInt"
columnTypes(129) = "Char"
columnTypes(131) = "Numeric"
columnTypes(135) = "DateTime"
columnTypes(200) = "VarChar"
columnTypes(203) = "Text"
set adoxConn = Server.CreateObject("ADOX.Catalog")
set adodbConn = Server.CreateObject("ADODB.Connection")
adodbConn.open ConnStr
adoxConn.activeConnection = adodbConn
set table = adoxConn.Tables(tablename)
for each col in table.columns
response.write col.name & " [" & columnTypes(col.type)
if col.type = 129 or col.type=200 then
' definedSize only works in SQL Server
Response.write " (" & col.definedSize & ")"
end if
Response.Write "]<br>"
next
set table = nothing
adodbConn.close: set adodbConn = nothing
set adoxConn = nothing
%>
<%
dbname = "databasename"
tablename = "tablename"
dim columnTypes(203)
columnTypes(2) = "SmallInt"
columnTypes(3) = "Integer"
columnTypes(6) = "Currency"
columnTypes(11) = "Boolean"
columnTypes(14) = "Decimal"
columnTypes(16) = "TinyInt"
columnTypes(129) = "Char"
columnTypes(131) = "Numeric"
columnTypes(135) = "DateTime"
columnTypes(200) = "VarChar"
columnTypes(203) = "Text"
If you are using SQL Server, you can use sp_help, which returns 6 recordsets when there are no constraints, and 7
recordsets when there are constraints. You might have a query like this:
<%
dbname = "databasename"
tablename = "tablename"
ConnStr = "provider=SQLOLEDB;network=DBMSSOCN;"
ConnStr = ConnStr & "uid=<uid>;pwd=<pwd>;server="
ConnStr = ConnStr & "<x.x.x.x>;database=" & dbname
RowGuidCol
Data_located_on_filegroup
Finally, here is the code for using sp_columns. Note that <uid> needs to have at least 'datareader' access to
<dbname>.
<%
dbname = "databasename"
tablename = "tablename"
ConnStr = "provider=SQLOLEDB;network=DBMSSOCN;"
ConnStr = ConnStr & "uid=<uid>;pwd=<pwd>;server="
ConnStr = ConnStr & "<x.x.x.x>;database=" & dbname
If you are only interested in SQL Server column names, you can execute one of the following queries:
SELECT name
FROM syscolumns
WHERE [id] = OBJECT_ID('tablename')
SELECT column_name
FROM information_schema.columns
WHERE table_name='tablename'
The latter is preferred, since it is a view that is less likely to be adversely affected by changes in the underlying
system tables.
Why do I get General error Unable to open registry key 'DriverId'?
The following error can happen when the Internet Guest Account (IUSR_<machine>) does not have sufficient
First thing you should do, is stop using ODBC drivers. If you use the JET OLE DB drivers instead (see Article #2126
for a sample connection string), at the very least you'll get a more meaningful error message.
Back to the permissions issue. This can happen right from the start, because proper permissions were never applied.
However, it can also happen when a compact and repair operation against the MDB file resets permissions. So, if
your database is getting corrupted often enough, you might find that compacting and repairing the database file also
leaves you with this error on your pages. (Of course, one way around this is to use a real database, if you can. See
If applying proper permissions doesn't solve the problem, or if they are already sufficient, then you may want to try
using an OLE-DB connection string instead of an ODBC and/or DSN connection (see Article #2126 for an example of
a DSN-less JET connection string). Also make sure to apply the most recent MDAC update
(http://www.microsoft.com/data/) and the most recent JET drivers (see Article #2342).
Q315456 FP: Error: Unable to Open Jet Temporary Key When You Attempt to Connect to Database
Q295297 PRB: Err Msg: 0x80004005: General Error Unable to Open Registry Key
Why do I get 80040E10 errors?
When creating SQL statements in ASP, you might encounter one of the following errors:
or
or
3. You tried to insert the wrong datatype (e.g. surrounded a numeric value with quotes).
To troubleshoot this, response.write your SQL statement... make sure there is data for all params you are passing,
and compare field names directly with those in the table. Also copy this SQL statement and execute it directly in the
database, if the error message and the response.write statement aren't already giving you enough information.
How do I know which version of MDAC I'm running?
The simplest way is to use the version property of the ADODB.Connection object, such as:
<%
set conn = server.createobject("ADODB.Connection")
response.write conn.version
set conn = nothing
%>
Another way is to go to Start, Run... and type regedit, then look at the following key:
HKEY_LOCAL_MACHINE
\Software
\Microsoft
\DataAccess
\Version
You can also use the component checker, available halfway down this page, although it seems this tool hasn't been
Yet another way is to find msado15.dll (in %SYSTEM32%\DLLCache\), right-click it and hit properties, and look
on the version tab. This should produce the exact same result as the registry scan above.
How do I hide system tables in SQL Server's Enterprise Manager?
Many people don't like the clutter provided so graciously by the existence of all the system tables, objects and
stored procedures in SQL Server's user interface. To get rid of the system objects, just right-click the server in
question (in MMC), choose "Edit SQL Server Registration properties...", and uncheck "Show system databases and
system objects" ... hit OK and that clutter should disappear, making direct manipulation of your data much easier.
This will work in both SQL Server 7.0 and SQL Server 2000.
How do I connect to SQL Server on a port other than 1433?
Sometimes an added measure of security can be achieved by using ports other than the defaults for server software.
SQL Server allows you to specify which port you want it to run on; the default is 1433. Provided you can access your
SQL Server through TCP/IP, the following connection string should help you connect to a different port (this example
<%
cst = "provider=SQLOLEDB;network=DBMSSOCN;uid=<uid>;"
cst = cst & "pwd=<pwd>;server=127.0.0.1,1510;database=pubs"
set conn = Server.CreateObject("Adodb.COnnection")
conn.open cst
...
Notice that the IP address and port number are separated by a comma, and that TCP/IP is 'forced' by adding
network=DBMSSOCN.
Why does ASP give me ActiveX errors when connecting to a database?
You may see the following errors after installing / re-installing / removing Access 97, Access 2000, Visual Studio, or
or
The problem is that operating systems prior to Windows 2000 had no way of preventing external applications from
removing or replacing critical system DLLs, like those installed with MDAC (Microsoft Data Access Components). To
fix this error, simply (re-)install the latest version of MDAC (http://www.microsoft.com/data/).
How do I prevent my ASP pages from waiting for backend activity?
If you are trying to run an executable or other backend process, see Adrian Forbes' article:
If you are trying to run long-running database tasks, then the following is designed for a very specific scenario. Your
1. initiate a long-running command that may cause a browser to time out; and,
This means it should be a page which triggers an event in the database which is NOT a recordset or other results-
obtaining operation, and that the user isn't expecting direct feedback of any kind to let them know that the process
has finished.
<%
set conn = server.createobject("ADODB.Connection")
conn.open "<connection string>"
conn.execute "<long-running command>",,&H00000010
response.write "The command is still running, but I'm not waiting!"
...
%>
Now wait at least 10 seconds, go into the dt table manually, and check out the differences in the dt column (and
One word of warning: errors get swallowed up by the provider when using asynchronous methods, so you'll want to
Both of these examples return the name and body of all stored procedures (excluding built-in procs with dt_ prefix)
that contain the specified string; in addition, they both highlight the searched string in the body of the procedure
<%
set conn = Server.CreateObject("ADODB.Connection")
connstr = "provider=SQLOLEDB;server=<ip>;database=<db>;uid=<UID>;pwd=<PWD>"
conn.Open connstr
set rs = conn.execute(sql)
if not rs.eof then
do while not rs.eof
s = hW(str, server.htmlEncode(rs(1)))
s = replace(s, vbTab, " ")
s = replace(s, vbCrLf, "<br>")
response.write "<b>" & rs(0) & "</b><p>" & s & "<hr>"
rs.movenext
loop
else
response.write "No procedures found."
end if
If you are opposed to using INFORMATION_SCHEMA for whatever reason, then here is a simple example that uses
<%
set catalog = Server.CreateObject("ADOX.Catalog")
set conn = Server.CreateObject("ADODB.Connection")
connstr = "provider=SQLOLEDB;server=<ip>;database=<db>;uid=<UID>;pwd=<PWD>"
catalog.ActiveConnection = connstr
conn.Open connstr
if instr(lcase(sptext),lcase(str))>0 then
' match!
disptxt = replace(disptxt, vbTab, " ")
response.write "<b>" & spname & "</b><p>" & hW(str, disptxt) & "<hr>"
end if
end if
Next
conn.Close
set conn = Nothing
Set catalog = Nothing
This seems to cause a lot of problems. In your environment, you may find that Access or SQL Server is set up for UK
dates, and Windows is set up for US dates. Or vice-versa. Or some other weird combination, like SQL Server is set
up for UK dates, the SQL machine's Windows is set up for US dates, but the current logged on user has a Danish
locale. Combine that with an ASP page on a Windows machine in the next rack, set up for a German charset, and
you can see there'll be some big trouble. Take a look at the dates formatted in this example:
<%
ReturnDateTime 1033, "English (US)"
ReturnDateTime 2057, "English (UK)"
ReturnDateTime 3081, "English (Australia)"
ReturnDateTime 1031, "German"
English (US)
Long date: Monday, February 25, 2002
Short date: 2/25/2002
English (UK)
Long date: 25 February 2002
Short date: 25/02/2002
English (Australia)
Long date: Monday, 25 February 2002
Short date: 25/02/2002
German
Long date: Montag, 25. Februar 2002
Short date: 25.02.2002
So, to display dates to the user in the desired LCID is pretty trivial. Now, to see where the problems really come in
with the short date format, try this little script (assuming SQL Server):
<%
set conn = server.createobject("ADODB.Connection")
conn.open "<connectionString>"
Call SetUSLocale()
rs.close
set rs = nothing
conn.close
set conn = nothing
%>
Assuming your SQL Server and Windows machines are set up to accept dates in US format (mm/dd/yyyy), you
The UK English insert statement failed because SQL Server doesn't know what month 25 is. In most VB / VBA
environments, such as Access, it will implicitly convert 25/02/2002 to 02/25/2002, figuring that the date was
entered incorrectly. SQL Server is not so forgiving, and for good reason -- there is ambiguity implied when a
database can make decisions for you. How does it know when to draw the line? Should it contemplate changing Nov.
To help eliminate differences in international formatting of dates and times, it is recommended to always use
YYYYMMDD hh:mm:ss format. Unfortunately, ASP/VBScript doesn't have this format built in, so we have to
accommodate a bit. Here is a function I wrote to format a correct database-style date in standard ISO format,
<%
Function dbDate(dt)
dbDate = year(dt) & left("00",2-len(month(dt))) &_
month(dt) & left("00",2-len(day(dt))) & day(dt) &_
" " & formatdatetime(dt,4)
End Function
%>
So, taking the above script, we can correct for differences in the LCID by passing the dates through dbDate() first
(note that the only changes in the script are marked in bold):
<%
Function dbDate(dt)
dbDate = year(dt) & left("00",2-len(month(dt))) &_
month(dt) & left("00",2-len(day(dt))) & day(dt) &_
" " & formatdatetime(dt,4)
End Function
Call SetUSLocale()
rs.close
set rs = nothing
conn.close
set conn = nothing
%>
UK English passed.
As alternatives to session.LCID, you can try maintaining an LCID in a specific session variable and setting it on a
For more information on date / time formatting in ASP, please see the following KB articles:
Q218964
PRB: VBScript Date and Time Formats Change with Logged on User
Q264063
Q306044
Finally, an alternative is to alter the default regional settings for the server, however this may impact other sites /
1. Log on to the server (either physically or through terminal services) and set the correct locale and date
format
3. Open the exported file in Notepad, and replace "HKEY_CURRENT_USER" with "HKEY_USERS\.DEFAULT"
Typically, the following error will happen if you try and set invalid cursor or lock properties on a recordset object.
This is often because you used the VB "friendly" names for the values (such as adLockReadOnly), instead of the
integer constants (which are the only values understood by the engine), without including ADOVBS.INC. So, a quick
After that, you should investigate two things. One is whether you should even be using a recordset object; more
often than not, the ADODB.Recordset object is unnecessary (see Article #2191 for more info). The second is
whether it makes sense to include ADOVBS.INC / ADOJAVAS.INC, or to simply define the few constants you actually
plan on using (take a look at Article #2112 for reasons to avoid using this massive file).
How do I prevent NULLs in my database from mucking up my HTML?
Many people have come across the issue where they're using a recordset to populate a table, and someone goofed
up and stored <NULL> values in the database, so it wrecks the formatting of the table.
First, you should consider not allowing NULLs into your database in the first place; see Article #2073 for more info.
Now, let's say you can't get around using NULLs (due to pointy-haird boss syndrome, or whatever else)... you can
Here is an example of using VBScript to get rid of NULL values *after* they've come out of the database, but before
<%
' ...
do while not rs.eof
cCol = rs("column_which_may_contain_nulls")
if len(cCol)=0 then cCol = " "
response.write "<td>" & cCol & "</td>"
rs.movenext
loop
' ...
%>
Manas Tungare adds that there is an even quicker solution to this. By appending a blank string to the end of the
Here is an example of using SQL (both in Access and in SQL Server) to replace these NULL values for you, taking
care of the problem at the data level (because it *is* a data problem):
/* For Access: */
In any case, I still strongly suggest you avoid NULLs in your database. But if you absolutely must have them, I hope
that these solutions help alleviate some of the problems they cause.
How do I enable connection pooling?
Connection pooling is enabled by default for SQL Server and Oracle, in IIS 4.0 and above. You shouldn't need to
configure anything to take advantage of connection pooling for these databases. You can check the status of your
current settings by going to the "ODBC Data Sources" Control Panel applet (this is under Administrative Tools in
Windows 2000 and later). There is a tab called connection pooling, which is only relevant if you are using ODBC to
connect to your database(s) (yet another reason to use OLEDB and AVOID USING A DSN).
If you are using Access, and absolutely must use a DSN (e.g. pointy-haired boss syndrome), and you want to enable
connection pooling: double-click on the entry for 'Microsoft Access Driver (*.mdb)' and change 'Don't pool
Please test the performance of your app with and without this setting. I'd be interested in knowing if this increased
or decreased connection and query times for ODBC connections to Access database.
Microsoft states:
"To make the best use of connection pooling, explicitly close database connections as soon as possible. By default, a
connection terminates after your script finishes execution. However, by explicitly closing a connection in your script
after it is no longer needed, you reduce demand on the database server and make the connection available to other
users."
Why do I get 'Syntax Error in INSERT INTO Statement' with Access?
Aside from an actual syntax error in your SQL statement, such as a misplaced quote, comma or bracket, the most
common cause for this error is by using a reserved word as an alias, column name or table name. For a list of
Article #2080
There is one word that keeps cropping up as the source for this error -- at least in Access -- though it is not
If you cannot change the name of the column that is using this reserved word, wrap it in [] brackets. E.g.:
becomes
Next, check your delimiters. You might get this if you try to delimit a numeric field with an apostrophe or quote, or a
date column with the wrong delimiter, or leave a string without a delimiter.
<%
conn.execute(sqlString)
%>
to:
<%
response.write(sqlString)
%>
That should point out the problem. If it doesn't, post your code, the resulting SQL, and your data structure to
Many people who get errors in SQL statements post their ASP code and the error, and say "what's wrong with this
SQL statement?" It's hard for anyone to tell when you've got a SQL statement that hasn't been built yet, for
example:
<%
sql = "SELECT field FROM table WHERE ID=" & someVarname
set rs = conn.execute(sql)
%>
To debug this properly, when you get an error, you should add the following two lines:
<%
sql = "SELECT field FROM table WHERE ID=" & someVarname
response.write sql
response.end
set rs = conn.execute(sql)
%>
Now, you can copy the SQL statement from the browser and post it directly in Access or Query Analyzer, and see
why it's not working. Sometimes it will be obvious even before that point, e.g. someVarname is empty or the wrong
data type. Sometimes it will be missing (or erroneous) quotes (e.g. quotes around a numeric value, or # characters
around a SQL date). Sometimes it will be a reserved word problem (e.g. you named your field DATE).
Before posting SQL errors to the newsgroups, please assemble the following information:
■ DDL (in the form of CREATE TABLE statements) - this is so everyone can understand exactly what data types
your table consists of, and enables them to easily re-create your table in their own environment.
■ Sample data (in the form of INSERT statements) - this is so everyone can understand what data you put into
your table, and can populate their local copy for testing purposes.
■ The *actual* SQL statement (not the one made up with variables - if you can't get that working to begin
ask people to fix it. This will almost always result in a "we need more info" type of post.
How do I create a database from ASP?
SQL Server
Assuming you have sufficient permissions to the SQL Server box, you can simply say:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string with UID and NO database>"
conn.execute("CREATE DATABASE dummy")
' create tables, etc...
%>
The owner of the database will be the user specified in UID. To change the database owner, use sp_changedbowner,
as follows:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
conn.execute("USE dummy; EXEC sp_changedbowner 'username'")
' ...
%>
Now, if you have user-defined objects, put them in the model database. This is the database that is used to be a
Access
Here is code that will create an empty Access database from ASP:
<%
newDB = "c:\inetpub\wwwroot\databases\new.mdb"
Set cat = Server.CreateObject("ADOX.Catalog")
cat.Create "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & newDB
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & newDB
' create tables, etc...
%>
Another way to do it (if you're creating cookie-cutter databases) is to generate a template with table structure and
<%
targetDB = "c:\inetpub\wwwroot\databases\new.mdb"
sourceDB = "c:\inetpub\wwwroot\databases\template.mdb"
set fso = Server.CreateObject("Scripting.FileSystemObject")
fso.CopyFile sourceDB, targetDB, true
set fso = nothing
set conn = Server.CreateObject("ADODB.Connection")
conn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & targetDB
' ...
%>
(Of course, you should check that the targetDB name is not already taken, or you could wipe out someone's data.)
Here is another set of samples that uses a bit more rigorous check for existing objects, and also creates a table in
This version allows you to create a database in Access. Note that there is no error checking.
<%
db = Server.MapPath("/sampleDatabase.mdb")
tableName = "sampleTable"
'otherwise:
'dbCreate = "Driver={Microsoft Access Driver (*.mdb)};DBQ=" & db
cat.activeConnection = conn
tableExists = false
And here is a version for SQL Server (note that you must connect to SQL Server with a user who has sufficient
privileges to create a new database). While I don't generally advocate the use of dynamic SQL, the following
procedure was concocted simply to demonstrate what you can do (and to avoid having to generate this SQL string
END
conn.execute(sql)
conn.close
set conn = nothing
%>
Why does Access give me 'unspecified error' messages?
Aside from the improper use of MEMO fields (see Article #2188), this may be caused by the mode in which Access is
opened. Unless otherwise specified, IIS opens Access databases with adModeUnknown... which has proven to cause
random problems in certain configurations. You can overcome this by setting the mode to adModeReadWrite
<%
cst = "Driver={Microsoft Access Driver (*.mdb)};DBQ="
cst = cst & server.mappath("/<pathtofile.mdb>")
set conn = Server.CreateObject("ADODB.Connection")
conn.mode = 3 ' adModeReadWrite
conn.open cst
%>
(If you know you have Jet 4.0 installed, you can use the following slightly more efficient method.)
<%
cst = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source="
cst = cst & server.mappath("/<pathtofile.mdb>")
set conn = Server.CreateObject("ADODB.Connection")
conn.mode = 3 ' adModeReadWrite
conn.open cst
%>
How do I access MIN, MAX, SUM, COUNT values from SQL statements?
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
set rs = conn.execute("SELECT COUNT(column) FROM table")
response.write rs("column")
%>
ADODB.Recordset (0x800A0CC1)
Item cannot be found in the collection corresponding to the requested name or ordinal.
This is because they weren't looking for "column" from the recordset, they were looking for an expression that
represents the COUNT of "column" in the table. There are at least three ways to obtain values of aggregate functions
from a recordset.
The first is to simply use ordinal numbers instead of the name of the column. This requires no modification to the
existing SQL statement, and has the added bonus of being slightly faster (though for most users I recognize this will
be negligible, and can make the ASP code harder to maintain if the SQL statement will change in the future).
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
set rs = conn.execute("SELECT COUNT(column) FROM table")
response.write rs(0)
%>
The second two solutions involving adjusting the SQL statement to use an alias for the expression:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
Chances are, you want to use TCP/IP, not Named Pipes. Make sure network library is configured to use TCP/IP by
default. If you have SQL Server installed, you can do this through the Client Network Utility.
Named pipes can sometimes be stubborn, even after you have changed your network library. This could be because
the remote serve is overriding your local settings. To alleviate this, add the following line to your existing connection
string:
<%
cst = cst & "Network=DBMSSOCN;"
%>
Can I compact / repair / synchronize an Access database from ASP code?
Yes, you can compact and repair an Access database using ASP. Thanks to Craig Starnes for suggesting this article.
I created a simple table, with an Autonumber column and a text(50) column. I created a macro that would insert
rows with random characters into the text columns. I set the repeat on the macro to 30,000 and ran it. While I was
waiting for Access to struggle through the task and release my CPU so I could do other things on the box, I went
and brushed my teeth. When I came back, it wasn't finished yet. So I read Tolkien's trilogy. When it finally
completed the task, I observed the size of the MDB file, and it was around 1.3 MB. I then wrote a query to delete
roughly half the records, interspersed (e.g. I did not delete the first or last 15,000 rows - I wanted to see what
compacting would do to a more realistically adjusted Autonumber column). I closed the database, and lo and behold,
the file was still 1.3 MB (even though I had just cut the actual storage size in half).
So, I decided to try out Craig's code, which came to me something like this:
<%
oldDB = Server.MapPath("/accessTest.mdb")
bakDB = Server.MapPath("/accessTestBack.mdb")
newDB = Server.MapPath("/accessCompact.mdb")
Sure enough, this code reduced the size of my MDB file by more than half (!) and did it rather quickly. I'm not sure
what would have happened to requests for the database that came in at exactly the same time, but if you're using
Note that compacting the database also performs the 'repair' operation you might otherwise perform only through
Access' GUI.
For more ways to compact an Access database, see the following articles:
Note that in Access 97 / 2000, compacting a database would reset any tables with AUTONUMBER columns (if all
rows have been deleted, the next AUTONUMBER resets to 1; otherwise, it will be the next number available). With
If you are looking for information on how to synchronize multiple connections to a single database using ASP and
JRO, please see Q200300. The example there was written in VB, but you could easily port it to ASP.
How do I handle BIT / BOOLEAN fields in a query?
Unfortunately, this doesn't work in SQL Server. The following can be used in SQL Server:
However, this doesn't work in Access, since TRUE in Access is -1, not 1. Confused yet? :-)
Also, remember that if you're using checkboxes to update / insert / display data, you have to convert from "on" or ""
to 1 or 0, and when retrieving you must change 1 or 0 (or true or false) to "checked"... for example, page1.asp:
<form method=post action=page2.asp>
<input type=checkbox name=bool>
<input type=submit>
</form>
And page2.asp:
<%
bitValue = 0
bool = request.form("bool")
if bool = "on" then bitValue=1
sql = "UPDATE table SET bitField=" & bitValue
' ...
%>
And page3.asp:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
set rs = conn.execute("SELECT bitField FROM table")
ch=""
bitValue = rs(0)
if bitValue then ch=" CHECKED"
' or you could say if bitValue<>0 then ch=" CHECKED"
%>
<form method=post action=page2.asp>
<input type=checkbox name=bool <%=checked%>>
<input type=submit>
</form>
The following ASP script will split up the search terms and do an AND or OR search appropriately:
<%
SQL = "SELECT field FROM table"
keywords = trim(replace(Request.Form("keywords"),"'","''"))
if len(keywords)>0 then
sqlExtra = " WHERE "
kind = Request.Form("kind")
keywordArray = split(keywords)
for i = 0 to ubound(keywordArray)
keyword = keywordArray(i)
' if you want to match entire words only!
sqlExtra = sqlExtra & " (field LIKE '% " & keyword & " %')" & kind
' if you want to find each string inside of other words
' sqlExtra = sqlExtra & " (field LIKE '%" & keyword & "%')" & kind
next
end if
response.write sql & left(sqlExtra,len(sqlExtra)-len(kind))
%>
Note that you may want to make your search case sensitive (in SQL Server you would do this through database
options, not in code). You also might want to have a list of noise words, such as 'a' and 'the' - particularly if you
don't use the WHOLE WORD search. For example, a search for 't' might return your entire table! Yes, that's the
user's fault, but it's still YOUR server doing all that extra work.
How do I solve 'ADO Could Not Find The Specified Provider'?
Chances are, you are using an out-of-date version of Microsoft's Data Access Components. Get a new version from
(Reinstalling the most current MDAC components will automatically re-register the problem DLL, MSDASQL.dll.)
Schema: How do I get the stored procedures out of a database?
The following SQL Statement will retrieve all the user-created stored procedures:
You have SQL Server set up to use Windows Authentication. Unless you are using Windows Authentication
exclusively throughout your web site, you should have SQL Server set up to authenticate using mixed mode - this
allows support for Windows Authentication AND passing credentials via clear text (e.g. a connection string from
Now, make sure you have a valid user set up to access your tables, views and procedures.
If you really want to use Windows Authentication, instead of a generic user, then see the following KB articles for
Q307002 PRB: ASP/ODBC/SQL Server Error 0x80040E4D "Login Failed for User '(Null)'"
How can I make my SQL queries case sensitive?
If you installed SQL Server with the default collation options, you might find that the following queries return the
same results:
You can alter your query by forcing collation at the column level:
If you want to do this in a more global way, instead of modifying each individual query, you can force the collation at
the database level, or at the column level, using the ALTER DATABASE and ALTER TABLE commands, respectively.
You can see the current collation level on the properties tab of the database server, through Enterprise Manager (if
As changing this setting can impact applications and SQL queries, I would isolate this test first. In SQL Server 2000,
you can easily run an ALTER TABLE statement to change the sort order of a specific column, forcing it to be case
sensitive. First, execute the following query to determine what you need to change it back to:
EXEC sp_help 'mytable'
The second recordset should contain the following information, in a default scenario:
Column_Name Collation
----------- ----------------------------------------------
mycolumn SQL_Latin1_General_CP1_CI_AS
Whatever the 'Collation' column returns, you now know what you need to change it back to after you make the
If this screws things up, you can change it back, simply by issuing a new ALTER TABLE statement (be sure to
If you are stuck with SQL Server 7.0, you can try this workaround, which might be a little more of a performance hit
The following code uses a pair of nested recordsets to grab the triggers from the sysobjects table and then display
<%
dbname = "databasename"
ConnStr = "provider=SQLOLEDB;network=DBMSSOCN;"
ConnStr = ConnStr & "uid=<uid>;pwd=<pwd>;server="
ConnStr = ConnStr & "<x.x.x.x>;database=" & dbname
Note that you must loop through the sp_helptext resultset because, as with stored procedures, each line of code in a
Are there other ways to do this? Sure, here's some sample code, but it should be avoided due to its reliance on an
undocumented SP:
EXEC sp_msforeachtable @command1= "PRINT '?'
EXEC sp_helptrigger @tabname = '?'"
And this code, adapted from a post by Alejandro Mesa - which includes more information, but not the text for each
SELECT
[Table] = OBJECT_NAME(o.parent_obj),
[Trigger] = o.[name],
[Type] = CASE WHEN
(
SELECT
cmptlevel
FROM
master.dbo.sysdatabases
WHERE
[name] = DB_NAME()
) = 80 THEN
CASE WHEN
OBJECTPROPERTY(o.[id],
'ExecIsInsteadOfTrigger') = 1 THEN
'Instead Of'
ELSE
'After'
END
ELSE
'After'
END,
[Insert] = CASE WHEN
OBJECTPROPERTY(o.[id],
'ExecIsInsertTrigger') = 1 THEN
'Yes'
ELSE
'No'
END,
[Update] = CASE WHEN
OBJECTPROPERTY(o.[id],
'ExecIsUpdateTrigger') = 1 THEN
'Yes'
ELSE
'No'
END,
[Delete] = CASE WHEN
OBJECTPROPERTY(o.[id],
'ExecIsDeleteTrigger') = 1 THEN
'Yes'
ELSE
'No'
END
FROM
sysobjects o
WHERE
OBJECTPROPERTY(o.[id], 'IsTrigger') = 1
-- leave out this part of the where clause, to
-- include system triggers, e.g. those in MSDB
AND
OBJECTPROPERTY(o.[id], 'IsMSShipped') = 0
ORDER BY
1,2
Schema: How do I show all the primary keys?
The following code uses a recordset to grab the primary keys using a SELF JOIN on sysobjects:
<%
dbname = "databasename"
ConnStr = "provider=SQLOLEDB;network=DBMSSOCN;"
ConnStr = ConnStr & "uid=<uid>;pwd=<pwd>;server="
ConnStr = ConnStr & "<x.x.x.x>;database=" & dbname
To show the primary keys for a specific table, you can use the following methods:
sp_pkeys will return a row for each column that participates in the primary key for <tablename>. The columns you
first recordset, there will only be a column called Object Name (kind of useless, since that's what you passed in). In
the second resultset, there will be the following columns: constraint_type, constraint_name, and constraint_keys.
Check these out in Query Analyzer. I will post ASP examples when I have a chance.
Why does my DELETE query not work?
Many people assume that DELETE queries are just like SELECT queries, in that you want to retrieve or affect a
certain group of columns hinging on a condition (e.g. WHERE clause). The syntax I generally see, and which causes
The problem here is that the DELETE command does not take columns as a parameter; you're deleting ROWS, not
COLUMNS. You can't delete just one field in records that match a given set of criteria; you delete the entire rows. So
(The latter usually scares people, but the two statements are functionally equivalent.)
How do I know which version of SQL Server I'm running?
For SQL Server 7.0 and 2000, the following will extract ONLY the version information. This has not been tested with
SQL Server 6.5 (sorry, can't find any of those anymore) but works from 7.0 through 2000 (8.0) SP2.
SELECT RIGHT(LEFT(@@VERSION,37),8)
Or you can run this from ASP, assuming a valid and open connection to the SQL Server in an object named conn:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
set rs = conn.execute("SELECT RIGHT(LEFT(@@VERSION,37),8)")
response.write("SQL Version is: " & rs(0))
rs.close: set rs = nothing
%>
8.00.608 = SQL Server 2000 SP2 + Q316333 Patch (Updated 2002-04-17 for QFE Q356326 and QFE Q356938)
If you are wary about using the string parsing provided (either for forward- or backward-compatibility), or are
worried your server might have another build of SQL Server (e.g. a beta of a service pack), you can roll your own
SELECT @@VERSION
These results are a little superfluous, IMHO... but may contain any extra information you need. For example, they
also tell you which version of NT / Windows 2000 (including service pack level), the actual type of SQL Server
installed (for example, Enterprise Edition), and whether the system is Intel or Alpha. Finally, if you want even more
exhaustive information, you can use the following (undocumented) extended stored procedure:
EXEC master..xp_msver
Often you will want to pass an array to a stored procedure and have the procedure loop through and process each
element in the array. Unfortunately, T-SQL does not have an array datatype.
Ken Henderson's new book, The Guru's Guide to SQL Server Stored Procedures, XML, and HTML, has a section on
dealing with arrays in T-SQL, with extended stored procedures (accompanied by source code) on the CD-Rom. His
method actually adds array support to T-SQL, rather than work around its absence.
While you're waiting for the book to arrive, what you can do instead, is pass in a list of comma-delimited strings and
parse it out, inserting it into a temp table of your choice. For example:
This eliminates the need, for example, to limit your lists to 4,000 or 2,667 characters when they're used multiple
times in a single dynamic SQL statement. Instead, you could just join against the #temp table.
With SQL Server 2000, you could implement a user-defined function (UDF) for this task. Please let us know if you
think an example of this code in the form of a UDF for SQL Server 2000 would be valuable.
Why doesn't SQL Server allow me to separate DATE and TIME?
Admittedly, this is one of the rare features that Access boasts over SQL Server. The ANSI-92 standard states that
■ DATE + TIME
■ DATE
■ TIME
Unfortunately, SQL Server only supports the first type of column, with the DATETIME (sub-millisecond accuracy) and
SMALLDATETIME (minute accuracy) datatypes. If you only insert partial information (such as '10/31/2001' or '3:25
PM'), SQL Server will fill in the rest for you. Try the following script, to see what I mean:
SET NOCOUNT ON
CREATE TABLE #foo
(
dt DATETIME
)
INSERT #foo VALUES('10/31/2001')
INSERT #foo VALUES('3:25 PM')
SELECT dt FROM #foo
DROP TABLE #foo
dt
-----------------------
2001-10-31 00:00:00.000
1900-01-01 15:25:00.000
Notice that SQL Server inserts midnight when time information is missing, and 1/1/1900 when date information is
missing.
So what do you do when you're only interested in one or the other? There are several camps on this one. One is to
store the date and/or time information as a CHAR or VARCHAR column. This makes comparisons and sorting very
difficult. Another camp suggests storing the extraneous information and ignoring it. Often "ignoring" means
"converting", so to get just the date or time from the above table, you would do this:
Results:
dateonly
----------
10/31/2001
01/01/1900
timeonly
--------
00:00:00
15:25:00
Unfortunately, this type of conversion will not take advantage of any index on the DATETIME column. A similar
approach is to store a standard value for the part you're not interested in, and handle that part of the data at the
application level.
Another way to store only time, and do so efficiently, is to use an integer field. You multiply the number of hours by
+ DATEPART(MINUTE,GETDATE())
)
SELECT tm FROM #foo
DROP TABLE #foo
You multiply the hours by 100, then add the minutes, thus getting the time in military format. You could leave the
formatting up to the application, or retrieve a nicely formatted time by running this query against #foo (I think this
SELECT
timeonly = CAST(
LEFT(tm, LEN(tm)-2) + ':'
+ RIGHT(tm, 2)
AS VARCHAR(5)
) FROM #foo
Results:
timeonly
--------
1:17
Similarly, to store only date as an integer, you multiply the year by 10000, add the month (multiplied by 100), and
SET NOCOUNT ON
CREATE TABLE #foo
(
dt INT
)
INSERT #foo VALUES
(
-- e.g. 20011031 = 2001/10/31
+ DATEPART(DAY, GETDATE())
)
SELECT dt FROM #foo
DROP TABLE #foo
Getting this one into date format is about as pretty as the previous example:
SELECT
dateonly = LEFT(dt, 4) + '/'
+ LEFT(RIGHT(dt, 4), 2)
Results:
dateonly
----------
2001/10/31
These latter solutions should only be used for enhancing storage space required for presentation values. If you need
to do computations and conversions on these columns, throw the last few solutions out the window.
In this age of infinite, nearly-free disk space, I think most SQL programmers would say "let the app people deal with
the formatting" while, conversely, the application developers would say "the data people should give me the format I
need."
Why do I get 'BOF or EOF' errors?
When doing searches or other SQL queries, you may have encountered this error:
Either BOF or EOF is True, or the current record has been deleted; the
operation requested by the application requires a current record.
or
Either BOF or EOF is True, or the current record has been deleted.
Requested operation requires a current record.
The most probable cause, of course, is that there is no record. For example, it would happen with the following code,
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open <connection string>
set rs = conn.execute("SELECT lname FROM table WHERE fname LIKE '%frank%'")
do while not rs.eof
response.write rs("lname") & "<br>"
rs.movenext
loop
%>
To prevent this error from "blowing up" your ASP page, you need to trap for the case where no records are there.
Often you need to show results from multiple tables in one unified interface. Let's say you have the following
ABBA
Gold
Michael Jackson
Bad
Very Bad - Worst Hits
Tragically Hip
Self-Titled
Up to Here
Road Apples
Fully Completely
Day For Night
Trouble at the Henhouse
Live Between Us
Phantom Power
Music@Work
Now, what I see many people attempting in ASP is something like this (and I'll keep HTML simple for brevity):
<%
sql = "SELECT id,name FROM artist ORDER BY name"
set rs = conn.execute(sql)
do while not rs.eof
response.write rs("name") & "<p>"
sql2 = "SELECT name FROM album WHERE artist_id=" &_
rs("id") & " ORDER BY name"
There are much more efficient ways to do this than creating multiple recordsets and nesting them. This is what joins
are for. Imagine the above code if you then had a table with the song titles on the album? And then another table
listing the bands who have covered each song? And then a table further to that listing the albums of each of THOSE
bands?
Creating nested recordsets is very inefficient. Let's look at the join solution to the above:
<%
sql = "SELECT " &_
"artistName = artist.name, " &_
"albumName = album.name " &_
"FROM " &_
artist, " &_
album " &_
"WHERE " &_
"artist.id = album.artist_id " &_
"ORDER BY " &_
"artist.name, " &_
"album.name"
currentArtist = ""
set rs = conn.execute(sql)
do while not rs.eof
if rs("artistName") <> currentArtist then
response.write rs("artistName") & "<p>"
currentArtist = rs("artistName")
end if
response.write rs("albumName") & "<br>"
rs.movenext
loop
%>
One recordset, getting all your results, without causing extra strain on the database by executing multiple
statements consecutively. One large recordset is always more efficient than multiple recordsets obtaining the same
amount of data.
Now, one problem you might come acrosss is, what if an artist has no albums, but you still want to show the artist
name? Imagine adding the following artist, who couldn't possibly have any record deals in effect:
Now, the query and ASP code to still return this value is as follows (note how little it changes):
<%
sql = "SELECT " &_
"artistName = artist.name, " &_
"albumName = ISNULL(album.name,'-') " &_
"FROM " &_
"artist " &_
"LEFT JOIN " &_
"album " &_
"ON " &_
"artist.id = album.artist_id " &_
"ORDER BY " &_
"artist.name, " &_
"album.name"
currentArtist = ""
set rs = conn.execute(sql)
do while not rs.eof
if rs("artistName") <> currentArtist then
response.write rs("artistName") & "<p>"
currentArtist = rs("artistName")
end if
if rs("albumname") <> "-" then
response.write rs("albumName") & "<br>"
else
response.write "No albums for this artist.<br>"
end if
rs.movenext
loop
%>
We do a LEFT JOIN so that artists are included even if they have no matching records in the albums title.
Why do I get the error 'Command text was not set for the command object'?
This error is usually caused by an empty SQL statement (and does not necessarily deal with the explicit
ADODB.Command object). Here is a sample piece of code that will cause this error:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
' ...
%>
Using Option Explicit would prevent errors like this from happening (well, actually, using Option Explicit would just
cause a different error). Using quick and sensible debugging practices will help determine the cause of many SQL-
related errors.
Why do I get weird results when using both AND and OR in a query?
Like grade school mathematics and the chicken and the egg, several fundamental concepts involving order of
operations also apply to SQL statements. Take the following, for example:
SELECT fields
FROM table
WHERE
a=1
AND b=2
OR a=2
So, even with that straightforward English, which of these records would meet the criteria?
id a b
----------------------
1 1 2
2 2 2
3 2 1
Tricky, isn't it? The SQL engine, like me, has a hard time determining which clause(s) the AND applies to, and which
clause(s) the OR applies to. It really depends on what you mean by AND and what you mean by OR. You can
separate and group distinct clauses by using parentheses. So, as an example, if you mean:
GIVE ME the records
OUT OF this table
WHEN
a is 1 *AND* b is 2,
*OR*
WHEN a is 2
SELECT fields
FROM table
WHERE
(a=1 AND b=2)
OR a=2
GIVE ME records
OUT OF this table
WHEN
a is 1
*AND*
WHEN b is 2 *OR* a is 2
Here is a connection string that attaches to an instance of SQL Server that is NOT the default instance.
<%
cst = "Provider=SQLOLEDB;Server=127.0.0.1\instanceName;"
cst = cst & "database=pubs;network=DBMSSOCN;"
cst = cst & "uid=<uid>;pwd=<pwd>"
set conn = Server.CreateObject("ADODB.Connection")
conn.open cst
%>
You could also connect to an alternate port, if that's where your instance is running:
<%
cst = "Provider=SQLOLEDB;Server=127.0.0.1,1510\instanceName;"
cst = cst & "database=pubs;network=DBMSSOCN;"
cst = cst & "uid=<uid>;pwd=<pwd>"
set conn = Server.CreateObject("ADODB.Connection")
conn.open cst
%>
Why do I get 80040E37 errors?
or
or
or
Make sure that the table exists, and that the file exists in the path specified. If you are using a linked table, make
sure that IUSR_machineName has permissions on the folder(s) hosting the 'remote' database files, and that the
links are still pointing to a valid location... use Access' 'Linked Table Manager' to correct this situation.
Make sure that the object exists *as you spelled it*, that the owner name is specified in your query (e.g.
dbo.tablename), that you are in the correct database (use databasename.ownername.tablename), and that the user
in your ASP page (e.g. the UID or User ID in your connection string) has access to the object. If you are sure the
If you are trying to use a variable for a table name in a query, use dynamic SQL instead; for example:
If you are trying to access a table, column or procedure name that begins with numerics, consider changing the
table name. You should never name objects with numbers... however in the meantime, you can probably surround
the object's name with [] square brackers to keep SQL Server quiet about it.
Should I index my database table(s), and if so, how?
An index is like a set of pointers to specific rows in a table. These pointers are ordered in terms of the column(s)
defined by the index, which makes SQL's scans much more efficient - they just look up the pointers to the rows with
the relevant data (based on a WHERE or other clause), and jump right to the row(s).
If you have multiple indexes on one column each, there will be n sets of pointers to the rows - each ordered by the
specific column. As I will discuss later, you should choose your index(es) wisely.
If you have one index on multiple columns, it creates one huge set of pointers -- ordering the rows by each column
So let's say you have a table with three integer columns (a, b and c). Let's insert some sample data, which are
stored on disk in heap format [remember - tables are not guaranteed to be sorted in any way], looking like this (a
a b c
-----
1 2 3
2 2 2
1 2 2
3 1 2
2 1 2
Now, in the first example, we'll create a multi-column index on a, b and c. Now, this is how the pointers will look,
ORDER BY a, b, c:
a b c
-----
1 2 2
1 2 3
2 1 2
2 2 2
3 1 2
So now, imagine running a query SELECT a,b,c FROM table WHERE a=1. This query will be very efficient, because
Now, imagine running a similar query, but this time SELECT a,b,c FROM table WHERE b=1. This query will not be
very efficient, since SQL Server's only index option is this index which does NOT consider b to be a top priority (and
it just so happens that these records are NOT grouped together). It's the jumping around on the pointers that makes
SQL Server work harder to get all the rows that match the WHERE clause - in some cases it may be more efficient
for SQL Server to just do a table scan, rather than care about your index.
Let's create multiple indexes now, one on each column. The pointers for each, in order, will look something like this:
a b c
-----
1 2 2
1 2 3
2 1 2
2 2 2
3 1 2
a b c
-----
2 1 2
3 1 2
1 2 2
1 2 3
2 2 2
a b c
-----
1 2 2
2 1 2
2 2 2
3 1 2
1 2 3
Now, running the two queries mentioned above, each will be very efficient. In the first query, SQL Server will choose
the first index, and get all the rows where a is grouped together - minimizing read / scan time. In the second query,
SQL Server is smart enough to ignore the first index, and use the second index instead.
I will mention that with a compound index, let's say columns a, b and c, the optimizer will use the index for a query
on column a, or a and b, or a and b and c, or a and c. However, the index will not be able to optimize on queries
Keep in mind that while an index can speed up SELECTs, it can also can slow down INSERTs and UPDATEs. In
addition, an index occupies disk space, which can be an issue not only for performance but also for backup /
replication purposes. Just something you should keep in mind before adding 19,000 indexes to a database - there is
Indexing in and of itself is a science, and is not easy to master without spending a lot of time analyzing different
indexes and their impacts on performance and disk space. And I didn't even start getting into fragmentation,
What you use and what will work best depends on your schema, the nature of your queries, where your performance
counts, the load on your system, hardware, levels of transactions, acceptable query times, type of application, etc. I
strongly recommend running Index Profiler wizard, feeding it a SQL trace of the typical activity on your system. The
wizard should identify which types of indexes will work best for your scenario.
How do I determine if a number is odd or even?
Sometimes you need to know this because it's important; other times, it's simply for flipping the colors of adjacent
rows in an HTML table. In any case, here are some samples of using the MODULUS operator on signed integers in T-
Transact-SQL
This mimics retrieving a resultset from a table, along with a description of whether each id is odd or even. Note that
SELECT
id,
CASE
WHEN ABS(id) % 2 = 1 THEN 'odd'
ELSE 'even'
END
FROM table
VBScript
This uses a hardcoded value, so you can switch it around and test it before incorporating it into more significant
code.
<%
id = 5
stat = "even"
SELECT CASE abs(id) mod 2
CASE 1: stat = "odd"
END SELECT
Response.write id & " is " & stat
%>
JScript
This sample is just like the VBScript code above. The additional use of the Math object was required to convert the
integer to positive.
Well, there is a simply way, if SQL Server is installed on the same machine you're interested in:
EXEC master..xp_enumDSN
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
set rs = conn.execute("EXEC master..xp_enumDSN")
if not rs.eof then
do while not rs.eof
response.write rs(0) & " (" & rs(1) & ")<br>"
rs.movenext
loop
else
response.write "No DSNs found."
end if
rs.close: set rs = nothing
conn.close: set conn = nothing
%>
NOTE: You must have appropriate privileges to run extended stored procedures from the master database to pull
this off.
for i = 0 to ubound(arNames)
o2 = oReg.GetStringValue(HKLM, sPath, arNames(i), sValue)
response.write arNames(i) & " (" & sValue & ")<br>"
next
Set oReg = Nothing
%>
Again, appropriate privileges are required either for the anonymous or the authenticated user, depending on how
This is often a datatype problem. Make sure you are passing valid datatypes to whatever is going on in the
database. For example, check that you are not passing a NULL or empty string value to a column that doesn't accept
them (either manually defined or, say, a DATETIME column). Make sure that all VARCHAR lengths and numeric
Turn on on error resume next, and check the errors collection of the connection object. For example:
<%
set conn = server.createobject("ADODB.Connection")
conn.open "<connection string>"
sql = "<Statement that causes errors>"
on error resume next
conn.execute(sql)
if err.number <> 0 then
response.write(sql)
if conn.errors.count > 0 then
for each e in conn.errors
response.write e.description & "<br>"
next
end if
response.end
end if
%>
If this doesn't yield enough information, you might get a better error message out of the database itself, so take the
results of the response.write statement and issue it directly against the DB. If you are using an ADODB.Command
object, you might consider re-writing your code to execute the stored procedure directly, instead of using the
command object. (If you are relying on output or return parameters from the stored procedure, you will have to
If you are using "Persist Security info" in your connection string, try disabling it temporarily.
If you are using Driver={SQL Server} in your connection string, try Provider=SQLOLEDB instead (or vice-versa).
If you are connecting via an ODBC DSN, try using a DSN-less connection (see Article #2126.
If you are using the AddNew/Update methods of ADODB.Recordset, consider not doing so. Use an UPDATE or
INSERT statement instead. If you must, make sure you do an .UPDATE or .CANCEL before attempting to continue
further work on this or another record. See Q294160 for more info.
ASP developers thrown into a project involving SQL Server will undoubtedly need to get up to speed very quickly on
the database schema, structure, goals, etc. Unfortunately, not every company documents and maintains their
Aside from that, there are several third party vendors, offering products of varying complexity. You may choke at
the prices of some of these products, but remember that some of these tools are Enterprise-level and offer a *LOT*
Embarcadero DBArtisan
Enhanced ISQL/w
QALite
WinSQL
Yes, you could document these things yourself using SQL Scripts, Database Diagrams, and what have you. But some
of these tools are downright cool, and for a few dollars, will save you from reinventing the wheel.
How do I get the number of rows in a table, or all tables?
SQL Server
For any one table, you can get the number of rows with the following query:
There are some other interesting things you can quickly determine using SQL Server.
The following query gives a list of all user tables in the database, including the number of rows:
(Note that this query will return a row for the 'dtproperties' table, which is not in the regular 'Tables' view in
<%
' assuming valid/open connection object, conn
set rs = conn.execute("EXEC FAQ_GetRowCounts")
do while not rs.eof
response.write rs(0) & " has " & _
rs(1) & " rows.<br>"
rs.movenext
loop
rs.close
set rs = nothing
%>
The following will give you an idea of other parameters for a given table, including number of rows, reserved size,
To do this for all user tables, you can use the following (it will create multiple recordsets):
And the following will provide similar information for the database as a whole (returns two resultsets):
EXEC sp_spaceused
You can easily call these procedures in much the same way.
Another way is to again use the undocumented stored procedure sp_MSForEachTable. PLEASE do not rely on this
method for production code, as its functionality could change in a future release of SQL Server - or even a service
pack.
IF @rowCount > 0
SELECT rowCount = @rowCount
SELECT
[table],
[rows],
-- get the percentage of space used by table
[weight] = CAST(
[rows]*100.0 / @rowCount
AS SMALLMONEY
)
FROM #tmp
ORDER BY 2 DESC
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open <connection string>
set rs = conn.execute("EXEC FAQ_GetRowCounts")
if not rs.eof then
response.write "<TABLE><TR><TD COLSPAN=3>"
response.write rs(0) & " Total Rows</TD></TR>"
response.write "<TR><TH>Table</TH>"
response.write "<TH>RowCount</TH>"
response.write "<TH>Share of Whole</TH></TR>"
set rs = rs.nextRecordSet()
do while not rs.eof
response.write "<TR><TD>" & rs(0) & "</TD>"
response.write "<TD align=right>" & rs(1)
response.write "</TD><TD align=right>" & rs(2)
response.write "%</TD></TR>"
rs.movenext
loop
response.write "</TABLE>"
else
response.write "No tables?"
end if
%>
Access
For Access, you can - of course - use the very first method described in the top of this article. To iterate through
each table, though, you have to do a little more monkeying around in ASP, because there are no such stored queries
and there are no shortcuts, at least that I know of. You can use this method for SQL Server as well, if you're wary of
using undocumented stored procedures -- just change the connection string information to be SQL Server-based.
Note that this code *looks* simpler than the above, but there are two reasons - one is that the performance is
abysmal, and two is that the formatting isn't as pretty.
<%
dbname = "databasename"
Books Online is fundamental documentation for SQL Server. If you are developing from a machine that does not
have SQL Server installed, you can (and SHOULD) download this resource from
In most cases, this error message is purely informational, and does not indicate that anything terrible has happened
while connecting to the database. To avoid the error, you can use on error resume next, and then check each
I have heard many people saying that their web site is chugging along, and suddenly they get this error:
or
This can often be fixed by "kicking" the server, e.g. hitting refresh on the affected ASP page eliminates the problem.
If you have MDAC 2.6 installed, upgrade to the latest version of MDAC from http://www.microsoft.com/data/. As an
immediate workaround, force TCP/IP lookup in your connection string and make sure you refer to the SQL Server by
IP address, not by name. For more info see Article #2082 and Article #2126.
The timeout can be caused by a name -> ip lookup. This is a problem with the version of DBNetLib.dll that shipped
While a quick bandage would be to increase timeout values (see Article #2066), a better approach would be to make
■ encapsulate all data access using stored procedures, instead of ad hoc queries (see Article #2201)
■ avoiding ADODB.Recordset and ADODB.Command objects when they're not necessary (see Article #2191)
■ using the adExecuteNoRecords flag (&H00000080) on conn.execute calls that don't return resultsets
■ choosing appropriate indexes, primary keys, data types, and query structures
■ try not to do too much database work with a single script... I often see errors like this happening on
/file.asp, line 1294 -- which is *way* too many lines of ASP code to (a) manage and (b) expect to run
efficiently
■ making sure your network connection between web server and database server is not the bottleneck
How do I solve 'Could not find installable ISAM' errors?
Using a DSN or DSN-less connection, with a JET/OLEDB or Access/ODBC driver, you may get this message:
The easiest fix for this, if you have access to the server, is to reinstall MDAC. If you are running on a web host, you
There are also registry-specific KB articles detailing what the MDAC reinstallation accomplishes.
SQL Server TEXT columns are kind of special. Because of their size, and the fact that they're stored off-row, string
manipulations must be considered wisely. Also, some of the functions that work for VARCHAR are not legal with
TEXT. Here is a partial list (these samples assume a TEXT column called 'data'):
SELECT LEN(data)
-- becomes
SELECT DATALENGTH(data)
SELECT LEFT(data,5)
-- becomes
SELECT SUBSTRING(data,1,5)
SELECT RIGHT(data,5)
-- becomes
SELECT SUBSTRING(data,DATALENGTH(data)-4,5)
-- changing case
SELECT LOWER(data)
SELECT UPPER(data)
-- becomes
SELECT LOWER(SUBSTRING(data,1,DATALENGTH(data))
SELECT UPPER(SUBSTRING(data,1,DATALENGTH(data))
SELECT REPLACE(data,'bob','frank')
-- becomes
SELECT STUFF(data,CHARINDEX('bob',data),LEN('bob'),'frank')
Take care with the STUFF function. You should always check if the column contains the target string, otherwise your
This can be useful if you want to create an artificial gap between your current seed and your desired next identity
value.
In this case, the id column will continue incrementing from the vaue of @NewSeed.
If you want to remove all entries in a table and start over at 1 (or a non-default seed), you can do one of two
things:
-- or
This is for SQL Server specifically. With Access, you'll likely have to rely on the methods described in Article #2092
...
The first thing you can do is simply compare the difference between the timestamp BEFORE your query, and the
DECLARE @i INT
SET @i = 0
WHILE @i < 10000
BEGIN
SET @i = @i + 1
END
SET @b = CURRENT_TIMESTAMP
SELECT DATEDIFF(MS, @a, @b)
You can achieve similar results by running SQL Profiler, setting appropriate filters, and watching the Duration column
Finally, you can alter the above code slightly so that you see all of the durations on the messages tab of Query
Analyzer:
Then if you look in Query Analyzer's Messages tab, you will see the number of milliseconds taken by each step in
your query.
Obviously, this is much more useful for queries with a reasonable amount of unique queries, and doesn't do much
good for code with loops. This particular STATISTICS option prints durations for every single operation (each
iteration of a loop is recorded), so there will be 10,000 messages, likely all stating 0 ms. This could really impact the
What you can do after this, to compare two queries (and perhaps get to the bottom of why one takes longer than
the other), is to turn on Show Execution Plan (CTRL+K) and view that tab after your queries are finished. You'll be
able to spot table scans and other operations with high I/O or CPU costs.
Why do I get 800A0CC1 errors?
<%
...
Response.Write(rs("columnName"))
%>
ADODB.Recordset (0x800A0CC1)
Item cannot be found in the collection corresponding to the requested name or ordinal.
... even though columnName is clearly being returned in the resultset when you run the procedure from Query
Analyzer.
Sometimes this is because you've misspelled the column name, referred to an aggregate without using an alias (see
Article #2159), referenced a column name that appears more than once in the SELECT list (e.g. a JOIN between two
tables that have a common column), or even referenced the wrong resultset in the case of multiple resultsets. If you
are having one of these problems, you can correct them quite easily by referencing the column correctly, or the
correct recordset. If you have a JOIN that has two columns with the same name, you probably have a design issue
because (a) if the columns contain the same value, you don't need both in the resultset; and (b) if the columns don't
contain the same value, then they have different meanings, and therefore should have distinct names.
But most often it is because you're using a stored procedure, and ADO is incorrectly interpreting messages in QA's
1 row(s) affected
... as a resultset, however there are no columns in the data pane, so it is useless except for debugging within Query
Analyzer.
You can correct this behavior by adding the following to the top of your procedure:
SET NOCOUNT ON
This prevents the messages from factoring into your ASP code.
If your database code is not changeable, you might experiment with adding set rs = rs.nextRecordset() lines until
If you are developing with MS Access, you've almost certainly seen this error message before:
There are many different error messages that can go along with this.
or
Make sure IUSR has access to the database (for more information, see Article #2062).
With SQL Server, the error message might be a little bit different:
...but again, make sure the user you're connecting as has appropriate access to the table, stored procedure or any
Another possible problem could be trying to issue an Update or Resync command on an ADODB.Recordset object
Some databases will toss this error if you put quoted identifiers around table or column names, e.g.
With MySQL, this error can come up if you have a malformed SQL statement.
Can't offer much here, except to debug your SQL statement (see Article #2145) and make sure its format is correct.
How do I temporarily disable a trigger?
Some people have found a need to disable a trigger for a short period of time while they did work on the system
that was not typical for their application. Rather than delete the trigger and re-create it later, here is some code that
When dealing with CHAR/VARCHAR columns, and not validating user input, you may have come across this error:
or
or
Typically, this is caused by trying to insert too many characters into a defined column. For example:
■ using client-side methods (e.g. MAXLENGTH or JavaScript) to prevent such data from being submitted;
There may be another reason for this symptom, if you are seeing this error from ASP but do not see it in native
tools; for example, Query Analyzer. You can alleviate this by making SET database options consistent -- see
Check that you are inserting valid numbers into numeric columns. These should not contain any non-numeric
Make sure you are inserting numeric values into a numeric column, and that it doesn't exceed the capacity of the
column. For example, 3,000,000,000 won't fit in a standard Access numeric (or SQL Server INT) column.
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]
The conversion of a CHAR data type to a DATETIME data type resulted in an
out-of-range DATETIME value.
or
or
or
This error usually happens when you do one of the following things:
If you are using a TEXT column for the first time, you might find that it does not behave the same as
This could be because the provider you selected doesn't support AbsolutePosition. But it is more likely that you are
not opening the cursor with the correct properties. Make sure you are using a dynaset or snapshot cursor; this
means that adOpenForwardOnly should be used in place of adOpenStatic, and the CursorLocation should be set to
adUseClient.
Where else can I learn about SQL Server?
There are several resources devoted to SQL Server. Here are Microsoft's:
If you don't already have it installed, download Books Online right now. See Article #2229.
T-SQL Solutions
SQLDev.Net
SQLXML.org
CallSQL
SQL.Nu
SQL-Server-Performance.com
Darren Green
Dinesh.T.K
Erland Sommarskog
Itzik Ben-Gan
Keith Kratochvil
Michael Hotek
Umachandar Jayachandran
And these are the titles on my bookshelf. You might think I am exaggerating, but I took a picture.
Programming Microsoft SQL Server 2000 with Microsoft Visual Basic .NET
Hitchhiker's Guide to Visual Basic and SQL Server, 6th ed. (Vaughn)
First, download the latest version of MDAC from http://www.microsoft.com/data/. Then, if you don't already have
JET 4.0 installed, download JET 4.0 SP3 FROM http://www.microsoft.com/data/download_Jet4SP3.htm (you will only
need this if you are running Windows 9x or NT 4.0, and do not already have JET components on your machine; if
You can see more information in Q239114 (Access 2000) and Q282010 (Access 2002 / Access XP).
Schema: how do I retrieve the description property of a column?
Assuming you created a column in Access and added a description to it, use the following:
<%
on error resume next
Set c = Server.CreateObject("ADOX.Catalog")
c.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=<path>\<file>.mdb"
d = c.Tables("<table>").Columns("<column>").Properties("Description").Value
Response.Write "Description = " & d
if err.number <> 0 then Response.Write "<" & err.description & ">"
Set c = nothing
%>
Then you could add a description in the Enterprise Manager GUI, or you could use this code:
EXEC sp_addextendedproperty
'MS_Description',
'<Some description>',
'user',
dbo,
'table',
'T2',
'column',
id
Now, you can retrieve that value with the following code:
<%
set conn = Server.CreateObject("ADODB.Connection")
sql = "SELECT name,value FROM ::fn_listExtendedProperty " &_
"NULL, 'user', 'dbo', 'table', 'T2', 'column', 'id'"
set rs = conn.execute(sql)
if not rs.eof then
do while not rs.eof
response.write rs(0) & " = " & rs(1)
rs.movenext
loop
end if
%>
Note that the column name is surrounded in quotes when retrieving from the function, but not when it is passed to
To get the extended properties for ALL columns in the table, just change 'id' to default or NULL (without quotes).
How do I convert columns of values into a single list?
In Article #2248, we discussed a method for converting a comma-separated list (e.g. from a set of checkboxes, or a
multi-select list) into a column of values. What about the other way around? You could do it from ASP easily enough,
as follows:
<%
' assuming valid connection object 'conn'
Set rs = conn.execute("SELECT value FROM table")
if not rs.eof then
Do while not rs.eof
list = list & rs(0) & ","
rs.movenext
loop
Response.Write left(list,len(list)-1)
end if
%>
However, what if you wanted to do this totally within the confines of the database (e.g. called from another stored
procedure)? You could use the following method, however before proceeding you should read this note from UJ, as
well as Q287515:
SET NOCOUNT ON
SET CONCAT_NULL_YIELDS_NULL ON
DECLARE @list VARCHAR(8000)
DECLARE @delm VARCHAR(2)
SET @delm = ','
Often you will have a huge resultset, but you only care about the first 5, 10 or 20 records. There are several
For these examples, let's assume you only want to retrieve the most recent 10 records from a table called foo, with
For SQL Server 6.5, you'll have to take a slightly different approach, utilizing the SET ROWCOUNT functionality (this
SET ROWCOUNT 5
SELECT column1, column2, dt FROM foo ORDER BY dt DESC
SET ROWCOUNT 0 -- (This last step is the most important!)
You could do this from ASP in a couple of ways, though I really don't recommend either method. Reason being,
you're making the database do all the work of pumping records out of the database when you're not interested in all
of them.
The first method would simply be to maintain a counter, and when you get to your count, stop the loop.
<%
' ...
sql = "SELECT column1, column2, dt FROM foo ORDER BY dt DESC"
set rs = conn.execute(sql)
counter = 1
do while not rs.eof and counter < 5
' process rs(???)
counter = counter + 1
rs.movenext
loop
' ...
%>
Another method is to use the MaxRecords method of the ADODB.Recordset object. If you've been through
aspfaq.com in the past, you'll probably guess that this is my least favorite method.
<%
' ...
sql = "SELECT column1, column2, dt FROM foo ORDER BY dt DESC"
set rs = Server.CreateObject("ADODB.Recordset")
rs.MaxRecords = 5
rs.open sql, conn
do while not rs.eof
' process rs(???)
rs.movenext
loop
' ...
%>
Note that Access does not support the MaxRecords property (see Q186267), so this code sample would only be valid
Here are some guidelines for database size, object number and naming limitations contrasted across Access 2000/XP, SQL Server 7.0,
SQL Server 2000, and MSDE 2.0 (SQL Server 2000 Desktop Engine). The main reason for this chart was to answer the question "how
many rows can I get into a table?" -- which doesn't have a direct answer, but is limited by many of the variables listed below (not to
Parameter Access 2000/XP SQL Server 7.0 SQL Server 2000 MSDE 2.0
Number of rows per table limited by storage limited by storage limited by storage limited by storage
Number of input params per procedure / query 1995 1,024 2,100 2,100
Number of indexes per table 32 250 (1 clustered) 250 (1 clustered) 250 (1 clustered)
1. Using a federated database in SQL Server 2000, you can have 32,767 databases of 1 TB each, which is probably more space
than anyone will ever need (though that phrase itself has proven dangerous to say <G>).
2. This is how many concurrent users Access will allow, however this number is much smaller when Access is used in a web-based
environment (see Article #2195).
3. SQL Server allows 32,767 concurrent connections, or the number of licenses allowed, whichever is lower.
4. MSDE has a performance throttler that kicks in when more than 5 workloads / batches are being run at once.
5. The Query Designer interface in Access limits you to 199 parameters, though it is possible (but not recommended) to create a
There are several characters that have special meaning within a SQL query, for example the percent sign (%) in a
LIKE query is a wildcard that essentially means "any number of characters can go here." Likewise, the underscore
(_) is a wildcard that says "any single character can go here." So what if you are actually looking for a value that
contains a literal percent sign? You will end up with bizarre results if you try the following:
-- or
The first query 'delimits' the special character with square brackets, telling the engine to treat it as a normal literal
character instead of a character with special meaning. The second query uses a custom escape character -- you can
use any character you like, just be careful that you aren't also expecting to use it as part of the literal string.
Now, you might be wondering, how do I escape a square bracket? If you have something like this:
The results won't be what you expect, because an opening square bracket is considered a special character.
Surprisingly, you can avoid this problem in much the same way, by one of the following two queries:
SELECT columns FROM table WHERE
column LIKE '%[[]SQL Server Driver]%'
-- or
You can do this replacement at the ASP side, before passing the string in, or within the SQL Server code itself.
Why does Enterprise Manager crash when I get an error in a stored procedure?
After installing Visual Studio.NET betas, you may find that when you are writing stored procedures and you
encounter an error, the EM crashes and aborts, rather than allowing you to correct the error.
There are a couple of workarounds. One is to use Query Analyzer or another tool (even ASP!) to create your stored
procedures. Another is to delete the 'OANACACHE' value from the Environment Variables section of system
properties. And finally, removing the .NET Beta and installing the RTM version will also fix this problem. You can find
When deploying applications to a client's server(s) or to a shared SQL Server, there is often a concern that other
people might peek at your business logic. Since often the code in a stored procedure can be proprietary, it is
understandable that we might want to protect our T-SQL work. There is a trivial way to do this in SQL Server,
instead of:
Now, before you do this, make sure you keep the logic of the stored procedure in a safe place, since you won't have
Now you will notice that when you try to open the procedure in Enterprise Manager's GUI, you will receive the
following error:
Microsoft SQL-DMO
Error 20585: [SQL-DMO]
/******
Encrypted object is not transferable,
and script can not be generated.
******/
And when you try to use sp_helptext to review the code:
Unfortunately, there are at least two ways to defeat this mechanism. One is to run SQL Profiler while executing the
stored procedure; this often can reveal the text of the procedure itself, depending on what the stored procedure
does (e.g. if it has GO batches, dynamic SQL etc). The user can also delete the stored procedures, hook up SQL
Profiler, and ask you to re-create them (in which case they will capture the CREATE PROCEDURE statements).
Another is to use the code from this Planet Source Code article.
Why do I get 'Operation is not allowed when the object is closed' errors?
The operation requested by the application is not allowed if the object is closed.
This error can be caused when you try to access values from an empty recordset or from a recordset that has
already been closed. The most common cause, however, seems to stem from calling a stored procedure that does
not use SET NOCOUNT ON. See Article #2275 for more info.
This can be caused by trying to set a property that needs to be set before the object is opened. For example, trying
to set the MaxRecords property of an ADODB.Recordset object after opening the recordset:
<%
set rs = server.Createobject("ADODB.Recordset")
rs.open "SELECT columns FROM tablename",conn
rs.maxRecords = 5
%>
To fix, the code should be:
<%
set rs = server.Createobject("ADODB.Recordset")
rs.maxRecords = 5
rs.open "SELECT columns FROM tablename",conn
%>
Why do I get 80040E14 errors?
This error is pretty self-explanatory... if you try to insert/update a column that does not allow NULL values, with
NULL (e.g. by hard-coding NULL into the statement, or by leaving a column - that does not have a default value
or
This will happen if you are using #temp tables and the collation on tempdb does not match that of the database(s)
you're working in. The collation of all affected databases should match, or at least be compatible. You can run the
following command to compare collation between the databases (look at the Collation= section of the 'Status' value
If you can't change the collation settings for tempdb, you can override them for your #temp tables by creating your
Several string functions cannot be performed against TEXT/NTEXT columns. See Article #2061 for more
information.
If you know the object exists, and that you are in the correct database, this is usually a permissions issue. See
Article #2284.
or
or
or
Syntax error in FROM clause
or
or
You either used a reserved word as a column or alias name, or didn't delimit a value properly. See Article #2086 for
This could be for the same reasons, or it could be that you are using an ADODB.Command object and are attempting
to pass a string to an INT parameter or vice-versa. Try using the following approach instead of the troublesome
ADODB.Command object:
or
(This is both easier on the eyes and less prone to errors. If you need an output or return variable, consider changing
the stored procedure to send that back as a recordset instead - you can then use the nextRecordsSet() method to
If you still get the same error, response.write the SQL statement and paste it into Query Analyzer. You might get a
microsoft.public.inetserver.asp.db and someone will try to help you figure out what the problem is.
You will need to investigate your query, and the structure of the table(s) it affects, to determine why you will be
Several functions available within Access are not available through ADO/JET providers - see Article #2394.
This is usually caused when you are using a web task to modify existing HTML files which are also in use by IIS. One
workaround would be to cycle between two filenames... active and inactive. Flip which one is 'current' every time the
web task runs; depending on the frequency of the web task, this will reduce the chance that someone will still have
the inactive file open when you make the other file active. In addition, you could delete the inactive file after each
run of the web task, to make it even more unlikely that IIS will have a lock on the file.
This can also happen if the account that the SQL Server and SQL Server Agent services don't have sufficient
privileges on the folder where the web task outputs its file.
This can happen if you have a table that is defined to allow more than 8060 characters per row (SQL Server warns
you about this when creating the table, but allows you to create it nonetheless). This kind of structure can be useful
if, say, you have two different VARCHAR(8000) columns where only one of them could possibly contain that much
text. If you try to insert 8000 characters into both columns, you get the above error. Your SQL statements need to
be constructed with logic that carefully insulates them from exceeding the physical bounds of the table. If you feel
you might need to exceed 8060 characters in a single row, consider storing the characters off-row (e.g. in a
TEXT/NTEXT column).
This error happens for one of two reasons. Either the disk where the data is stored is full, or the database is not set
to auto-grow and it has reached capacity. If the former, you will need to free up space on the drive (or move the
data files to a different location). If the latter, you will need to set the database to auto-grow, or clear out stale data
or
or
The current query would generate a key size of <n> for a work table. This
exceeds the maximum allowable limit of 900.
This usually means you are trying to run a complex query with a row width that the optimizer can't handle (typically
due ot use of wide CHAR or VARCHAR columns). In SQL Server 7.0 and up, you can solve this issue by adding
OPTION ROBUST PLAN to your query. Here is a Books Online excerpt about ROBUST PLAN:
Forces the query optimizer to attempt a plan that works for the maximum potential row size,
possibly at the expense of performance. When the query is processed, intermediate tables and
operators may need to store and process rows that are wider than any of the input rows. The rows
may be so wide that, in some cases, the particular operator cannot process the row. If this
happens, SQL Server produces an error during query execution. By using ROBUST PLAN, you
instruct the query optimizer not to consider any query plans that may encounter this problem.
For more information about how to implement OPTION ROBUST PLAN, see the 'SELECT' topic in Books Online.
This is usually caused by using " instead of ' to delimit string values, often in an attempt to avoid having to replace '
with '' (see Article #2035). However, " are not string delimiters by default in SQL Server, they are identifiers. This
means that strings inside of " within a SQL expression are expected to contain column names. So, instead of:
SQL = "UPDATE table SET column = """ & request.form("value") & """"
use:
Further to this, two other comments. First, the act of doubling up the ' character is not only to prevent parsing
errors, but also to avoid exposure to your system to attempts at SQL injection. Second, if you override the quoted
identifiers so that " can be interpreted as a string delimiter, instead of worrying about ', now you have to worry
This usually means your SQL statement is joining on more than one table, and doesn't know which column you're
SELECT column1
FROM table1
JOIN table2
ON table1.column1 = table2.column1
The changes:
■ Used an alias for the column name, so that external code continues to work when referencing "column1"
■ Added the prefix "t1" to the requested column, so that the engine knows which table we want the data from
■ Added table aliases in the FROM / JOIN clauses to shorten the code
Why is Query Analyzer only returning 255 characters of my VARCHAR / TEXT column?
SQL Server's Query Analyzer tool is limited, by default, to display 255 characters of any column (regardless of
datatype).
To expand this a bit, you can go to QA's Tools / Options menu, move to the results tab, and adjust the 'Maximum
characters per column' field. The maximum length of the output for any column is 8,192 characters; if you need
Go to the Query menu, Current Connection Options. On the Advanced tab, change the value for 'Maximum
You might be confused about how to retrieve the second and third set of results when using a standard rs against
the connection object. Here is code that demonstrates how to handle this, using the NextRecordSet() method:
<%
' ...
' assuming valid and open object, conn
set rs = rs.nextRecordSet()
if not rs.eof then
do while not rs.eof
response.write rs(0)
rs.movenext
loop
end if
set rs = rs.nextRecordSet()
if not rs.eof then
do while not rs.eof
response.write rs(0)
rs.movenext
loop
end if
rs.close: set rs = nothing
' ...
%>
Why can't I use LIKE '%datepart%' queries for dates against SQL Server?
Several people want to offer the ability for users to enter any portion of the date (e.g. only the month, or only the
year), and leave wildcards for the rest. So, for all records in 2002, they would do something like this:
Unfortunately, SQL Server does not internally store dates in the character format you see them represented as in
While for some odd reason Books Online recommends LIKE queries against datetime values, date range searches are
much more effective because they don't rely on specific formatting of dates, regional settings on a server, etc. They
will also have the ability to use an index, if it exists. Here is the above query in a much more efficient and logical
format:
Another interesting approach, albeit not as efficient as the above, is to use the shorthand datepart functions in SQL
Server to match one or more criteria passed in. The following stored procedure will work for any combination of day,
Year:
<select name=y>
<option value=0>Any
<%
for i = 2002 to 2000 step -1
response.write "<option value=" & i
if i = y then response.write " SELECTED"
response.write ">" & i
next
%>
</select>
Month:
<select name=m>
<option value=0>Any
<%
for i = 1 to 12
response.write "<option value=" & i
if i = m then response.write " SELECTED"
response.write ">" & monthname(i)
next
%>
</select>
Day:
<select name=d>
<option value=0>Any
<%
for i = 1 to 31
response.write "<option value=" & i
if i = d then response.write " SELECTED"
response.write ">" & i
next
%>
</select>
<input type=submit>
</form>
<hr size=1>
<%
if request.form("y") <> "" then
set Conn = Server.CreateObject("ADODB.Connection")
Conn.open "<connection_string>"
sql = "EXEC dbo.ReturnRecordsForAnyPartOfDate " & _
y & ", " & m & ", " & d
set rs = conn.execute(sql)
if not rs.eof then
do while not rs.eof
response.write rs(0) & "<br>"
rs.movenext
loop
else
response.write "No records."
end if
rs.close: set rs = nothing
conn.close: set conn = nothing
end if
%>
Can I have optional parameters to my stored procedures?
Yes, you can have a stored procedure that has optional parameters. You can do this by setting a default value for
each parameter that you want to make optional. The default value is typically NULL, but it doesn't have to be. Here
/*
END
GO
EXEC foo @param1='bar', @param2=4
EXEC foo @param1='bar'
EXEC foo @param2=4
EXEC foo 'bar',4
EXEC foo 'bar'
EXEC foo
For an Access stored query, you can use the following logic:
There are several text messages that go along with 0x800a0cb3 errors:
Current Recordset does not support updating. This may be a limitation of the provider, or of
the selected locktype.
Depending on which error message you are receiving, one of the following is probably true:
■ you are using named constants (e.g. adUseClient) but forgot to include ADOVBS.INC (see Article #2102 and
Article #2112)
■ you tried to use the NextRecordset() method to process multiple resultsets from an Access provider (see
Q202433)
■ you tried to combine server-side recordset properties, e.g. cachesize, with a client-side (adUseClient)
recordset - make sure the properties you are using make sense for the type of recordset you've opened
■ you tried to use advanced methods or properties such as bookmark, MovePrevious, or AbsolutePage on
default recordset objects - make sure you set the correct lockType and/or cursorLocation
■ you are trying to use an ADODB.Recordset object with an improper lock type to handle an update or addnew
- use an UPDATE or INSERT statement instead of a Recordset object (see Article #2191).
■ you are using an ADOX.Catalog object (or one of several other potential objects), and tried to close it. All
you have to do is set a catalog object to nothing... it does not have a close() method.
What datatype should I use for my character-based database columns?
Often I see the phrase "I have an NVARCHAR column..." and sometimes have to keep myself from asking "why did
you choose that datatype?" Most times, this datatype isn't chosen intentionally; when you upsize from Access to SQL
Server, or transfer from other database products such as Sybase SQL Anywhere, this is the default datatype applied
to character-based columns (possibly to ensure that any Unicode data stored in such columns would not be lost /
corrupted).
When designing your database, you should really try to understand your data, and the datatype that suits it best.
CHAR supports fixed width strings, up to 8,000 characters. Best used when data length is constant,
CHAR
e.g. social security numbers or ISBN numbers.
Similar to CHAR, with the support of Unicode characters. You should only use this datatype if you
NCHAR need Unicode support - due to storage overhead (2 bytes per character means the maximum
VARCHAR supports variable length strings, up to 8,000 characters. Best used when data length is
VARCHAR
variable, e.g. last names or product SKU codes.
Similar to VARCHAR, with the support of Unicode characters. You should only use this datatype if
NVARCHAR you need Unicode support - due to storage overhead (2 bytes per character means the maximum
TEXT supports variable length strings, up to 2 GB, stored off row with a 16-byte pointer in the
record itself. Should be used whenever your data will exceed the 8,000 character limit of
TEXT CHAR/VARCHAR columns (though if you need only 12,000 or even 24,000 characters, you might
consider overflowing into multiple VARCHAR columns, rather than use the inflexible and less
Similar to TEXT, with the support of Unicode characters. You should only use this datatype if you
NTEXT need Unicode support - due to storage overhead (2 bytes per character means the maximum
columns, you can only store a couple at most before exceeding the capacity of the entire database (see Article
So, if you inherited nchar/nvarchar/ntext columns from an upsize or import, consider changing those that do not
need to support Unicode characters to their non-Unicode datatype equivalents. If you do need to support Unicode
strings, make sure you use an N prefix (see Q239530 for more information):
Access
Access only supports TEXT (255) and MEMO (64 KB when entered through the GUI, and 1 GB when entered
programmatically), so the choice here is much easier - use TEXT unless you need more than 255 characters. In
addition, if you only need 10 or 20 characters, don't accept the default size (50) as this will be a considerable waste
of space.
Why do I get 800A0E7D errors?
When using the command object to invoke a SQL Server stored procedure, you may have seen this error:
ADODB.Command (0x800A0E7D)
Requested operation requires an OLE DB Session object, which is not
supported by the current provider.
or
The most likely cause is that you are trying to set the command object's activeConnection to an invalid connection
object, or you didn't set the activeConnection property at all. Check over your code and make sure your connection
object is valid and open before trying to set it as the active connection for your command object. Or avoid using the
■ you are using SQLOLEDB and have forcefully turned off pooling (either in the metabase or by adding "OLE
■ you are using an ADODB.Recordset when you could be getting by with a simpler object (see Article #2191);
or,
■ you are using an ancient version of MDAC (upgrade to the most recent version at
http://www.microsoft.com/data/).
Why do I get 'object could not be found' or 'invalid object name', when the object exists?
-- or
This can can happen in SQL Server, and can be due to a number of reasons. For example:
■ the user you are connecting as does not have SELECT, UPDATE, INSERT, DELETE, EXEC permissions on the
object;
■ you are referencing the object without an owner name prefix (or with the wrong owner name);
All SQL Server 2000 versions (as well as several other Microsoft products) ship with SQL Server 2000 Desktop
Edition, often referred to as MSDE (Microsoft Data Engine) 2.0. Essentially, MSDE is a version of SQL Server that you
can distribute with applications. As such, it is slightly lighter weight, and has some notable restrictions. Other than
the following list, however, MSDE and the other editions of SQL Server have very similar behavior and performance
■ performance throttling occurs when there are more than five (5) concurrent workload batches in progress;
■ MSDE cannot be a publisher in transaction replication, and when acting as publisher in all other types of
■ MSDE does not ship with GUI administrative tools such as Enterprise Manager or Query Analyzer. To
administer an MSDE database, you can either install client tools from any other version of SQL Server, use a
programming environment like Visual Studio or Web Matrix, or use OSQL from the command line (see
■ MSDE does not come with Books Online, but you can download it from Microsoft at
http://www.microsoft.com/sql/techinfo/productdoc/2000/books.asp
The licensing issues surrounding MSDE have long needed to be clarified - perhaps
This can happen if you use an adOpenStatic ADODB.Recordset object to open Excel, or use an adOpenKeyset
Basically, make sure the methods/properties you are using are supported by the driver/provider you are using to
If you are executing a stored procedure, make sure you do so through the connection.execute() method, rather than
a command object, and add the following two lines of code to the beginning of your procedure:
If you are using With, an ADODB.Recordset, and the AddNew/Update methodology, consider not doing so (see
If you are sure that these things are intact, make sure you have the latest version of MDAC
(http://www.microsoft.com/data/).
Microsoft OLE DB Provider for SQL Server error '80040e21'
Optional feature not implemented.
or
This can often be caused by using invalid ad* constants with ADODB.Command, such as adDBDate. Use
adDBTimeStamp instead (see Q214459) or, better yet, use a straight EXEC statement instead of using the
This error is pretty explanatory. Access does not allow these 'special' columns to be included in DISTINCT queries.
Though I have always questioned why you would have duplicates in a memo column; since it is designed to store a
LOT of text, it seems that in most scenarios it would be unlikely to repeat enough, and also be insignificant in terms
of individual records?
Microsoft OLE DB Provider for SQL Server error '80040e21'
Invalid character value for cast specification.
This can happen if you try and pass a NULL string or a non-string datatype to an ADODB.Recordset object or a
stored procedure, and the database logic attempts to perform an implict or explicit CONVERT or CAST.
If you are using an aggregate function (e.g. SUM, COUNT, MAX), then any other column in the SELECT list must also
be in the GROUP BY clause. This is so that the database knows how to organize results.
This is not an easy question to answer, as there are many criteria which can vary in presence and rank from shop to
Oracle 9i
For small business applications and/or where cost is an issue and size/concurrency is expected to be limited, my
preference goes:
MySQL
Microsoft Access
Here is where your work comes in. You can evaluate each product at the following URLs, and determine which
MySQL
Oracle 9i
Microsoft Access
Why do I get 80040200 / 80040514 / 800A0E7A errors?
First things first: check your connection string. This error can often happen if you have a typo in your provider or
driver details.
If you are sure that your connection string is accurate, then follow these directions, depending on the database
platform in use:
SQL Server
Make sure you have the most recent version of MDAC installed (http://www.microsoft.com/data/).
Access
This is often caused by attempting to connect to an Access database using OLE-DB (e.g.
Provider=Microsoft.Jet.OLEDB.4.0;) without having JET components installed. A major source of this problem is that
Microsoft stopped shipping the JET files with MDAC, starting at 2.6 (see Q271908), so many machines set up
starting at that baseline or later do not have the necessary JET runtime files.
See Article #2342 for information on getting the most recent version of the JET provider.
Other problems with Access include fat-fingering the connection string, or mixing up Access and JET parameters,
e.g:
Provider=Microsoft.Jet.OLEDB.4.0;DBQ=c:\file.mdb
(Data Source is the parameter for database location with the JET provider)
See Article #2126 for sample Access connection strings to help model your own.
Make sure you have the most recent version of MDAC installed (http://www.microsoft.com/data/), and check the
vendors' web site and make sure your server has the most recent version of their ODBC driver or OLE-DB provider.
Under certain scenarios, connection pooling may cause the following error:
To make this error go away, you can override the prompt property of the connection object in the following
manner:
<%
const adPromptNever = 4
set conn = server.createObject
conn.Properties("Prompt") = adPromptNever
conn.open <connection string>
...
%>
If you are using any of the fancy wizards that come with Visual Studio 6.0, you may have this error message:
My advice: don't use DataGrid, DataEnvironment, ADODC, DTC or any other 'automatic' / helper control, or at least
And finally, if you are using CDO / CDONTS with (or trying to replicate) Outlook Web Access, you may get one of the
following:
MAPI_E_END_OF_SESSION
CdoE_END_OF_SESSION
If you see any of the above, please refer to Q297969 for information and potential resolution(s).
How do I determine if a column exists in a given table?
In Access, we can take one of our existing schema extractors and modify it slightly:
<%
columnToFind = "foo"
dbname = "/path_to.mdb"
tablename = "tablename"
if found then
response.write("Column exists.")
else
response.write("Column does not exist.")
end if
%>
In SQL Server, you can use a much more direct approach:
<%
columnToFind = "foo"
dbname = "dbname"
tablename = "tablename"
set rs = conn.execute(sql)
if clng(rs(0))>0 then
response.write("Column exists.")
else
response.write("Column does not exist.")
end if
Here are the situations I know of that can bring about 800a01fb errors.
This error can happen when you use an ADODB.Recordset to retrieve a resultset, and the resultset is empty.
object, this error might be caused by a corrupted MDAC. Update MDAC from http://www.microsoft.com/data/.
If you try to pass an array to a COM object, you might see this error. See Q217114 for information on passing
When you rely on jobs in SQL Server, it can be quite frustrating when SQL Server Agent has stopped and either it's
not set to auto-restart, or you are using an older version of SQL Server where this option didn't exist. So if you can't
connect to the database using Enterprise Manager or Query Analyzer, you can write up a quick ASP script that
connects to the database and starts the service (assuming the SQL Server service is started, and that the user has
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connectionString>"
conn.close
set conn = nothing
%>
Keep in mind that XP_ServiceControl is undocumented and unsupported; its behavior could change with a service
The following will allow you to query a table for last names beginning with each letter in the alphabet.
<%
Ltr = UCase(Request.QueryString("Ltr"))
if (Ltr < "A") or (Ltr > "Z") then Ltr = "A"
for i = 65 to 90
if i > 65 then response.write " | "
if Asc(Ltr) <> i then
response.write "<a href='Alpha.asp?Ltr=" & _
Chr(i) & "'>" & Chr(i) & "</a>"
else
response.write "<b>" & Chr(i) & "</b>"
end if
next
SQL = "SELECT lastName FROM nameTable WHERE lastName LIKE '" & Ltr & "%'"
Response.Write "<p>" & SQL
Now, you might only be interested in letters (and other characters) that are actually represented in your database.
Most of you probably don't have last names that start with Q or X, for example; and sometimes a user will enter
their name incorrectly, e.g. !Smith. So you could consider building your alphabet list like this:
<%
Ltr = UCase(Request.QueryString("Ltr"))
Set Conn = Server.CreateObject("ADODB.Connection")
conn.open "<connectionString>"
SQL = "SELECT DISTINCT LEFT(lastName,1) FROM nameTable ORDER BY LEFT(lastName,1)"
set rs = conn.execute(SQL)
do while not rs.eof
response.write " | "
rs0 = UCase(rs(0))
if rs0 <> Ltr then
response.write "<a href='Alpha.asp?Ltr=" & _
rs0 & "'>" & rs0 & "</a>"
else
response.write "<b>" & rs0 & "</b>"
end if
rs.movenext
loop
response.write " |"
SQL = "SELECT lastName FROM nameTable WHERE lastName LIKE '" & Ltr & "%'"
Response.Write "<p>" & SQL
With Access, you would simply use FileSystemObject's FileExists method to verify the location of the MDB file. e.g.:
<%
databaseName = "databaseName"
set fso = Server.CreateObject("Scripting.FileSystemObject")
if fso.fileExists(server.MapPath("/" & databaseName & ".mdb")) then
response.write "Database exists."
else
response.write "Database does not exist."
end if
set fso = nothing
%>
<%
databaseName = "databaseName"
set rs = conn.execute(sql)
if clng(rs(0))>0 then
response.write("Database exists.")
else
response.write("Database does not exist.")
end if
rs.close: set rs = nothing
conn.close: set conn = nothing
%>
How do I determine if a table exists in the database?
In Access, we can take one of our existing schema extractors and modify it slightly:
<%
tableToFind = "foo"
dbname = "/file.mdb"
if found then
response.write("Table exists.")
else
response.write("Table does not exist.")
end if
%>
set rs = conn.execute(sql)
if clng(rs(0))>0 then
response.write("Table exists.")
else
response.write("Table does not exist.")
end if
When working with an Access 97 database, you may have seen this:
This usually occurs when moving the database to a machine that doesn't have Jet 3.5 installed (or properly
registered). One workaround is to upgrade the machine in question to Access 2000 or greater, and make sure you
have MDAC 2.7 installed (which you can download from http://www.microsoft.com/data/).
Failing that:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\ISAM Formats
Name the key Jet 3.x and create the following values:
CreateDBOnExport REG_BINARY 00
IndexDialog REG_BINARY 00
OneTablePerFile REG_BINARY 00
(See Q310804 for more information.)
Next, find msrd3x40.dll, copy the path to this file, and again go to Start / Run and type:
regsvr32 <path>\msrd3x40.dll
Can I use the NZ() function without getting 80040E14 errors?
VBScript doesn't support the NZ() function. But here is a custom implementation, and a demonstration of its use:
<%
Function NZ(ValueIfNotNull, ValueIfNull)
NZ = ValueIfNotNull
If (IsNull(NZ)) Then NZ = ValueIfNull
End Function
str1 = "foo"
Response.Write NZ(str1, "bar")
str2 = NULL
Response.Write NZ(str2, "bar")
%>
A more common place you would expect to use NZ(), however, is returning the results of an Access query. You will
find that if you try to use NZ() and call the query from an ASP page, you'll get this error:
Let's say you have a numeric column, and want it to return 0 instead of NULL for all records that are NULL.
SELECT ColumnValue =
NZ(Column, 0)
FROM table ...
Since you can't use NZ() from ODBC/OLE-DB, you can use IIF instead:
SELECT ColumnValue =
IIF(ISNULL([Column]), 0, [Column])
FROM table ...
Or you can try IFNULL if you are going through ODBC, e.g.
SELECT ColumnValue =
IFNULL(Column, 0)
FROM table ...
If you are porting this code to SQL Server, you would use slightly different syntax:
SELECT ColumnValue =
COALESCE(Column, 0)
FROM table ...
or
SELECT ColumnValue =
ISNULL(Column, 0)
FROM table ...
(I prefer the COALESCE expression, which accepts multiple successive arguments. So you could say COALESCE(col1,
col2, col3, ...) and it would return the *first* column that is NOT NULL.)
Of course if NULL means the same thing as 0, you should consider running this query:
For more information on preventing NULLs from entering your ASP page, or avoiding NULLs in your data altogether,
■ when connecting to Access, use a JET connection string, instead of the generic Access driver, if you have JET
installed - and whatever you do, avoid using a DSN (see Article #2125)
■ if you absolutely must use a DSN, try to avoid using an underscore in the name
■ do not pass username and password information to an Access connection string unless they are required,
■ use server.MapPath() if you are not sure of the absolute location of your MDB file
■ if all else fails, make sure your server is up to date with all patches and security fixes (see Article #2151),
You may have seen this error when working with Access:
The error might also be system resource exceeded. This can be caused by at least two different things.
The first thing you should do is make sure that IUSR has sufficient permissions to the folder where JET*.tmp files
are created. This is typically in %windir%\temp\ or %systemdrive%\temp\ -- but you can check your exact location
by using SET at the command line, or checking the PATH properties in the Control Panel / System applet.
The second thing you should do is try to minimize the number of JET*.tmp files you need to create. For example,
when opening the database solely to retrieve data (no UPDATE, INSERT or DELETE), do so in adModeRead mode.
This way, Access will use the least amount of temporary files, since it doesn't need to prepare room or logic for
changing existing data. Here is an example of opening an Access database in adModeRead mode:
<%
cst = "<connection string>"
set conn = Server.CreateObject("ADODB.Connection")
conn.mode = 1 ' adModeRead
conn.open cst
%>
Why do I get script errors in Enterprise Manager's 'taskpad' view?
This is a known bug. I haven't seen it in a while, so I'm not sure if it has been fixed in Service Pack 2 or any of the post-
SP2 fixes that have been released. However, there is an easy workaround. Switch the database view to something else,
like Large Icons. Close Enterprise Manager. Open Enterprise Manager again, and switch the view back to Taskpad. This
When using a command object, or creating tables, you may be using named constants like adVarChar or adDate. If
you haven't defined these constants correctly (or forgot to include ADOVBS.inc), you might see this error:
Make sure your constants are defined correctly (see Article #2112 for better alternatives to including ADOVBS.inc)
and, further to that, make sure they're spelled correctly when you use them. When you can avoid using situations
which require the use of named constants (e.g. using a command object for executing a stored procedure), I
strongly recommend you do so (e.g. you could use a simple EXEC procedureName @param='whatever' SQL string
or
or
If you are using an ADODB.Recordset to UPDATE / INSERT (e.g. using the AddNew method), use a direct SQL
statement instead (see Article #2191). If using SQL Server, use a stored procedure (see Article #2201).
If you are using a DSN, try using an OLE-DB, DSN-less connection (see Article #2126).
If you are attempting to use MovePrevious on a forward-only / default recordset, consider the requirement again...
why would you need to MovePrevious? If the records are ordered 'backwards', change the query to reverse the
ORDER BY. If you absolutely must use MovePrevious, then consider using an AdOpenKeyset recordset.
If you are using ADO constants such as 'AdOpenDynamic', make sure you've included ADOVBS.inc (however read
If you are trying to 'page' through a recordset, using AbsolutePage and PageSize properties, see an efficient
If you are trying to binaryWrite image data to an ASP page that also has plain text content, consider retrieving this
data in a separate file and pointing to it via <IMG SRC>. You can also see Q276488 for ways to use ADODB.Stream
to return binary content from a database, without using the GetChunk method.
Why do I get 80040E24 errors?
or
or
If you are attempting to use MovePrevious or MoveLast, consider changing your ORDER BY statement to reflect the
order in which you want to process records. All recordsets being written out to ASP should be processed forward-
only; if you absolutely need to move backwards, consider creating an array using GetRows(), and traverse the array
in reverse order. If you are only interested in the last record, reverse your ORDER BY and use SELECT TOP 1 in your
SELECT query.
If you are attempting to get the RecordCount, see Q246636 for a potential explanation, and Article #2193 for
alternative methods.
If you've set the cursorType of an ADODB.Recordset, you should re-consider whether you need a recordset object
with special properties (see Article #2191) and, if so, make sure you include the appropriate constants from
This can happen if you are attempting to delete records while looping through an ADODB.Recordset object. Use
<%
' ...
conn.execute("DELETE table WHERE column ...")
%>
This will be more efficient code, and will alleviate the 'error in row' problem.
or
This is pretty straight forward; you're attempting to insert/update a row with a value that already exists, on a
column (or set of columns) that is defined as a primary key. By definition, a primary key can only have one 'copy' of
each unique value. Either investigate the coding logic that is generating these primary keys for insert/update, or re-
examine whether this column should, in fact, be a primary key. If the column(s) should be a primary key, consider
validating prior to insert... if someone is trying to insert a new row that already exists (at least in terms of the
Sounds like you have a foreign key defined, and are not satisfying its requirements. E.g. if you have table Parent,
with an Autonumber column, and table Child, with a foreign key that references table Parent's Autonumber column,
you should check that you are inserting into table Parent first, and that any updates to table Child are still
referencing a valid column in table Parent. (This should be done in the reverse order when deleting records... you
should delete from table Child first, then the 'master' record from table Parent.)
Why do I get 80040E54 errors?
This error can happen if you affect too many rows with an objRS.UpdateBatch call, or an ADODB.Recordset opened
with adLockBatchOptimistic. Please, do yourself a favor. If you want to update rows in a table, use this
Using a recordset, which is meant for *retrieving* data, to enforce updates on a table is wasteful, and leads to
errors such as this. For more information on avoiding a recordset for non-data-retrieving operations, see Article
#2191.
If you absolutely must use an ADODB.Recordset for updating rows, I encourage you to read Article #2191 again. If
you *still* find that you need to do this (e.g. pointy-haired boss syndrome), make sure you break it into chunks, use
a static or keyset cursor, and be sure that you always close your recordset objects / set them to nothing.
Why do I get 80070070 errors?
This is exactly what it sounds like... one of the disks that your ASP page is (perhaps indirectly) trying to write to is
full. It may be the drive the ASP page is running in, or creating a file in (which could be a share on another
machine). It may be the drive containing the temp folder(s) or the page file(s). If you are using CDONTS, it could be
Check the code, and analyze where it might be writing files; then, check those locations for sufficient space.
Why do I get 8002000A errors?
This is usually caused when you pass a numeric value too large for the data type, when using a ADODB.Command.
For example, sending 2^32 to a parameter using adInteger. A decent way to avoid this strong typing issue
altogether is to avoid the command object and use connectionObject.Execute("EXEC procName"). See Article #2201
for more information on executing stored procedures from ASP without using a command object.
How do I return row numbers with my query?
Often, people want to "invent" an identity, or rank, on the fly. So their original result set would look like this:
Lastname Firstname
-------- ---------
Evans Bob
Smith Frank
This would act like Oracle's ROWNUM, which isn't supported in SQL Server.
Of course, once you've retrieved this resultset into your ASP page, you could use a counter to increment as you're
processing.
SELECT
*
FROM
#table
ORDER BY
RowID
SQL Server
This is undocumented, so please don't rely on it, or use it in production code. The sp used may change or be
disabled in a future release or service pack / hotfix, or it could disappear altogether. So this tip is mainly for creating
your own diagnostic page to give you a quick overview of how many records are in each table in a specific database.
INSERT #foo
EXEC sp_msForEachTable
'SELECT PARSENAME(''?'', 1),
COUNT(*) FROM ?'
SELECT tablename, rc
FROM #foo
ORDER BY rc DESC
response.write "</table>"
end if
rs.close: set rs = nothing
conn.close: set conn = nothing
%>
Note that this will only count USER tables, not system tables. You could consider creating this procedure in the
master database and marking it as a system object; this way, you could execute it within the context of any
database, instead of having to create a copy of the proc for each database.
Access
Coming soon...
How do I store objects or components in session/application variables?
Please don't.
Many people seem tempted to store objects in the session, so that they don't have to create instances on each page.
They assume this is more efficient, but it is not. This is the absolute worst use of resources you can have
(particularly with ADO objects) and will significantly reduce your application's scalability. See these articles for more
details:
Here are some other KB articles that should help clarify the issues at hand (I hope you didn't expect this to be light
reading):
Q243544 INFO: Component Threading Model Summary Under Active Server Page
Q191979 PRB: VB Component Not Marked Apartment Produces ASP 0115 Error
Another possible outcome of storing an improperly threaded object in session or application scope is the following:
Note that it is possible to safely store a recordset in the session, by first converting it to its array form using
GetRows().
There is at least one exception to this 'rule' - it is acceptable to store the free-threaded version of the MSXML DOM
<%
Set DOM = Server.CreateObject("MSXML2.FreeThreadedDOMDocument")
DOM.loadXML "<somexml><somenode/></somexml>"
Set Application("DOM") = DOM
%>
DLL: How do I avoid 'Permission Denied' when re-compiling?
You may need to add start lines for other services such as smtpsvc or msftpsvc. The disclaimer of course, if you're
worried about stopping your web site traffic: don't develop DLLs on production machines.
You may also find the need to use KILL.EXE, if iisadmin refuses to be shut down. KILL.EXE is found in the NT and
Windows 2000 Resource Kits. As long as the .EXE is in your system's path, you can modify the above script as
follows:
In Windows 2000, you can use a much faster process by issuing the following command (again, at a command
iisreset
iisreset /stop
iisreset /start
If your object is hosted in MTS (or an application running in its own memory space), you should be able to just
unload that application / package. Similarly, for objects hosted in COM+, shutting down the application from
Component Services should unlock any holds IIS has on your DLL.
If you are developing your ASP files using Visual InterDev, then IntelliSense may be doing you a disservice. Since
this feature actually hooks into your custom COM objects (once you've created them using their ProgID in a
createobject() statement), this places a lock on the DLL similar to the one IIS places on it. So if you are editing an
ASP file which calls the DLL in question, you can recompile without rebooting by simply closing the ASP file.
When does ASP release COM objects?
Page-level
If you have page-level objects and are running IIS 4.0, they are not released when you issue the following
command:
<%
set object = nothing
%>
Page-level objects are NOT released until the page goes out of scope (and, by some reports, are not released at all if
IIS 5.0 cleans this up quite a bit; since it releases the object THE INSTANT you set it to nothing, there is suddenly a
Session-level
Session-level objects, unless you explicitly release them from the session yourself, are (allegedly) destroyed when
the session ends. This depends on how the session ends, of course. See Article #2078 for more information.
There are few scenarios where you should be using objects in session variables (see Article #2053). In about 99% of
all cases, objects should be created and destroyed at the page level.
How do I detect browsers without components?
We all know that to make a successful and attractive web site, we must strictly adhere to HTML 3.2 standards,
avoiding all temptations to use any browser- or platform-specific tags and features.
For the rest of us, we have to find innovative ways to present browser-specific content, yet still have it degrade
gracefully for everyone else. This can be relatively straightforward for individual elements, such as ActiveX controls,
cascading style sheets and even Java applets. But what if you want to present a certain paragraph only to IE4
users? or only let Netscape users see the contents of a specific <div> element? or make your body text 10pt for a
There is a way to do these things, without using COM objects, a database, or 200 tedious client-side document.write
commands. All you need is a little familiarity with the user agent string of the browser(s) you wish to target.
Let's start with getting at that string in the first place. It is available in the server variable "http_user_agent" and
can be accessed as follows (I convert this variable to lower case, in order to prevent ambiguity):
<%
myUA = Request.ServerVariables("HTTP_USER_AGENT")
ua = lcase(myUA)
Response.Write uA
%>
Be aware that there are literally thousands of unique user agent strings. Thankfully, the ones you will likely be most
concerned with share common characteristics. Also, depending on the purpose of your discrimination, most can be
grouped into similar categories.
Let's take DHTML for example. Say you want to have a headline that, when clicked on, presents an "abstract" and a
link to more information (instead of linking the user right away). The code would look something like this:
In IE3 and NN3, the description and link will always be visible, and in Netscape 4 it will never be visible. In most
cases the code above will generate a scripting error as well, which is not pretty. However, in IE4 or greater, the
description will be hidden until the user clicks on the headline. Wouldn't it be nice if you had the ability to regulate
that code, and ONLY insert the extra DHTML snippets for IE4 and IE5, preventing errors and other weird things in
Netscape?
Here's what you can do, based on the code above. Since all IE4+ user agent strings (including AOL and NeoPlanet
versions) are remarkably similar, you can search for "msie 4" or "msie 5" in the above strings. If this string is found,
you know that your visitor is using IE4 or better, and so it is safe to include your DHTML code. For example:
<%
myUA = Request.ServerVariables("HTTP_USER_AGENT")
ua = lcase(myUA)
ie4 = instr(ua,"msie 4")
ie5 = instr(ua,"msie 5")
ie6 = instr(ua,"msie 6")
ie7 = instr(ua,"msie 7")
' now that Netscape 7 is out, you know it's coming!
If you find that certain script elements you use only work on certain platforms (e.g. Win32), you can add extra
search elements into your discriminatory code, searching for key elements like "x11", "mac", "win95", "win98", or
"winnt" -- the possibilities for this kind of browser detection are really only limited by your imagination and
creativity. I use it almost religiously when writing any ASP application... from scripting functions, to adding CSS
support, to deciding whether or not to tell people they're using an outdated browser.
If manual browser detection isn't for you, I strongly recommend you take a look at CyScape's BrowserHawk before
you resign yourself to using Browscap. BrowserHawk is a very powerful server-side component which makes
browser detection a breeze and, unlike Browscap, maintains itself as new browser versions are released. If you are
using Browscap, however, make sure you keep it up to date as new browsers are released (see Article #2199).
How do I generate PDF files from ASP?
There are several components out that there that will help you generate PDF files dynamically - whether based on
ActivePDF
ABCPdf
Appligent
ASPEasyPDF
Dreamscape PDFReport
DynamicPDF
iSEDQuickPDF
PDFLib
RPT Software
http://www.pdfzone.com/
How do I determine if a COM object is installed?
As long as you know the ProgID (e.g. "Scripting.FileSystemObject"), you can easily determine if a COM object is
installed and available for you to use. In the following example, I am going to send an e-mail *if* ASPMail
<%
On error resume next
Set Mailer = Server.CreateObject("SMTPsvg.Mailer")
if err.number <> 0 then
Response.Write "ASPMail is not installed."
else
Mailer.RemoteHost = "<some remote SMTP host that allows relay>"
Mailer.FromName = "Me!"
Mailer.FromAddress = "[email protected]"
Mailer.Subject = "Test"
Mailer.BodyText = "Hello"
Mailer.Addrecipient "[email protected]","[email protected]"
if not Mailer.sendMail then Response.Write mailer.response
set Mailer=nothing
end if
%>
If you're not making code decisions based on the results of the check, you can just print out a result. For example I
have a status ASP page I check on all servers we add to our farm, to make sure all COM objects are installed,
registered, licensed where applicable, and work as coded. Here is the type of code I use:
<%
On error resume next
if isObject(Server.Createobject("SMTPsvg.Mailer")) then
response.write "ASPMail is installed."
else
response.write "ASPMail is not installed."
end if
%>
Of course, knowing the object is there is only half the battle sometimes; CDONTS is particularly gnarly to get
Your Browscap.ini file is probably outdated, or you are using an obscure browser. Unfortunately, developers are
pretty much left on their own these days when it comes to Browscap.ini. See Article #2199 for information on
If you want to avoid using Browscap, see some tips for manually detecting browsers in Article #2184.
In addition, CyScape makes a great browser detection component called BrowserHawk -- reducing the need for
much of the manual coding to provide proper browser detection. You can read about its advantages over the
If this is happening with your own DLL, or a third-party component, make sure IUSR_<machine_name> has access
rights to the DLL. See Q198432 for more info. With IIS 5.0, you might also want to make sure that the application
which is using the object is set to LOW under 'Application Protection' in Internet Services Manager.
Why do I get 8007000E errors?
This error can be caused by various things. Errors you might see are "System Resource Exceeded" and "Not Enough
Storage Is Available to Complete This Operation." You should see the following KB articles to see if any of these
Q248668 - BUG: "Not Enough Storage Is Available to Complete This Operation" with Oracle OLE DB
Q174776 - Index Server Queries Return Not Enough Storage Is Available Error
Q254759 - BUG: ListAvailableServers Method of the SQLDMO.Application Object Causes Error 0x800A000E
Otherwise, a sure way to alleviate the problem is to upgrade or reinstall the latest version of MDAC
(http://www.microsoft.com/data/).
Why do I get 800401F3 / 800A01AD errors?
This error is usually associated with trying to create an instance of a COM object using a ProgID that is not actually
registered on the machine. The error message might be one of the following:
4. using an MSWC or IIS sample component that is not installed (see Q249290 for more info)
See Article #2135 for information on determining if a COM object is installed on your server.
See Q241057 for information on setting your MTS package identity appropriately.
Where can I get a shopping cart for my web site?
If you want to roll your own cart, can't deploy custom COM objects on your web site, or must not support cookies /
session variables, you can look at the very simplistic sample code at http://www.aspfaq.com/cart/ to get you started
... nothing beats the education (and flexibility!) you gain from writing your own application. However if you lack the
time or ability, here is a list of free and commercial components, as well as some pure ASP solutions. I have used
ActiveCart and it is fairly well written; I have also heard very good things about IISCart.
a.shopKart
A+ Store E-Commerce
A-Cart
AceFlex B2C
ActiveCart 3.0
ASPBasket
ASPCart
BridgeCart
Cart32
CartEasy
ClickCart!
Comersus Cart
CyberShop
CyberStrong eShop
DevInteractive's Web-Cart
Envision eStore
iHTML Merchant
IISCart
Line9 Lite
Line9 Pro
MetaCart Free
MetaCart2 (PayPal)
opuslabs' ShopShop.Cart
QuadComm Q-Shop
SalesCart
StoreFront 5.0
Ultra Cart II
VP-ASP Cart
WebStores Developer
Many people view MSWC.BrowserType as the holy grail, taking care of all of their browser detection issues. I'm not
going to lie to you: Browscap.ini is a PITA. You have to update this file with umpteen new entries every time a new
There used to be several places where you could download a new version, which someone else updated, pretty much
within days of a new User Agent hitting the logs. Microsoft denied responsibility for this file almost immediately after
it was born (the version that ships with XP has User Agent strings containing "Windows 2000" and its highest
Navigator version is 4.0 Beta 2!); Cyscape has not updated theirs since February of 2000; and asptracker.com seems
Understandable. Given the explosion of browser versions, and the public availability of betas of betas of betas, it's
simply too time-consuming to sit there updating a file with new versions, and making sure your new entries don't
You can use Juan Llibre's version, or GaryKeith.com's version (note that this will only work with IIS 5.0, 5.1 and 6.0 -
4.0 is not supported). And you can download both, making your own adjustments as you see fit.
How do I pass server-side values to a client-side ActiveX control?
Many people go way overboard and try to dream up elaborate ways to have a client-side ActiveX control
communicating with the server to retrieve values. As long as these values are not dependent on user activities
AFTER the ActiveX control has been loaded, try something like this:
<%
strValue = "some string"
intValue = 5
Response.Write("<OBJECT clsid=[blah blah]>")
Response.Write("<PARAM NAME=INT VALUE=""<%=intValue%>"">")
Response.Write("<PARAM NAME=INT VALUE=""<%=intValue%>"">")
Response.Write("</OBJECT>")
%>
</script>
Can I code ISAPI filters / extensions with Visual Basic?
Not directly, because ISAPI filters and extensions require functionality only found in C++ (or Delphi). For more
information, see Developing ISAPI Extensions and Filters at MSDN Online. The reason most people ask about this is
that they want to know how to access http://theirsite/theirDLL.dll?whatever much like expedia does (e.g.
http://www.expedia.com/pub/agent.dll?qscr=cmfd&itid=&itdx=&itty=&ecid=&subm=0).
However, there are some links out there that allegedly help make this possible (though the overhead of hitting the
VB runtime with every request to your web server is something you definitely want to weigh in your testing). You
VB Bridge
SpyWorks
OLEISAPI (outdated)
I have not tried any of these methods, so do not vouch for their validity.
Should I instantiate my COM object with CreateObject or Server.CreateObject?
Server.CreateObject marshals the ASP intrinsic objects (e.g. Request, Session). When you use CreateObject, the
component does not have direct access to these objects. So if your component needs to access, say, the Response
object, you should use Server.CreateObject. In addition, Server.CreateObject correctly calls any (now obsolete)
onStartPage and onEndPage methods at the appropriate times, whereas CreateObject cannot be relied upon for
this.
CreateObject instantiates the object in the context of the scripting engine, as opposed to that of the ASP page itself.
This is fine in a client-server or single user application, but in a web page environment, the fact that CreateObject
bypasses automatic memory management makes it a poor and less scalable choice.
Server.CreateObject allows the component to participate in the same MTS/COM+ transaction as the ASP page itself,
and also provides process isolation. If you use CreateObject, the component can still take part in a transaction, but
the ASP page can not control nor determine the commit/rollback of the component's activities, since they are in
separate transactions.
CreateObject, in the case of IIS 4.0, is much better at cleaning up after itself. It gets garbage collected before the
page is finished, whereas Server.CreateObject waits until the page goes out of scope. This is no longer the case in
IIS 5.0 and above, which is much smarter about how it cleans up objects - as soon as you destroy them.
You cannot use Server.CreateObject when accessing a COM object through a firewall (e.g. in a distributed system).
So, the only time you would want to use CreateObject is if you had a very small user base, against IIS 4.0, with a
Here are several components designed to convert HTML to RTF, and/or vice-versa.
Doc2HTML
MyLittleWriter
Net2RTF
RTF2HTML
RTFConverter
TX Text Control
WordToy
If you want to do this yourself, there are several articles out there that can help.
This google message has a pretty thorough technical description of the RTF document itself.
And once you have an RTF format (e.g. a template) set up, Q270906 will get you up and running from ASP.
15 Seconds
And the following code samples are written in VB, but could be converted to ASP with little difficulty:
Developer Fusion
Planet Source Code
Why do I get 8000401A errors?
When running a component in an MTS or COM+ application, you may have come across one of the following errors:
or
The problem is that the 'Identity' set up for the application lacks sufficient privileges. This could be for several reasons, but before we get into
those, let's check out where this identity is configured. Open Control Panel, Administrative Tools, and double-click Component Services. Expand
COM+ Applications, right-click the application in question, hit Properties, and switch to the Identity tab. You should see a dialog similar to the
following:
One possible reason is that, when setting the identity for your application, you simply chose a user that doesn't have access to the DLL, or to a
Another potential cause is that someone changed the Windows password for that user (since the application won't automatically adjust for that
If this identity is set to the 'Interactive User', then the above error might occur when the user currently logged in to the machine is a peon, or
when nobody is logged in at all. This is the primary reason why this error is never detected in development... the user developing the COM+
application, and in complete control of the machine, is also the user logged in and running as the interactive user.
You should make sure to use a local user account with sufficient privileges, if that's what your DLL requires.
<%
set conn = server.createobject("ADODB.Connection")
response.write isObject(conn)
response.write isNull(conn)
response.write isEmpty(conn) & "<p>"
response.write isObject(conn)
response.write isNull(conn)
response.write isEmpty(conn)
%>
When the object has been created, isObject() returns true, while isNull() and isEmpty() both return false. When the
object has been destroyed, isObject() still returns true, and isNull() and isEmpty() both still return false. This
information isn't very useful, as it doesn't change during and after the lifetime of an object.
The criteria you are looking for is whether the object "is nothing", as demonstrated here:
<%
set conn = server.createobject("ADODB.Connection")
Note that some components, e.g. ADODB.Recordset, have properties that will tell you their state. For example, see
or
You are probably attempting to instantiate a COM object that is not installed - or is installed more than once. If this
is your own COM object, you might have to scrounge through the registry and delete instances of ProgIDs that point
to older version(s) of the object. Another option is to re-compile the COM object with a new ProgID. If you are not
using MTS/COM+, make sure binary compatibility is enabled... this way re-registering the component will replace
the old registry references, instead of adding a new GUID each time you compile.
If you are trying to use MSXML's ServerXMLHTTP class, you cannot use this object in Windows 95 or Windows 98, or
If you are attempting to create a stock ProgID, e.g. Scripting.FileSystemObject, you might have a corrupted script
ADODB.Command, or ADODB.Recordset, or ADODB.Parameter, then you probably have a corrupt MDAC install -
If none of the above suggestions help, you might try downloading FileMon and RegMon from www.sysinternals.com;
these great utilities can help you pinpoint problems in file or registry access points.
Exchange
If you are seeing this error code in the Event Log, associated with Event ID 11, then you may have an Exchange /
/ ASP):
Make sure you are not using anonymous access, and that users are authenticated with at least Basic Authentication
(Windows / IE clients should use Windows Authentication). See Q181739 for more information, and other possible
options. For some great information on enabling CDO within ASP, see http://www.cdolive.com/asp2.htm.
How do I handle MD5 from ASP?
You can include this JavaScript code in your ASP page, by using <script language=javascript runat=server> around
http://pajhome.org.uk/crypt/md5/md5src.html
ANEI-MD5
AspEncrypt
DigiSign 2.0
MD5DLL(free)
MD5 Toolkit
And if you are looking for an extended stored procedure for SQL Server 7.0 or 2000:
XP_Crypt
Why do I get 8007007E errors?
When using Server.CreateObject, you may have come across this error:
or
Make sure that the DLL you are trying to instantiate has actually been registered on the system. A DLL you created
in VB will work within the VB environment, but will not work from other interfaces (such as ASP) until you register
regsvr32 <path>\<file>.dll
Next, check IUSR permissions on the DLL, and any folders, files or executables it might be accessing.
If you are attempting to instantiate FileSystemObject, make sure that no anti-virus or other programs are blocking
access to script components, and try re-register scrrun.dll, with the following code at the command line:
regsvr32 %windir%\system32\scrrun.dll
If you are trying to use CDONTS.NewMail, use CDO.Message instead, especially if you are running Windows XP. See
or
This can happen when you try to use Server.CreateObject("InetCtls.Inet.1"). This object is really not recommended
for server usage; you should use XMLHTTP instead (see Article #2173).
This can happen when you try and use DAO to compact / repair an Access database. See Article #2190 for code that
To see other potential solutions for failed CreateObject situations, see Article #2134, Article #2323, Article #2316,
or
Make sure to use the SET keyword when creating objects (or calling methods that return objects), and that you use
Server.CreateObject as opposed to CreateObject, if you are expecting to use ObjectContext. According to Q243772,
you should also register your component in MTS as opposed to running it standalone, though I've never been forced
Make sure you actually instantiate an object using the NEW keyword, e.g.:
If you have complex looping or if/else structures in your component, make sure your nesting is complete and you
don't have any dangling structs that weren't picked up by the compiler.
Do not attempt to use ObjectContext functionality within Class_Initialize() / Class_Terminate (see Q250309).
Why do I get 80072EE5 errors?
First off, make sure your URL is valid ... e.g. begins with http:// or https:// and has a properly encoded querystring.
While version 4.0 is recommended, if you are using the 3.0 version of MSXML's ServerXMLHTTP class, and open a
URL with more than 2083 characters, you will get this error. If you upgrade to MSXML 3.0 SP1, you will get the error
0x80004005 (Unspecified error). See Q291088 for more information - though this article casually suggests
reproducing the error with 3000 characters... which doesn't really tell you as much as you might want to know.
Another way this can happen is if you pass a URL that contains more than two space characters, which have not
been URL encoded. So, in addition to making sure your URL is shorter than 2083 characters, you should be careful
to use Server.URLEncode on all querystring parameters to properly encode trouble characters, and avoid this error.
The only object I know of that will produce this error is ASPSmartUpload. The error is as follows:
This is usually due to a permissions problem. Make sure IUSR_<machineName> (or the authenticated user) has
write permissions to the folder indicated in the save method, and that this folder is referenced in absolute local path
This means that the site you were trying to parse either could not be found, and the component gave up; or it is
taking far too long for the page to finish loading. One way you can avoid this error is to set timeout values that are
<%
url = "http://www.espn.com/main.html"
set xmlhttp = server.CreateObject("MSXML2.ServerXMLHTTP")
resolveTimeout
This value is for the return of a DNS resolution (mapping the domain name to its representative IP address). The
connectTimeout
This value is for establishing a connection with the server, and the default value is 60 seconds.
Long integer. connectTimeout is applied to establishing a communication socket with the target server, with a
This value is the time allowed for sending an individual packet of data to the server. The default value is five
minutes.
receiveTimeout
This value is the time allowed for receiving an individual packet of data from the server. The default value is 60
minutes.
Why do I get 800A0030 errors?
If you are creating or using a Visual Basic 5.0 ActiveX DLL that returns a collection, see Q178777. If you are creating
or using a Visual basic 5.0 or 6.0 ActiveX DLL that returns a user-defined type (UDT), see Q224397.
If you get this error when instantiating an ADODB.Connection or other built-in component, re-install the latest
When trying to automate Word.Application, Excel.Application or even MAPI.Session from the server, you have come
Like the error states, one way to allow this is to set AspAllowOutOfProcComponents setting to 1, using MetaEdit.exe
(see Article #2227 for instructions on setting a similar value in the metabase editor). You can also see several other
Before changing the metabase, however, try setting IIS to allow scripts and executables, and play with the process
isolation of the site. These settings may be preferable to changing the metabase...
Why do I get 800C0007 errors?
It is possible that this error is due to a fauly script engine install (reinstall from this crazy URL). More likely,
however, you are using the MSXML2.DOMDocument object to retrieve XML data from a URL, and receiving the
following error:
If this is the case, you should use MSXML2.ServerXMLHTTP to retrieve the data, then use the DOMDocument object
to parse it. See Article #2173 for plenty of samples (and see Q281142 for more information about the error, and a
different workaround).
How do I upload files from the client to the server?
For your users to upload files, you must provide them with the following interface:
Keep two things in mind: (1) users of IE 3.02 or lower will need a Microsoft add-on to facilitate file uploads, and (2)
the enctype of the form precludes you from accessing non-file form elements directly (but most components deal
with this).
Now, to handle the uploaded file(s) from ASP, you typically need a component:
ASPUpload
ASPSmartUpload
ABCUpload
HugeASP Upload
Infomentum ActiveFile
MiniUpload
MS Posting Acceptor
SAFileUp
W3 Upload
There are also a few ways to upload files without traditional 3rd party components:
ASP 101
aspfaqs.com
ASPFree
ASPZone
Curt C
PureASP
StarDeveloper
For exact syntax to handle the incoming file(s), please see the documentation and sample code that will be available
<%
url = "http://wherever.com/"
response.write("<script>" & vbCrLf)
response.write("parent.framename.location.replace('" & url & "');")
response.write(vbCrLf & "</script>")
%>
Or this:
<%
url = "http://wherever.com/"
response.write("<script>" & vbCrLf)
response.write("parent.framename.location.href='" & url & "';")
response.write(vbCrLf & "</script>")
%>
I prefer the replace() function because it doesn't muck up the history list.
If it is a form you're submitting to, you can use the following (either for a frame or a new window):
<%
url = "http://wherever.com/"
dest = "<form method=post action='" & url & "' target=framename>"
' for a new window:
'dest = "<form method=post action='" & url & "' target=_blank>"
response.write(dest)
%>
How do I validate a credit card number in ASP?
While with ASP alone you can't verify that a credit card belongs to this person, that it isn't stolen, that the credit is
good or that the purchase price doesn't exceed the card's limit -- you can verify that it is a possible number before
<%
function isCreditCard(cardNo)
isCreditCard = false
lCard=len(cardNo)
lC=right(cardNo,1)
cStat=0
for i=(lCard-1) to 1 step -1
tempChar= mid(cardNo,i,1)
d=cint(tempChar)
if lcard mod 2 = 1 then
temp=d*(1+((i+1) mod 2))
else
temp=d*(1+(i mod 2))
end if
if temp < 10 then
cStat = cStat + temp
else
cStat = cStat + temp - 9
end if
next
cStat = (10-(cStat mod 10)) mod 10
if cint(lC) = cStat then isCreditCard = true
end function
'
' *************** TRY YOUR OWN **************
'
%>
How can I mimic a client-side POST from ASP?
Often you need to POST information to an ASP or other script without relying on the user to click a Submit button.
1. Write a component. We have one that does this (among many other things); however, it is proprietary, and
as such I can't distribute source. I will tell you it is an ATL component and makes a low-level connection.
2. Use an existing HTTP component, such as AspHTTP or MSXML (described in Article #2173)
<form
method=post
action='<script>'
name='myform'>
<input
type=hidden
name='myname'
value='myvalue'>
</form>
<script>
window.onLoad = document.myform.submit();
</script>
Note that it's fairly easy to build such a form with ASP, simply by iterating through the incoming form
elements
How do I iterate through a form collection?
It's kind of a pain to list all of the elements from a submitted form by referring to each one individually by name,
e.g.:
<%
response.write("a = " & request.form("a") & "<br>")
response.write("b = " & request.form("b") & "<br>")
response.write("c = " & request.form("c") & "<br>")
......
response.write("n = " & request.form("n") & "<br>")
%>
There's an easy way to manipulate this, particularly when you're troubleshooting and just want to write out all the
variables to the screen (or to a comment in the page). The following code will iterate (haphazardly, mind you)
<%
for each x in Request.Form
Response.Write("<br>" & x & " = " & Request.Form(x))
next
%>
And in JScript:
<%
for(f = new Enumerator(Request.Form()); !f.atEnd(); f.moveNext())
{
var x = f.item();
Response.Write("<br>" + x + " = " + Request.Form(x));
}
%>
What I mean by haphazardly is that, while this is a very easy way to get all of the elements in three lines, they will
not be in the order you expect... they'll be all over the place, and I have yet to see a valid explanation of how the
order is derived.
So another way to do this iteration actually preserves the order of the original form, by cycling through the form
items numerically (there is a count property of the form object). Here it is in VBScript:
<%
for x = 1 to Request.Form.count()
Response.Write(Request.Form.key(x) & " = ")
Response.Write(Request.Form.item(x) & "<br>")
next
%>
And in JScript:
<%
for (x = 1; x <= Request.Form.count(); x++)
{
Response.Write(Request.Form.key(x) + " = ");
Response.Write(Request.Form.item(x) + "<br>");
}
%>
[This technique also works for the QueryString and ServerVariables collections - in the ServerVariables collection,
Personally, I would have chosen "name" and "value" over "key" and "item." Of course, I don't have as much
Jay McVinney wrote an article on server side form validation for Wrox's ASPToday.com site. You can find it here, if
According to Microsoft, you should ALWAYS use the complete name of the collection you are retrieving items from.
Request Object
A lot of people leave out the actual collection name, merely putting request("item") either out of laziness or because
they're not sure which collection the value will come from. In my opinion, it is better programming practice to either
(a) use one method exclusively, or (b) do the following in such cases where you can't avoid using multiple
submission methods:
<%
item = Request.QueryString("item")
if item = "" then
item = Request.Form("item")
end if
%>
(Note: this is more efficient than querying for Request.ServerVariables("REQUEST_METHOD"), since the
The reason you should always use an explicit name is that if you don't specify, you can't be certain which collection
the variable is coming from (it also can be a much more significant performance hit to poll all of the collections
looking for your variable). Take the following example - play with each form, and see which collection is used as the
<%
response.cookies("HTTP_USER_AGENT") = "cookie"
%>
<form method=post action=order2.asp?HTTP_USER_AGENT=get>
<input type=hidden name=HTTP_USER_AGENT value=post>
<input type=submit value='form with all'>
</form>
order2.asp
----------
<%
response.write "The first value is from: " & _
request("HTTP_USER_AGENT")
%>
Note that in each case, the collection changes. If you had avoided using the cookie in the first line of the page, you
would have received the value in Request.ServerVariables("HTTP_USER_AGENT") only in the last case (where that
15 more characters... and if you're doing it often enough, you could create a function for each collection to ease
When you insert values into textboxes dynamically, you have to remember the same rules that hold true for basic
HTML. When you use a string, you must store it within quotes to prevent premature concatenation. :-)
<... value=<%=value%>>
Becomes:
<... value="<%=value%>">
One thing you want to be careful of is embedded quotes. You might try using ' or " as the delimiter, and eliminating
the other for possible entry (using client-side validation of course; the value is destroyed before you'd be able to
validate for it on the server side). If you have to allow both ' and ", you could consider using the rarely used "back-
apostrophe" (`). You can also try to user Server.HTMLEncode() on the value, before slipping it into the HTML
element.
If you do this:
And everything after 'foo' is ignored, because the browser interprets that as the end of the string.
How do I retrieve the name of the form that was submitted?
The name of the form is not passed with the form collection. You can do one of two things:
1. Use a hidden element in each form that will go to the same page, this way you can determine the form that
was submitted. This should be sufficient, unless you don't have control over the submitting forms. If you
don't, you have to explain to the people who DO have control over them, that you need some way to
2. Use Request.ServerVariables("HTTP_REFERER") to determine what page the request came from, which
should help you determine which form it was. HTTP_REFERER is unreliable, so I don't recommend this one
too highly.
Why does my form variable become 'value, value' instead of 'value'?
This is usually a case of multiple form fields on the previous page with the same name. For example, if you have a
If this happens, scan through your form looking for multiple instances of that input field name.
What is the size limit of a posted FORM field?
Any single request element is limited to ~102kb. If you exceed this limit, you may get 80000009 or 80004005
errors.
In addition, Microsoft has a few workarounds listed in Q273482 (note the warnings regarding usage of the code).
What is the limit on Form / POST parameters?
Unlike QueryString data, POSTed form data has a very high number of allotted characters. This is because the data
Note that there is no limit on the number of FORM elements you can pass via POST, but only on the aggregate size
While GET is limited to as low as 1024 characters, POST data is limited to 2 MB on IIS 4.0, and 128 KB on IIS 5.0.
Each name/value is limited to 1024 characters, as imposed by the SGML spec. Of course this does not apply to files
uploaded using enctype='multipart/form-data' ... I have had no problems uploading files in the 90 - 100 MB range
using IIS 5.0, aside from having to increase the server.scriptTimeout value as well as my patience! :-)
See Q260694 to learn how to adjust the limits of POST data (this deals with adding/modifying
Rather than try to reproduce it here, take a look at this great article:
http://ppewww.ph.gla.ac.uk/%7Eflavell/www/formquestion.html
How do I make one dropdown depend on another?
This is asked all the time. Often you want <SELECT> elements to populate based on previous selections. There are
many different solutions, based on what you want to do, and how much data you actually need to swap around. The
articles / samples below vary on a few things, mainly whether the re-populating is performed client-side or after a
http://www.macromedia.com/support/ultradev/ts/documents/client_dynamic_listbox.htm
http://www.aspfree.com/authors/adrian/twocombotwoform.asp
http://www.atgconsulting.com/doublelist.asp
http://www.atgconsulting.com/triplelist.asp
http://www.caoxuan.com/cxk/webart/goodies/selopt1.html
http://www.mattkruse.com/javascript/dynamicoptionlist/
http://www.infinitemonkeys.ws/infinitemonkeys/articles/javascript/997.asp
Please let us know if you know of other resources / methods to accomplish this functionality.
What is the limit on QueryString / GET / URL parameters?
Servers should be cautious about depending on URI lengths above 255 bytes, because some older client or
The spec for URL length does not dictate a minimum or maximum URL length, but implementation varies by
browser. On Windows: Opera supports ~4050 characters, IE 4.0+ supports exactly 2083 characters, Netscape 3 ->
4.78 support up to 8192 characters before causing errors on shut-down, and Netscape 6 supports ~2000 before
Note that there is no limit on the number of parameters you can stuff into a URL, but only on the length it can
aggregate to.
Keep in mind that the number of characters will be significantly reduced if you have special characters (e.g. spaces)
that need to be URLEncoded (e.g. converted to the sequence '%20'). For every space, you reduce the size allowed in
the remainder of the URL by 2 characters - and this holds true for many other special characters that you may
Keep in mind, also, that the SGML spec declares that a URL as an attribute value (e.g. <a href='{url}'>) cannot be
more than 1024 characters. Similarly, the GET request is stored in the server variable QUERY_STRING, which can
If you are hitting a limit on length, you should consider using POST instead of GET. POST does not have such low
limits on the size of name/value pairs, because the data is sent in the header, not in the URL. The limit on POST
size, by default, is 2 MB on IIS 4.0 and 128 KB on IIS 5.0. POST is also a little more secure than GET -- it's tougher
(though not impossible) to tinker with the values of POSTed variables, than values sitting in the querystring.
See Article #2223 for more information on using POST to overcome limitations on length.
When I'm uploading files, why can't I access the request.form collection?
You will get errors from IIS in both of the following cases:
■ You try to access the form collection (request.form) after calling request.binaryread, or
■ You try to access the incoming binary stream after calling request.form.
or
The two major commercial upload components have their own .form collection that you can call, and this is found in
their documentation:
ASPUpload
SA-FileUp
Dundas.Upload
If you wish to roll your own upload method(s), take a look at this helpful article:
http://msdn.microsoft.com/library/en-us/dnasdj01/html/asp0900.asp
How do I perform spell checking from a web page?
When a user enters data, it'd be nice if you could find possible typos in their text and offer suggestions. Here are
ASPFree.com
Chado SpellServer
Note that some of these solutions require licenses for Microsoft Word.
When I have multiple submit buttons, how do I tell which was clicked?
You can use a hidden input type, coupled with a client-side handler that intercepts the submit.
</form>
<script>
function setButton(s)
{
document.form1.buttonChoice.value = s;
document.form1.submit();
}
</script>
<%
for each x in request.form
response.write "<p>" & x & " = "
response.write request.form(x)
next
%>
How do I disable certain FORM elements?
You can use the DISABLED attribute to turn off input for elements, for example:
One side effect is that this attribute is not recognized by Netscape, so those users will still be able to edit the text.
Also, FORM elements marked as "disabled" are removed from the collection, so the page you're posting to won't be
able to collect the value (a messy workaround would be to also place the value in a HIDDEN element).
A workaround for all of this is to use the READONLY attribute, for example:
This will solve both side effects mentioned above. However, some have noted that this doesn't change the
appearance of the field (it still *looks* editable). To do this also, you can add a style to the field (which, admittedly,
Many people want to know how to display "Option 1" on the resulting page, in addition to retrieving the value
parameter. I have heard of examples where they resorted to a database lookup based on the value, since the
SELECT element does not support passing the text, only the value. Here is a decent workaround:
<%
foo = Request.Form("foo")
foo = split(foo,":")
Response.Write "Value was " & foo(0) & "<br>"
Response.Write "Display was " & foo(1)
%>
You may want to choose a delimiter other than ":", for example if your text might contain that character. There
should always be a character you can choose as a delimiter that has little to no chance of ever being inside the
string.
How do I make form fields read-only?
The first two require "recent" browsers (left as an exercise to the reader); the third requires a JavaScript-enabled
browser.
Why won't my <TEXTAREA> display the data I passed to it from ASP?
<TEXTAREA VALUE='<%="stuff"%>'></TEXTAREA>
A TEXTAREA element is a container, like a <TD> or <SPAN>, so you put contents BETWEEN the opening and closing
tags, as follows:
<TEXTAREA>
<%="stuff"%>
</TEXTAREA>
How do I disable IE's Autocomplete feature?
While this isn't an ASP question per se, it is often requested by developers creating an ASP site. Sometimes you just
don't want IE to remember previous entries in an input field. You can turn this feature off as follows:
You can also turn it off at the form level, like so:
<FORM AUTOCOMPLETE="Off">
How can I programmatically interfere with the INPUT TYPE=FILE element?
When uploading a file, users are constantly asking if they can programmatically populate the path in an INPUT
TYPE=FILE element. The fact is, users must input this file themselves by typing it in manually or using the provided
Browse... button. Otherwise, it would be fairly trivial for malicious users to steal files from users' hard drives...
How do I pass x-y coordinates to ASP, after the user clicks an image?
Use an image input type as your submit button. With the following code:
<%
Response.Write("The coordinates were: " & Request.Form("coords.x"))
Response.Write(", " & Request.Form("coords.y") & ".")
%>
How do I submit a form to a new window, with the control of window.open()?
Many people have asked how they can submit a form to a new window. This is fairly trivial to accomplish; you can
<form
target=_blank
action=whatever.asp>
However, some would like to submit their forms to a new window that they have a little more control over.
This is not really an ASP issue, as the solution could be used in any web-based technology (including plain HTML).
However, here is a way to submit a form to a new window, and have control over parameters like width, height,
toolbar, etc.:
<form name=form1
method=post
action=whatever.asp
target=myNewWin>
<input type=button
value=' Submit '
onClick='sendme();'>
</form>
<script>
function sendme()
{
window.open("","myNewWin","width=500,height=300,toolbar=0");
var a = window.setTimeout("document.form1.submit();",500);
}
</script>
Why do I get 'HTTP 405 - Resource Not Allowed' errors?
HTTP/1.1 Error
405 Method Not Allowed
The method specified in the Request Line is not allowed for the resource identified by the
request. Please ensure that you have the proper MIME type set up for the resource you are
requesting.
Please contact the server's administrator if this problem persists.
You can get this when your form doesn't have a name, or a method -- particularly in Netscape.
You can get this if you try to submit a form to an HTM, HTML or other 'static' page type.
You can get this when your form doesn't have an action parameter, or it is left blank, if the form is in the default
http://yoursite/yourfolder/default.asp. See Q216493 for more information (and not ethat you don't have to be using
If you are using Visual InterDev's preview/design modes, switch to your browser. Don't use your editor to preview
If you are using Posting Acceptor to upload files, make sure IUSR has full permissions on cpshost.dll; or, better yet,
You can get this if you have FrontPage Server Extensions installed, and the _vti_bin lacks 'execute' permissions. See
Q238461 for more information. Also, see Q206046 and Q229295 for other FrontPage-related articles.
One of the greatest obstacles faced by ASP developers is the ability to dynamically include files. Since #include
directives are processed BEFORE the ASP code, it is impossible to use if/else logic to include files.
Or is it?
Depending on what you are doing within your include files, and how many you are including, it IS possible to use
if/then logic to make use of includes. While it is not feasible for all situations, and it is often an inefficient solution, it
Let's start with two sample HTML files, 1.htm and 2.htm. For the sake of simplicity, they contain very simple code:
Now, let's set up some conditional includes! For this example, we'll assume you want to include the file 2.htm if your
page is passed a parameter of 2, otherwise include 1.htm. Here is an example of how you could accomplish this
task:
<%
if request.querystring("param")="2" then
%>
<!--#include file="2.htm"-->
<%
else
%>
<!--#include file="1.htm"-->
<%
end if
%>
Now try accessing the page in these three ways, and experiment with the results:
http://yourserver/file.asp?param=1
http://yourserver/file.asp?param=2
http://yourserver/file.asp
Of course you can perform this kind of include logic based on various conditions, such as the date, the time, or the
user's browser.
Please note that in the above example, BOTH include files are processed. So, the more options you have, the less
efficient this kind of solution will be. When the number of possible includes start getting a bit high, you could try
<%
if request("param")="2" then
filespec = "2.htm"
else
filespec = "1.htm"
end if
filespec = server.mapPath(filespec)
scr = "scripting.fileSystemObject"
set fs = server.createobject(scr)
set f = fs.openTextFile(filespec)
content = f.readall
set f = nothing
set fs = nothing
response.write(content)
%>
The FileSystemObject is useful for many things, and it fits into the dynamic include paradox quite nicely.
IIS 5.0 / ASP 3.0 supports "dynamic includes" with Server.Execute. Here is a sample using the above scenario:
<%
if Request.QueryString("param")="2" then
Server.Execute("2.htm")
else
Server.Execute("1.htm")
end if
%>
If you have IIS 5.0 at your disposal (IIS 5.0 and ASP 3.0 will ONLY run on Windows 2000), look this method up in
the VBScript documentation. They can be handy for a quick and dirty solution to the dynamic include problem.
Please see Article #2006 for some of the common problems people have with these methods.
Where can I find info on working with files and FileSystemObject?
http://msdn.microsoft.com/library/en-us/script56/html/FSOoriFileSystemObject.asp
There are samples for creating, copying, deleting and renaming files, as well as reading and appending the text
within them.
If you are not running Windows 2000 or Windows XP, make sure you have the latest scripting engine before trying
any of these samples. Several methods and properties of the FileSystemObject were not available in the original
implementation.
How do I create / manipulate images from ASP?
There are a few components that allow you to do this; here are two of them:
ShotGraph
ASPImage
The following components are specialized for creating charts and/or graphs from ASP:
ASPGFX
ChartDirector
Chart FX
DundasChart
IntrChart
The following free component will tell you the properties of a local image file:
ImageSize
http://www.learnasp.com/learn/graphicdetect.asp
I found this script a bit buggy with JPG files produced by certain filters in Photoshop. Also, thanks to Bryan O'Malley
and Thomas Honoré Nielsen, here is a correction of the ReadJpg function from the above link:
Function ReadJPG(file)
Const maxJpegSearch = 2048
Dim fso, ts, s, HW, nbytes, x, SOF
HW = Array("","")
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.OpenTextFile(Server.MapPath("/" & file), 1)
s = ts.Read(maxJpegSearch)
ts.Close
for x = 1 to Len(s) - 1
if Asc(Mid(s, x, 1)) = &hFF then
if Asc(Mid(s, x + 1, 1)) >= &hC0 AND _
Asc(Mid(s, x + 1, 1)) <= &hCF AND _
Asc(Mid(s, x + 1, 1)) <> &hC4 then
SOF = x
exit for
end if
end if
next
if SOF > 0 then
s = Mid(s, SOF + 5, 4)
HW(0) = HexToDec(HexAt(s,3) & HexAt(s,4))
HW(1) = HexToDec(HexAt(s,1) & HexAt(s,2))
else
HW(0) = -1
HW(1) = -1
end if
ReadJPG = HW
End Function
One of IIS' great built-in tools is FileSystemObject. Unfortunately, there is no native way to produce a file listing in
the order you specify. For example, consider the following code snippet:
<%
folder = "C:\"
This produces a list of files in seemingly random order. It is not ordered by name, file size, type, date modified, date
Here are three ways you can order your files alphabetically; the first uses a VBScript sort routine, the second uses a
Here is the VBScript version (thanks for the help with this, Luke Magnus):
<%
folder = "C:\"
varchar(255)):
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
set fso = Server.CreateObject("Scripting.FileSystemObject")
set fold = fso.getFolder("c:\")
for each file in fold.files
sql = sql & "INSERT INTO fileTable(filename)"
sql = sql & " VALUES('" & file.name & "'); "
next
conn.execute(sql)
sql = "SELECT filename FROM fileTable ORDER BY filename"
set rs = conn.execute(sql)
do while not rs.eof
filename = rs("filename")
if left(filename,5) = "D0101" then
fileURL = server.urlEncode(filename)
response.write("<a href='Documents/" & fileURL
response.write("'>" & filename & "</a><br>")
end if
rs.movenext
loop
rs.close: set rs = nothing
conn.execute("DELETE FROM fileTable")
conn.close: set conn = nothing
%>
The bonus with the database version is that you can insert other elements (such as extension or DateLastModified),
and order in different ways (file type; creation date, ascending or descending; reverse alphabetical; etc...). A slightly
more efficient way to generate a list of files ordered in whatever way you like would be to use a recordset opened as
adOpenDynamic:
<%
CONST adFldIsNullable = &H00000020
CONST adVarChar = 200
CONST adDate = 7
CONST adOpenDynamic = 2
CONST adUseClient = 3
With rs
.CursorLocation = adUseClient
.CursorType = adOpenDynamic
.Fields.Append "filename", adVarChar, 255, adFldIsNullable
.Fields.Append "filemod", adDate, 32, adFldIsNullable
.open
.close
end with
response.write "</table>"
set rs = nothing
end if
set folder = nothing
set fso = nothing
%>
How do I make the filename correct for the client, when using binaryWrite?
If you have an ASP file that dynamically produces files in binary for download, you've probably noticed that when the
client saves the file, it gets renamed to yourfile.asp instead of thefile.ext. Here's how you can avoid this... before
<%
fn = "thisfile.ext"
Response.AddHeader "Content-Disposition","attachment;filename=" & fn
...
response.binarywrite(binarydata)
%>
Note that some 3rd-party download managers will still override this setting, and save the file as yourfile.asp. You
can try the utlities mentioned in Article #2232 to see if any suit your needs.
Why does FileSystemObject hang all of a sudden?
Likely, you (re-)installed or re-configured Norton Anti-Virus, and as a result, a call within ASP to OpenTextFile hangs
and/or other methods stop responding. This program has an option called 'script blocking' which, among other things,
prevents FileSystemObject from working on the local file system. With the default setting, Norton raises a 'prompt'...
and sits there waiting and waiting for a response from ASP (which it can obviously never get). To stop this from
happening, go to Norton's Options screen, click on 'Script Blocking' and uncheck 'Enable Script Blocking'...
Keep in mind you shouldn't be running client applications like this on a commercial web server... hopefully this issue is
Microsft KB #Q295375
Symantec KB #2001031311101006
How do I get the name of the current URL / page?
To retrieve the name of the current file, you can use any of these:
<%
Response.Write Request.ServerVariables("SCRIPT_NAME") & "<br>"
Response.Write Request.ServerVariables("PATH_INFO") & "<br>"
Response.Write Request.ServerVariables("URL") & "<br>"
%>
To make that path local (for example, to use with FileSystemObject), just apply the server.mappath() method to the
result.
To get the entire URL, including the http:// or https:// prefix, you can do this:
<%
prot = "http"
https = lcase(request.ServerVariables("HTTPS"))
if https <> "off" then prot = "https"
domainname = Request.ServerVariables("SERVER_NAME")
filename = Request.ServerVariables("SCRIPT_NAME")
querystring = Request.ServerVariables("QUERY_STRING")
response.write prot & "://" & domainname & filename & "?" & querystring
%>
<%
scr = Request.ServerVariables("SCRIPT_NAME") & "<br>"
loc = instrRev(scr,"/")
scr = mid(scr, loc+1, len(scr) - loc)
response.write scr
%>
Now. If your file is an #INCLUDE within another file, the above scripts will produce the name of the CALLING file
(since the included file is first integrated into the calling script, then the ASP within it is all executed in the context of
the 'parent' file). One way you can work around this is to re-populate a current_filename variable before loading
<%
current_filename = "filetoinclude.asp"
%>
<!--#include file='filetoinclude.asp'-->
(And no, don't try passing current_filename as a variable to the #INCLUDE directive; see Article #2042.)
Then, in filetoinclude.asp:
<%
Response.Write "Current file: " & current_filename
%>
Of course, you could just as easily hard-code the filename inside of each include file. But I suppose that solution
would somewhat defeat the purpose of retrieving that information at least somewhat dynamically.
Why do I get 'Permission Denied' errors with FileSystemObject?
Permission Denied, when dealing with the FileSystemObject, has to do with local file and folder permissions for the
To open a text file forReading (1), IUSR_<machine_name> must have read access in the folder the file is located.
To open a text file forWriting (2) or forAppending (8), or to create a text file, IUSR_<machine_name> must have
Rather than alter the permissions for IUSR_<machine_name>, giving him/her access to various parts of your
machine, my recommendation is to first pursue placing any such text files within the web structure, or in any other
place that IUSR_<machine_name> already has access. The more you alter file- and folder-level permissions to suit
your application, the more open your system will be for attack... and the more maintenance you will have to do if
Unfortunately, FSO does not have a "renameFile" method. However, there are two ways around this. The name
<%
set fso = server.createobject("scripting.filesystemobject")
set fs = fso.GetFile("c:\boot.ini")
fs.name = "c:\boot.old"
set fs = nothing
set fso = nothing
%>
You can also rename a file using FSO's movefile method. Simply enter the old name and the new name as
<%
Set fso = Server.CreateObject("Scripting.FileSystemObject")
fso.moveFile "c:\boot.ini", "c:\boot.old"
Set fso = Nothing
%>
I strongly recommend writing a reverse script to change this file back before you reboot, or else use another
filename on your machine. (Hopefully, though, your IUSR doesn't have proper permissions for the above script to
work.)
Why do I get 'Path not found' errors with FileSystemObject?
This error is usually pretty self-explanatory. You tried to access a file/folder that doesn't exist, or you tried to access
a file that does exist but you specified the wrong folder. (This message is also incorrectly displayed when you to try
to access a text file or folder on another machine via a UNC share... the error message should be Permission Denied.
2. Use OpenTextFile with the "create" flag to true, instead of assuming the file exists (or using convoluted logic
3. When folders are in the web structure, use server.mappath("/virtual/") to get the local location, since this
can be vary on different machines. This is an expensive call, so you might want to store the webroot
(server.mappath("/")) in an application variable instead of calling the mappath method every time you need
it.
How do I prevent people from 'leeching' my images?
Many people have expressed concern that other users have tried to 'steal' or 'leech' their images, simply by
referencing them in their own HTML pages. For example, you can easily put the following line of code into your own
<IMG SRC=http://www.aspfaq.com/img/nld.jpg>
Here is one way to get around this, making it easy for you to host all of your images and not worry about others
being able to 'borrow' them. Place your image file(s) in a folder, either in an unknown spot in your webroot or
outside of your webroot altogether, and then use the following code (which plops c:\okay.gif into the browser if the
request came from the same domain, otherwise it returns c:\warning.gif - which can be as effective as you make it
<%
ref = lcase(Request.ServerVariables("HTTP_REFERER"))
if instr(ref, lcase("YOUR_DOMAIN_NAME"))>0 then
fn = "okay.gif"
else
fn = "warning.gif"
end if
<IMG SRC=imageFile.asp>
You may want to add other clauses for other domains your site may be referenced as, such as localhost, 127.0.0.1,
its 'real' IP address, etc. etc. You may also want to generate the IMG SRC tags using known parameters such as
width and height (for the tag itself) and the 'okay' filename (to pass via the querystring).
This method will definitely affect performance, so you will have to measure for your environment and decide whether
Now, as we know, it's virtually impossible to prevent people from taking your images when they browse your web
site, e.g. saving them to their hard drive and re-using them. While they will always be able to use the PrintScreen
button (or one of the many shareware / commercial screen scrapers out there), there are several things you can do
to prevent the casual user from using the most common methods:
http://pubs.logicalexpressions.com/Pub0009/LPMArticle.asp?ID=41
Can I place a file on a user's hard drive without bothering them with a prompt?
NO.
How do I get a list of a folder's subfolders?
Many people expect FileSystemObject to just return a list of subfolders and display them to the screen. It's almost
that easy:
<%
folderspec = server.mappath("/")
</script>
Note that the JScript version returns the full folder path, while the VBScript version only returns the folder name
itself. You can alter the VBScript version to return the full path, by omitting the ".name" from the Response.Write
statement. You could also parse the folder name out of the string JScript returns. Or you could read the docs (the
way I clearly *haven't* done), and tell us the proper method of returning only the subfolder name in JScript.
Why do I get permissions errors after upgrading to Windows XP?
After switching from Win95, Win98 or WinME to Windows XP, you may find that previously functional ASP pages are
now choking on code that uses FileSystemObject or MS Access (usually 'Permission Denied' or 'Operation must use
an updateable query' errors). To straighten this out, you need to apply appropriate permissions for
In Windows Explorer, right-click the folder in question, hit Properties, and select the Security tab. If the Internet
Guest Account is not listed, click the Add... button and type IUSR_<machine_name> into the textbox, and click OK.
Now select the Internet Guest Account, and check the permissions appropriate. For most web applications, Read and
Write is sufficient. You may have to do this for individual files as well. One one of my work machines, Photoshop 6.0
saves files in a weird manner, so that IUSR cannot access them until I fix their permissions. Still investigating that
one.
Open up Windows Explorer, open the Tools menu, choose Folder Options, and go to the View tab. The last item in
the list is called "Use simple file sharing (recommended)" - which is actually NOT recommended if you want to get
any work done. Uncheck this box, click Apply and OK, and try the above steps again.
It's possible this was hidden from you by a group policy (perhaps the OEM set the machine up that way, or your
network admin doesn't trust you). Assuming you have appropriate permissions on the machine itselgf, go to Start,
Run... and type in "gpedit.msc" without the quotes. This launches the Group Policy Editor. Navigate to User Config /
Administrative Templates / Windows Components / Windows Explorer / Remove Security Tab. Read about this
setting before just changing it, and back up your system before applying any changes (just in case).
Ummm, Houston?
If those steps don't work, I'm at a loss. I don't run XP Home, and never will, so I'm not sure how much more digging
Often, ASP developers want to put common functions in a place accessible to both client-side and server-side
scripts. There are some challenges with this, including the fact that several server-side objects (such as Response,
Request) are not available on the client, much like several client-side elements (such as document, window and
msgbox) are not available on the server. But if you write a routine that's generic enough, it can make sense to have
it serve both the client-side and server-side scripts - reducing maintenance in the long run.
If you're using IIS 5.0 or better, you can achieve this simply as follows. Let's say you have a .js file called
'whatever.js':
Now you can call this from both server-side and client-side JScript like this:
<script language=jscript>
document.write("Client: "+returnValue());
</script>
I'm going to provide an example of using a VBScript routine in server-side and client-side VBScript, as well as using
a JScript routine in server-side and client-side JScript. I'm not going to attempt to crosswire the languages, nor am I
going to try to deal with workarounds for swapping out document / response depending on the scope.
Here is an example of using a VBScript routine in client-side and server-side script. You'll need a file called
fixString.asp:
<%
function fixString(str)
fixString = replace(str,"Bob","the coder formerly known as Bob")
end function
%>
<!--#include file='fixString.asp'-->
<%
response.write fixString("Do you think that's okay with Bob?")
set fso = Server.CreateObject("scripting.FileSystemObject")
set fs = fso.OpenTextFile(server.MapPath("fixString.asp"))
f = fs.ReadAll()
fs.close: set fs = nothing: set fso = nothing
<script language=vbscript>
msgbox fixString("I don't think Bob will mind."),64,"Bob"
</script>
Here is an example of using a JScript routine in client-side and server-side script. You'll need a file called
fixStringJS.asp:
<script language=JScript runat=server>
function fixString(str)
{
return str.replace("Bob","the coder formerly known as Bob");
}
</script>
<!--#include file='fixStringJS.asp'-->
<Script language=JScript runat=server>
Response.Write(fixString("Do you think that's okay with Bob?"));
var fso = new ActiveXObject("Scripting.FileSystemObject");
var fs = fso.OpenTextFile(Server.MapPath("fixStringJS.asp"));
var f = fs.ReadAll();
fs.close(); var fs = null; var fso = null;
f = f.replace(" runat=server","");
Response.Write(f);
</script>
<script language=Jscript>
alert(fixString("I don't think Bob will mind."));
</script>
Now, this fictitious example doesn't deal with a serious limitation in JScript's implementation of replace - it only
replaces the *first* instance of the search string, not *all* instances (like VBScript). Not sure which language
implemented it wrong; according to the ECMA docs, JScript is correct. But I prefer the behavior of VBScript. I'll leave
This often happens because you used a VBScript "friendly name" constant in place of its integer equivalent. Visual
Basic understands these friendly names, such as FileSystemObject's 'forAppending' and 'forWriting' constants.
An easy solution is to add this line to any include files you use in every page (or else add it at the top of every page,
<%
Const ForReading = 1, ForWriting = 2, ForAppending = 8
%>
Another possible cause is trying to use a string operation (like Mid(), InStr(), Left() or Right()) on a NULL value. So
for example:
<%
response.write Left(rs("column"), 10)
%>
Should be:
<%
if len(rs("column")) > 0 then
response.write Left(rs("column"), 10)
else
response.write " "
end if
%>
If you're having these problems with NULL values coming out of a database, see Article #2150.
Another possibility is that you are coming over from JavaScript, or otherwise think that string lengths are 0-based.
The following code sample will cause this error:
<%
str = "foo"
response.write Mid(str, 0, 1)
%>
Similarly, passing a starting argument of 0 to the Instr() function will cause the same error. To solve this problem,
Usually this means you used an absolute path to a drive that either doesn't exist, or is not currently enabled.
Common scenarios for this are removable media like ZIP drives or CD-Roms, network shares that are not always
available, or simply fat-fingering a drive letter in your ASP code (it's okay, it happens to all of us!).
Of course, it is possible that this error will also really mean what it says: that your hard disk has a failure of some
sort. If this is the case, you have more to worry about than a malfunctioning ASP page. <G>
How do I prevent people from 'leeching' my CSS or JS files?
I've heard a few people complain that external sites have linked to their CSS or JS files, which is both a waste of
bandwidth and a potential copyright violation. Unfortunately, there is no straightforward way to prevent this from
happening, and have fun trying to pursue it legally -- I was an expert witness in an Internet-related trial a few years
ago, and it's amazing how much crap a lawyer can get away with it simply because the judge has no technical savvy
whatsoever.
In any case, there are ways to prevent people from borrowing these file types, and it's very similar to the method
The basic concept is that you serve up the CSS or JS from ASP, and the ASP page checks the referer to make sure
it's on the same domain. So, for example, you can do this:
<%
ref = lcase(request.serverVariables("HTTP_REFERER"))
if instr(ref,"localhost")>0 then
response.write "document.write('HELLO');"
end if
%>
As with the images solution, note that this will be a bigger performance hit... and may affect how JS / CSS files are
cached.
How do I prevent that ugly red x when an image is missing from my server?
The 'red x' is a fairly telltale symptom of (a) a poorly maintained file system, or (b) poorly written HTML code. :-)
There is a way to prevent this nasty little red x from showing up. Read Article #2162, which describes how to set up
a custom 404 page to capture mistyped URLs and missing files. You can modify the example there to include the
following code:
<%
qs = Request.ServerVariables("QUERY_STRING")
if left(qs,4)="404;" then qs = right(qs,len(qs)-4)
if qs <> "" then
qsf = lcase(qs)
if right(qsf,4)=".gif" or right(qsf,4)=".jpg" or _
right(qsf,5)=".jpeg" or right(qsf,4)=".png" then
Server.Transfer("/404.gif")
' use Response.Redirect for IIS 4.0
end if
end if
%>
Now any bad request for an image file will result in the 404 graphic to be displayed. For example:
Just make sure that you have a file called /404.gif, or else the custom error page will go into an infinite loop.
How do I determine the owner of a file?
Here's a little script that will list out the files in a folder, and show the filename, type, size,
created/modified/accessed date, owner, and file attributes. This code has been tested on Windows 2000, XP
<style>
body,td,th { font-family:tahoma;font-size:8pt }
</style>
<%
filesz = 0
fileown = 8
srv = request.serverVariables("SERVER_SOFTWARE")
if instr(srv,"/5.0")>0 then
filesz = 1
filecr = 6
fileac = 7
fileat = 4
filesz = 1
filecr = 4
fileac = 5
fileat = 6
end if
folder = "c:\"
Set sh = Server.CreateObject("Shell.Application")
set fl = sh.Namespace(folder)
set fl = nothing
set sh = nothing
response.write "</table>"
else
response.write "Unable to process results."
end if
%>
You may need to make sure that IUSR_YourMachine (or the authenticated user) has appropriate access to the folder
in question. Also, be aware that hidden or protected system files (such as boot.ini) won't be shown unless your
Thanks to "Reverend Brad" for pointing me to the .path property, which at least put aside the mystery of the
missing extensions that I encountered when initially putting together this article.
Here is a full listing of the GetDetailsOf elements - they are different on Windows 2000 than on Windows XP / .NET
Server.
Notice that some are particularly useful for Word or other MS Office documents. Note that Name doesn't always
contain the extension, just in case you try to use it and don't understand why you get inconsistent results.
0 Name Name
1 Size Size
2 Type Type
8 Owner Owner
9 Author ???
10 Title Author
11 Subject Title
12 Category Subject
13 Pages Category
14 Comments Pages
15 Copyright Copyright
27 ??? Caller Id
28 ??? Routing
You don't. You can't use ASP to read, write, copy, move or delete files on the client's machine. ASP runs on the
server; what you need to handle this is a client-side technology, such as an ActiveX control or a signed Java applet.
If you want to get a file from the client's system to your server, you must ask them to upload it (see Article #2189).
And no, you can NOT pre-populate or otherwise programmatically interfere with the <input type=file> element.
If you want to get a file from the server to the client's system, you must ask them to download it (see Article
#2161). And no, you can NOT force the location -- or even the filename -- that the user will save with.
Why do I get an 'Invalid Path Character' error?
In Windows, you can create a folder called c:\who,what\. You can also create a folder with 'bad' characters, e.g.
commas, in the name using FileSystemObject (of course, assuming sufficient permissions):
<%
set fso = Server.CreateObject("Scripting.FileSystemObject")
fso.CreateFolder "c:\who,what\"
set fso = nothing
%>
<%
set fso = Server.CreateObject("Scripting.FileSystemObject")
fso.CreateFolder Server.Mappath("/who,what/")
set fso = nothing
%>
The initial response I received from Microsoft was that the path passed to MapPath was perhaps 'protected' due to
possible conflicts with URLEncode or HTMLEncode. I commented that a comma is a perfectly valid URL and HTML
character, and that neither Server.URLEncode nor Server.HTMLEncode really altered its appearance. They said
they'd keep digging, and that they might even have to look at the code (gasp!).
So, while we wait for an official response, which I'm promised is forthcoming, I can suggest a workaround. Namely,
use Server.MapPath() to obtain the root, and then build out using "local" folder paths. For example:
<%
set fso = Server.CreateObject("Scripting.FileSystemObject")
fso.CreateFolder Server.Mappath("/") & "\who,what\"
set fso = nothing
%>
While I'm not optimistic this limitation of classic ASP will ever get fixed, I have to assert that if I can create a folder
with certain characters in the name in Windows or using FileSystemObject, I should be able to get the path of that
FWIW, here are the characters that cause the Server.MapPath method to fail:
CHR(0)
CHR(9)
CHR(10)
CHR(11)
CHR(12)
CHR(13)
CHR(32)
CHR(34) "
CHR(42) *
CHR(44) ,
CHR(58) :
CHR(60) <
CHR(62) >
CHR(63) ?
CHR(160)
Another case where you may come across this problem is something like the following:
<%
server.transfer("/file.asp?foo=1&bar=2")
%>
This is because server.transfer internally performs a server.mappath() (for some reason), and it can't take ? and &
characters...
How do I change the modified time of a file?
Unfortunately, this can't be done through ASP. The dateLastModified property of the FileSystemObject is read-only.
However, if it's a text-based file, you could read in the contents of the file, delete the file, and re-create it. That
would alter the create *and* modify time of the file, and might not be the desired result. Here is the code to do this
in VBScript:
<%
Sub Touch(filename)
set fso = Server.CreateObject("Scripting.FileSystemObject")
set fs = fso.openTextFile filename,1,true
f = fs.readAll()
fs.close: set fs = nothing
fso.deleteFile filename,true
set fs = fso.createTextFile filename,true
fs.writeline(f)
fs.close: set fs = nothing
set fso = nothing
End Sub
Call Touch(Server.MapPath("/somefile.htm"))
Call Touch("c:\somefile.txt")
%>
Another alternative is to use touch.exe, which you can get from the NT or Windows 2000 Resource Kit.
Why is 'the operation completed successfully' an error message?
I still don't get the logic behind this error message. When using the file system object or wscript shell, you may have
or
or
Despite the "success" indicated by the text of the error message, this usually indicates that there was a permissions
problem accessing a file or folder with Scripting.FileSystemObject or Wscript.Shell. If the former, make sure that
IUSR_<machine> has appropriate privileges on scrrun.dll (found in your windows\system32 folder) and also
whatever resource you are attempting to access. If the latter, make sure IUSR_<machine> has appropriate
One reason could be forgetting to double up backslash characters in local paths. Since \ is an escape character in
var f = fso.openTextFile("c:\foo.htm");
Will actually get interpreted incorrectly, since JScript will be trying to render a "\f" character. So to fix, use the
following syntax:
var f = fso.openTextFile("c:\foo.htm");
Notice that no filename was specified in the second argument. To correct, make sure both the source and target file
has a name.
Another possible cause is attempting to use FileSystemObject to load a file from a URL. FileSystemObject only has
the ability to read *local* files, so if you want to process a text file that resides on another server, see Article
#2173.
Why do I get 800A0BBA errors?
When using ADODB.Stream to send files to the client, you may receive this error:
Double-check your path -- be sure that the file exists and it is greater than 0 bytes. Make sure that
IUSR_machineName (or the authenticated user(s) / group(s)) has at least read access on the file. If the file is on a
network drive, please see Article #2168. If you are trying to stream a file from a URL (e.g. http://), use
A lot of people use arrays or recordsets to pick a random file from a folder. Here is a slightly more efficient way...
instead of loading all of the filenames into a large array, just run through the list until you hit the random number.
<%
Set fso = Server.CreateObject("Scripting.FileSystemObject")
Set fold = fso.GetFolder(Server.MapPath("folderName"))
Set fileset = fold.files
fileCount = fileset.count
counter = 0
randomize
fileToPick = clng((rnd * fileCount) + 0.5)
response.write randomFile
else
end if
There are several components that enable you to do this. You may already have one, if you have an SMTP server
installed alongside IIS - it's called CDONTS. Here is the documentation and an article for CDONTS:
http://msdn.microsoft.com/library/en-us/cdo/html/_denali_newmail_object_cdonts_library_.asp
Article #2026
There are also several other components available (if I missed any, let us know)
AspMail / ASPQMail
http://www.serverobjects.com/products.htm#Aspmail
Persits ASPEmail
http://www.aspemail.com/
MailListBot
http://www.maillistbot.com/
ATLMail
http://www.chizl.com/dev/c++/
SA-SMTPMail
http://www.softartisans.com/softartisans/smtpmail.html
Dundas Mailer
http://www.dundas.com/subFrame.asp?products/asp/Mailer/index.asp
EasyMail
http://www.quiksoft.com/products/
w3 Jmail
http://www.dimac.net/
HTMLMailer / HTMLMailerPlus
http://www.oopadelic.com/htmlmailer/
http://www.oopadelic.com/htmlmailerplus/
DevMailer
http://www.geocel.com/devmailer/
VSEmail
http://www.vsoft-tech.com.au/vsemail/readme.html
SimpleMail
http://simplemail.adiscon.com/en/
OCXMail
http://www.flicks.com/aspmail/
Zaks.POP3
http://www.zaks.demon.co.uk/code/cpts/pop/index.html
If you don't have access to CDONTS or CDO.Message, and can't install a COM object on your web server, you might
consider using a stored procedure to send mail from within SQL Server. See Article #2403 for a tutorial on
configuring your SQL Server for sending mail through Exchange or an external SMTP server.
How do I send e-mail with CDONTS?
To send an e-mail with Active Server Pages requires some kind of component. There are many third party
components available (see bottom of page), but one of the most readily available is the free Microsoft mail
component CDONTS, which ships with the Option Pack for WinNT 4.0.
Now, once you stop trying to comprehend it's name (I know I can't), it is a simple mail program to use.
CDO works by using the SMTP service in IIS, unless Exchange is installed, then it will just use Exchange's SMTP
system. Before continuing, make sure you have your SMTP service properly set up. You can check by using the
Microsoft Management Consol (MMC), or you can look to see if CDONTS.DLL is in your system32 directory.
To send e-mail from ASP, all you have to do is define the object and use the ".send" function.
<%
Set MailObj = Server.CreateObject("CDONTS.NewMail")
MailObj.Send "[email protected]", "[email protected]", "My Subject", "My Text"
Set MailObj = Nothing
%>
Wasn't that easy? If you need more properties, you can add them as necessary. But as it gets more complex, you
might find it easier to use the format that most people use:
<%
Set MailObject = Server.CreateObject("CDONTS.NewMail")
MailObject.From = "[email protected]"
MailObject.To = "[email protected]"
MailObject.Subject = "Subject Text Here"
MailObject.Body = "Body Text Here"
MailObject.CC = "[email protected]"
MailObject.Send
Set MailObject = Nothing
%>
Sending an Attachment
<%
Set MailObject = Server.CreateObject("CDONTS.NewMail")
attFile = "c:\attachments\StandardPolicy.txt"
attName = "Policy.txt"
MailObject.From = "[email protected]"
MailObject.To = "[email protected]"
MailObject.Subject = "Subject Text Here"
MailObject.Body = "Body Text Here"
MailObject.AttachFile attFile, attName
MailObject.Send
Set MailObject = Nothing
%>
Problems
If you are having difficulties with CDONTS, see the following reference:
http://msdn.microsoft.com/library/en-us/cdo/html/_denali_newmail_object_cdonts_library_.asp
If you get "Permission Denied" errors, try switching "run in separate memory space" off and on again. Also see these
KB articles:
If you get "The system cannot find the path specified" errors, you may need to (re)install the SMTP service. For
Q235681
Windows XP
If you are trying to run CDONTS.NewMail on WinXP, you might get one of the following errors:
Invalid class string
' or
ActiveX component can't create object: 'CDONTS.NewMail'
' or
The "SendUsing" configuration value is invalid.
This is because Microsoft is using a new progID of the DLL used to send mail through CDO (CDONTS.NewMail is
being shuffled out). To deal with this, you can start using the new code style:
<%
sch = "http://schemas.microsoft.com/cdo/configuration/"
With cdoConfig.Fields
.Item(sch & "sendusing") = 2 ' cdoSendUsingPort
.Item(sch & "smtpserver") = "<enter_mail.server_here>"
.update
End With
With cdoMessage
Set .Configuration = cdoConfig
.From = "[email protected]"
.To = "[email protected]"
.Subject = "Sample CDO Message"
.TextBody = "This is a test for CDO.message"
.Send
End With
You can handle it this way also -- thanks to Siegfried Weber for the advice -- including the CDO type library so you
don't have to use the schema URL/namespace, and this also allows you to use the named CDO constants:
<%
Set cdoConfig = Server.CreateObject("CDO.Configuration")
With cdoConfig.Fields
.Item(cdoSendUsingMethod) = cdoSendUsingPort
.Item(cdoSMTPServer) = "<enter_mail.server_here>"
.Update
End With
With cdoMessage
Set .Configuration = cdoConfig
.From = "[email protected]"
.To = "[email protected]"
.Subject = "Sample CDO Message"
.TextBody = "This is a test for CDO.message"
.Send
End With
Or you can use the workaround of copying cdonts.dll from a Windows 2000 machine and regsvr32'ing it on the XP
machine. I have not tested this method, so try it at your own risk. If it works, it may -- at least in the short term --
be the better solution. But you should plan to migrate your code eventually, because hosts running .NET Servers are
If you switch to the new code technique, the advantage is that it works on Windows 2000... so you can migrate your
code gradually. (If you are using Windows 2000, you should start using CDO.Message and migrating your code.)
There is a comprehensive article here -- describing all of the methods and properties, and showing a few code
samples:
http://msdn.microsoft.com/library/en-us/cdosys/html/_cdosys_imessage_interface.asp
http://msdn.microsoft.com/library/en-us/cdosys/html/_cdosys_messaging.asp
Last resort...
If CDONTS continues to frustrate you, check out Article #2119 for a thorough list of alternative SMTP components.
Your web host almost certainly supports at least one of them. If they don't, they should.
Another alternative, if you're using SQL Server, is to send the mail from within the database. Your ASP page can
pass parameters to a stored procedure that sends the mail without having to use CDO or a custom COM object on
If you are using HTML mail, you can use HTML carriage returns. For example, if the body is coming from a textarea,
you'll want to use the following to put HTML carriage returns in:
<%
' ...
body = request.form("textareabody")
body = replace(body,vbCrLf,"<br>")
' ...
%>
If you are using plain text, then you just need to insert the VbCrLf constant to generate carriage returns. For
example:
<%
' ...
body = "Hello Aaron," & vbCrLf & vbCrLf & "How are you?"
' ...
%>
How do I validate an e-mail address?
You can use ASP to validate the *format* of an e-mail address, using a function like the following:
<%
function isEmail(car)
dim lencar
lencar = len(car)
dim cs, j, k, c, c1
cs = 0
for j = 1 to lencar
c = mid(car, j, 1)
if c ="@" then
cs = cs + 1
end if
next
if cs <> 1 then
exit function
end if
for k = 1 to lencar
c1 = ucase(mid(car, k, 1))
isEmail = true
end function
%>
However, it is not so simple to test whether the e-mail account is actually valid. For that, you may want to generate
some random string or a key, e-mail it to the user, and have them enter it into your interface.
Why does my CDONTS mail hang out in the queue or pickup folders?
■ if your mail is not leaving the queue folder, check out Q273644
■ if your mail is not leaving the pickup folder, and you are getting smtpsvc Event ID: 535 ("Virtual Server :
The drop directory , for * could not be created.") in your event log, see Q288538
■ turn on logging for your SMTP server, to see if the mail is even being attempted to be sent to the server
■ make sure your smarthost allows you to relay unconditionally (some require authentication)
■ make sure your server can resolve the ip of your smarthost name - try using the IP of your smarthost to see
■ restart the SMTP Service (or IIS altogether), and if that fails reboot the server, to see if this clears up the
backlog
■ check any .rtr files in the \queue\ folder - these can be caused by various mistakes in code or config
■ check the server itself by setting up outlook express on the machine, using the same settings, and
If you're sending out bulk e-mail to 80,000 people, ASP is not the tool you want to use. You probably want to
schedule an application that handles this in the background, instead of relying on a request/response-based
technology.
If you are using CDONTS, you may have come across 80040020 or 80070020 errors, in which case you'll want to
Simply put, ASP scripts aren't meant to run for as long as it can take to send out 80,000 individual e-mails. It may
work if you send, say 800 e-mails with 100 BCCs each, and use a mail object that supports queueing. But that's still
a lot of strain on your web server, your SMTP server, and your network in general. You can see an example of using
CDONTS and WSH to schedule blocks of e-mails in Q221495. If your distribution lists are getting this large, perhaps
it's time to consider enlisting the help of listserv software and/or services.
If you are running SQL Server, and have control over the server, you might consider installing XPSMTP. This way,
you can schedule a job to break apart large chunks of your task, without relying on ASP and without having to go
through the hassles of configuring SQL mail and a MAPI account on the SQL Server box. I have implemented this
extended stored procedure with great success; see Article #2403 for information on its usage.
Can I get CDO messages to return a read receipt?
Thanks to David Tabaka for providing the insight into this answer.
You can add a return receipt to an existing piece of CDONTS code with the following enhancement (marked in
bold):
<%
Set MailObject = Server.CreateObject("CDONTS.NewMail")
MailObject.From = "?FromAddress?"
MailObject.To = "?ToAddress?"
MailObject.Value("Return-Receipt-To") = "?ReceiptAddress?"
MailObject.Subject = "?Subject?"
MailObject.Body = "?Body?"
MailObject.Send
Set MailObject = Nothing
%>
Apparently, this is how you set up a read receipt with CDO.Message, but it doesn't seem to work in my
environment:
<%
Set cdoConfig = Server.CreateObject("CDO.Configuration")
With cdoConfig.Fields
.Item(cdoSendUsingMethod) = cdoSendUsingPort
.Item(cdoSMTPServer) = "<enter_mail.server_here>"
.Update
End With
fld1 = "urn:schemas:mailheader:disposition-notification-to"
fld2 = "urn:schemas:mailheader:return-receipt-to"
With cdoMessage
Set .Configuration = cdoConfig
.From = "[email protected]"
.To = "[email protected]"
.Fields(fld1) = "[email protected]"
.Fields(fld2) = "[email protected]"
Keep in mind that this won't necessarily tell you that the user has actually read and understood the message, only
that they've opened it; and, also, that both Outlook and Outlook Express (and perhaps other clients as well) support
the ability to turn off read receipts (either per instance or globally).
In any case, here are the other CDO constants (and their header values) that are applicable to a CDONTS message,
cdoApproved approved
cdoComment comment
cdoContentBase content-base
cdoContentDescription content-description
cdoContentDisposition content-disposition
cdoContentId content-id
cdoContentLanguage content-language
cdoContentLocation content-location
cdoContentTransferEncoding content-transfer-encoding
cdoContentType content-type
cdoControl control
cdoDisposition disposition
cdoDispositionNotificationTo disposition-notification-to
cdoDistribution distribution
cdoExpires expires
cdoFollowupTo followup-to
cdoInReplyTo in-reply-to
cdoLines lines
cdoMessageId message-id
cdoMIMEVersion mime-version
cdoNewsgroups newsgroups
cdoOrganization organization
cdoOriginalRecipient original-recipient
cdoPath path
cdoPostingVersion posting-version
cdoReceived received
cdoReferences references
cdoRelayVersion relay-version
cdoReturnPath return-path
cdoReturnReceiptTo return-receipt-to
cdoSummary summary
cdoThreadIndex thread-index
cdoXMailer x-mailer
cdoXref xref
cdoXUnsent x-unsent
Why do CDONTS messages end up in the badmail folder?
If you are getting .bdr files in the badmail folder, this means the mail could not be sent for some reason. You might
see an error like this, if you open up one of the .bdr files:
Unable to deliver this message because the following error was encountered:
This message is a delivery status notification that cannot be delivered.
Specific error code was 0xC00402C7
The most common cause is that you've either omitted the address in the From or To properties, or included an
invalid e-mail address (see Q267859 - fixed in Windows 2000 Service Pack 2)
You've reinstalled the IIS SMTP service (see Q290290), or your SMTP service isn't properly configured (see
Q265621)
You are using Smart Host and a DNS server that does not support TCP queries (see Q276347)
You are attempting to run Microsoft Commercial Internet System on Windows 2000 (see Q258918)
How do I prevent my links from wrapping in an e-mail?
http://www.wherever.com/myfolder/mypage.asp?id=1&page=2&frank=3&bob=43521
In most e-mail readers, this link will be active (clickable) but probably won't work correctly, because the mail
program (and/or the mail server) might force the text to wrap to the next line after a certain number of characters
(usually 72 or 76). So, what can you do to prevent this from happening?
If your host headers and/or DNS entries are set up correctly, the first thing you can do is remove 4 characters in
http://wherever.com/myfolder/mypage.asp?id=1&page=2&frank=3&bob=43521
Additionally, you can think about using a shorter subfolder and/or ASP page name, e.g.:
http://wherever.com/myf/myp.asp?id=1&page=2&frank=3&bob=43521
With IIS 5+, you can consider using a subfolder where the page you want to go to is the default page, so that the
http://wherever.com/myf/?id=1&page=2&frank=3&bob=43521
Finally, if it's acceptable, you can use an the IP address, which may or may not shorten the domain name part of the
URL.
http://204.3.20.8/myf/?i=1&p=2&f=3&b=43521
If you can't modify the actual page that handles the incoming traffic, you could build a "stub" file like that redirects:
<%
qs = request.servervariables("QUERY_STRING")
response.redirect("http://www.wherever.com/myfolder/mypage.asp?" & qs)
%>
For IIS 4+, place the stub file in your root or in a subfolder, so your link would become:
http://wherever.com/stub.asp?id=1&page=2&frank=3&bob=43521
or
http://wherever.com/rd/stub.asp?id=1&page=2&frank=3&bob=43521
For IIS 5+, you can use the subfolder method above, and place a default page to do the redirect -- so your link
would become:
http://wherever.com/rd/?id=1&page=2&frank=3&bob=43521
About 10 seconds at www.tinyurl.com yielded a much shorter link than any of the above methods:
http://tinyurl.com/imh
The only concern I have is longer term -- how long will a free service (a) remain free, and/or (b) keep those links
around?
You may want to look at sites like www.shorturl.com and www.makeashorterlink.com, which offer similar services -
albeit a bit less convenient. The former requires registration by the person creating the link, and the latter shows
their page and logo before redirecting the user(s) following the link.
So what do you do if you can't control the format and location of the pages on the server, and can't use a service
like tinyurl.com? If your clients accept it, you can format your e-mail as HTML, so that the link will surely not wrap.
You should make sure that your content is easy enough to read for those that have disabled HTML, are using a web
interface that doesn't allow HTML through, or are using a mail reader without HTML support (e.g. Pine). In cases
where you can offer alternate content, you should tell the user that they may have to re-construct the link. In
addition, the latest build of my chosen mailreader allows me to turn off HTML completely (in both preview pane and
full view); I've also recently started playing with a macro that strips the HTMLbody portion of an incoming Outlook e-
mail -- so I can even stop reading Comic Sans MS, which one of my co-workers insists on using. That messiness,
combined with the fact that many people turn off HTML to prevent web bugs etc., leads me to encourage you to do
anything you can to use the above methods *before* going the HTML route. AS more people learn of the methods to
disable HTML mail, the HTML solution will work in a smaller and smaller percentage of your audience.
Okay, enough chatter, here's some code that will help you construct HTML e-mail from a variety of mail objects
available in ASP. If you know of another mail object that supports HTML format, please let us know and we'll add a
code sample.
<%
' set up
With cdoM
Set .Configuration = cdoC
.From = FromAddress
.To = ToAddress
.Subject = Subject
.HTMLBody = htmlbody
.TextBody = textbody
.Send
End With
' if you have a registered copy of ASPEmail 4.5, you can provide
' multipart content, so that non-HTML-aware readers can have an
' alternate version:
For more information on sending HTML mail with CDO.Message, see Q286431...
How do I send e-mail from SQL Server?
SQL Server has the ability to send mail without being triggered from ASP. So, if you have an application that needs to
e-mail users once a month, or on their birthday, or to send you details every day about what went on in the database
that day, you can set up a job that wakes up and sends e-mail. Similarly, you can call extended stored procedures to
First off, make sure your SQL Server Mail account is set up correctly - see
(this part is not a lot of fun, because it requires a MAPI client on the server, and a valid Exchange account for the
MSSQLServer service).
If you are still having problems configuring or using SQL Mail, see if any of the following articles help: Q274330,
If you can not (or do not wish to) configure SQL Mail, you can use CDO (see Q312839) or XP_SMTP_SendMail (from
http://www.sqldev.net/xp/xpsmtp.htm). I prefer the XP_SMTP_SendMail procedure over both CDO and SQL Mail.
XP_SMTP_SendMail is an extended stored procedure that will send mail through any valid SMTP server you specify,
and supports HTML formatting. This makes sending mail very flexible, especially if your SQL Server is not a part of a
domain, or you're not running Exchange - and you don't have or don't want to install CDO on your database
server(s).
Let's say you want to send an e-mail from within a stored procedure called foo. You can do it like this:
With XP_SendMail:
CREATE PROCEDURE foo AS
BEGIN
SET NOCOUNT ON
-- do some other actions
EXEC xp_SendMail
@recipients='[email protected]',
@message = 'foo was fired '+CONVERT(VARCHAR, GETDATE()),
@subject = 'foo was fired.'
END
With XP_SMTP_SendMail:
CREATE PROCEDURE foo AS
BEGIN
SET NOCOUNT ON
-- do some other actions
EXEC xp_SMTP_SendMail
@TO = '[email protected]',
@from = '[email protected]',
@message = 'foo was fired '+CONVERT(VARCHAR, GETDATE()),
@subject = 'foo was fired.',
@server = 'smtp.yourdomain.com'
END
If you need to send an attachment, you can simply add the following parameter:
@attachments = 'c:\attachment.txt'
To schedule mail
Now, let's say you have a stored procedure that checks a table for birthdays, and e-mails a happy birthday to all
matches. It might look like this (this assumes XP_SMTP_SendMail is the method you're using):
-- and include your own address, with today's date, for testing!
EXEC XP_SMTP_SendMail
@TO = '[email protected]',
@BCC = @BCCList,
@from = '[email protected]',
@subject = 'Happy Birthday to you!',
@message = 'You belong in a zoo!',
@server = 'smtp.yourdomain.com'
END
GO
Currently, you might be invoking this stored procedure manually, or having a scheduled ASP / VBS script execute the
stored procedure through ADO. Wouldn't it be nice to have the database take care of this, without involving other
Make sure SQL Server agent is running. In Enterprise Manager, expand the server in question, and expand the
following dialog:
On the General tab, name the job (e.g. SendBirthdayNotices).
On the Steps tab, click New... and enter 'Step 1' into the Step Name: box. Switch the database dropdown to the
database where your stored procedure lives, make sure 'Transact-SQL' is selected, and enter 'EXEC
proc_happyBirthday'.
On the Schedules tab, click New Schedule... and enter 'Schedule 1' in the Name: box. Make sure 'enabled' is checked,
and click the 'Change...' button. Set up a schedule that makes sense... e.g. daily at noon or daily at 5:00 am. The
interface may look a bit daunting but it's pretty simple to configure.
Now, you can monitor your job's progress in the jobs view to see last/next run dates, and right-click the job itself to
see the job history. You can also right-click the job and start it now (e.g. if you created it AFTER the scheduled run
Here are some other resources for CDO / CDONTS outside of aspfaq.com:
CDOLive
Slipstick.com
15 Seconds
IISFaq I
IISFaq II
ASPIN.com I
ASPIN.com II
ASPFAQs.com
4guysFromRolla.com
And of course, if you are having SMTP-related problems, the experts in microsoft.public.inetserver.iis.smtp_nntp
CDONTS.NewMail only supports local smtp servers, so you can't set up a remote server to handle your mail from
this object. If you are unable or unwilling to configure the web server to support outgoing SMTP mail, you will need
to use another product. In Windows 2000 and above, you can use CDO.Message as follows:
<%
myMailServer = "smtp.yourdomain.com"
sch = "http://schemas.microsoft.com/cdo/configuration/"
Set cdoConfig = Server.CreateObject("CDO.Configuration")
cdoConfig.Fields.Item(sch & "sendusing") = 2
cdoConfig.Fields.Item(sch & "smtpserver") = myMailServer
cdoConfig.fields.update
If you don't have access to CDO.Message (e.g. running NT 4.0), see Article #2119 for a list of 3rd party COM objects
When switching from CDONTS to CDO.Message, when using code like the following (as described in Article #2026):
<%
sch = "http://schemas.microsoft.com/cdo/configuration/"
Set cdoConfig = Server.CreateObject("CDO.Configuration")
cdoConfig.Fields.Item(sch & "sendusing") = 2
cdoConfig.Fields.Item(sch & "smtpserver") = "<enter_mail.server_here>"
cdoConfig.fields.update
error '8004020f'
The event class for this subscription is in an invalid partition
/<file>.asp, line <line>
It is not clear where this error message comes from; it's not even in Microsoft's Knowledge Base or MSDN Library.
However here are some things you can try to alleviate the problem:
1. Make sure the SMTP server allows anonymous (non-authenticated) relaying. Otherwise, you'll have to use a
example, some users have complained that they can send to users on their own domain only; others have
said that they can send to any domain except their own.
3. If you have a proxy or firewall, make sure the web server is set up to correctly pass through it, that the
SMTP server knows about it, and that the proxy allows access to port 25.
4. Try using a SendUsing value of 1 (pickup) instead of 2 (port). E.g. the following line:
Becomes
If using CDONTS.NewMail, you might have come across this error, from files in your badmail folder:
Check permissions on the mailroot folder. IUSR_MachineName, or the authenticated user(s)/group(s), needs to have
Try running the site / application in a different application protection level. This is not a fix, obviously, but a bandaid
Another alternative is to use CDO.Message instead (which you'll eventually have to do anyway, since
CDONTS.NewMail has been deprecated -- and no longer ships with Windows XP / .NET Server). See Article #2026
Check to make sure that your SMTP service is running and that your CDONTS code sets proper and valid from and to
addresses. There have been several reported problems about sending mail through CDONTS when the domain is the
same as the smart host, or not the same as the smart host, and these seem to always be alleviated by using
CDO.Message or a third-party component, which uses an SMTP server as opposed to the local Exchange system.
How do I alter the priority of a CDO message?
<%
sch = "http://schemas.microsoft.com/cdo/configuration/"
Set cdoConfig = Server.CreateObject("CDO.Configuration")
cdoConfig.Fields.Item(sch & "sendusing") = 2
cdoConfig.Fields.Item(sch & "smtpserver") = "<enter_mail.server_here>"
cdoConfig.fields.update
Note:
2 = High Importance
1 = Medium Importance
0 = Low Importance
I haven't investigated whether this property is available with CDONTS.NewMail. Since it is being phased out, I think
Error '80090020'
An internal error occurred.
CDONTS has always been a bear to set up correctly, and has many configuration settings which cause errors in
different environments. The recommended fix for this error is to use the newer CDO.Message instead, since
CDONTS.NewMail has been deprecated, and no longer ships with Windows XP / .Net Server. You can see sample
If you're using IIS 6.0, search %WINDIR%\system32\inetsrv\MetaBase.XML for the following line:
PickupDirectory=
Verify that the path exists, and has appropriate permissions. Alter the value if it is not set correctly.
On previous versions of IIS, the pickup directory is usually c:\inetpub\mailroot\pickup -- so make sure that this
If you are trying to send SMTP mail through exchange, make sure you set the cdoSendUsing property to
cdoSendUsingExchange (3). Otherwise, the SMTP service might be expecting a local pickup directory which has
error '80040108'
This can happen if you try to read or write a CDONTS property or a CDO configuration property after the mail has
been sent. Allegedly, these parameters are destroyed immediately after the message is sent, so they cannot be
<%
' ...
objCdo.body = "foo" & _
objCdo.send
%>
If you don't end that string correctly, the CDO object is going to try and append the send method to the body, which
of course is going to confuse the heck out of it. :-) So check your code for little items like that...
In addition, if you are using CDONTS.NewMail, consider using CDO.Message instead (see Article #2026).
Why do I get 8004020A errors?
Error 8004020A
The SMTP server name is required, and was not found in the configuration source
You either left out the smtpserver option on CDO.Configuration, or didn't spell it in lower case (see Q265527). For
When you use CDONTS.NewMail and include the constant CDOVBS.INC, you may come across this error:
Error '8009000f'
Object already exists.
Some suggestions:
2. instead of including CDOVBS.inc, use CONST statements in your own script to declare only those variables
you need;
If you copied the CDO.Message code from Article #2026 verbatim, you will probably get this error:
Make sure your date is a valid date. You can do this in VBScript using the isDate() function. Syntax is:
<%
if isDate(dateVar) then
' do something
else
' do something else
end if
%>
NOTE: If you are using Access, you need to surround dates with pound signs (#). With most other databases,
including SQL Server 7, dates are surrounded with apostrophes (') and are treated like strings.
<%
' *** SQL Server:
sql = "SELECT field FROM table WHERE datefield > '" & dateVar & "'"
If you only want records with datefields in the last n days, you can do something like this:
<%
n = 5
If you want records that fall between two dates (inclusive), you can do this:
<%
sql = "SELECT field FROM table WHERE datefield BETWEEN '" & dateVar1 & "' AND '" &
dateVar2 & "'"
%>
Another tip... don't name datetime fields with reserved words like DATE or TIME.
Use yyyy-mm-dd format for all dates when passing to the database. Then the database won't care which way it's set
up internally, the default locale (or the current user's regional settings) on the database machine, the default locale
(or current user's regional settings) of the IIS machine passing dates through ASP, and the date that the user
entered manually. Here is a quick example of converting ASP's date to yyyy-mm-dd format:
<%
dateVar = year(date) & "-" &_
left("00",2-len(month(date))) & month(date) &_
"-" & left("00",2-len(day(date))) & day(date)
%>
Of course, it will be up to you that dates entered by the user are in the correct format. No code can determine
whether the user who typed in 2/3/01 actually meant Febraury 3rd or March 2nd - it can only determine in which
VBScript has many useful date functions that can help you with many issues. One problem I had on a project was
running a loop from the first day of the PREVIOUS month to today. The DateAdd() function helped with this
immensely.
<%
lastmonththisday = dateadd("m",-1,date())
%>
Then I subtracted from that date the number of days that had passed that month (which would bring you back to
<%
lastmonthfirstday = dateadd("d",-day(date())+1,lastmonththisday)
%>
Now I put that together in a for loop that created a table, with each day from the beginning of last month to today
<%
response.write("<table>")
lmfd = dateadd("d",-day(date())+1,(dateadd("m",-1,date())))
for i = lmfd to date()
response.write("<tr><td>" & formatdatetime(i,1) & "</td></tr>")
next
response.write("</table>")
%>
I then threw in some logic to color the weekends with a different color:
<%
response.write("<table>")
lmfd = dateadd("d",-day(date())+1,(dateadd("m",-1,date())))
for i = lmfd to date()
bg = "#ffffff"
if weekday(i)=1 or weekday(i)=7 then bg = "#ffffcc"
response.write("<tr><td bgcolor=" & bg & ">" & formatdatetime(i,1) & "</td></tr>")
next
response.write("</table>")
%>
http://www.learnasp.com/learn/datetime.asp
http://www.merlyn.demon.co.uk/js-dates.htm
How do I delimit dates for inserting/updating a database?
<%
sql = "insert into table(datefield) values('1999-06-05')"
%>
With Access:
<%
sql = "insert into table(datefield) values(#1999-06-05#)"
%>
(In any database platform, you should try to use yyyy-mm-dd format. See Article #2260 for more details.)
SQL Server: How do I select time only from a DATETIME field?
One is to use the CONVERT function in conjunction with specific style numbers, to convert the date value into a
specific type of string. I like using 114, because you can simply change the number of characters returned to
SELECT
CONVERT(CHAR(5), DateField, 114)
FROM
table
[WHERE ... ]
11:45
For HH:MM:SS:
SELECT
CONVERT(CHAR(8), DateField, 114)
FROM
table
[WHERE ... ]
11:45:37
11:45:37:623
Depending on your application, you may need any of the above accuracies. Usually, though, to-the-minute is
sufficient.
The other option is to use DATEPART to concatenate the time yourself. This is a bit messier, but is useful, for
example, if you only want the minutes and are not concerned about the actual hour. I can't think of a practical use
SELECT
DATEPART(MINUTE, DateField)
FROM
table
[WHERE ... ]
If you want to build an entire time string on your own, you could do this:
SELECT
CONVERT(VARCHAR(2),DATEPART(HOUR, DateField)) + ':' +
CONVERT(VARCHAR(2),DATEPART(MINUTE, DateField))
FROM
table
[WHERE ... ]
Now, you might find that minutes less than 10 will result in weird padding; for example, if you were going to use this
technique for building a time string on your own, you could end up with a result as follows:
5:3
This would be 5:03, but could obviously be misconstrued by the user. Here is how I work around this scenario:
SELECT
CONVERT(VARCHAR(2), DATEPART(HOUR, DateField)) + ':' +
CASE
WHEN DATEPART(MINUTE, DateField) < 10 THEN
'0'+CONVERT(VARCHAR(2),DATEPART(MINUTE, DateField))
ELSE
CONVERT(VARCHAR(2),DATEPART(MINUTE, DateField))
END
FROM
table
[WHERE ...]
Why does JavaScript's document.lastModified() not work in ASP files?
ASP is compiled when the user requests the file, so browsers report the time the HTML was generated, not when the
VBScript:
<%
thisfile = Request.ServerVariables("SCRIPT_NAME")
thisfile = Server.MapPath(thisfile)
set fso = Server.CreateObject("Scripting.FileSystemObject")
set fs = fso.getfile(thisfile)
dlm = fs.datelastmodified
set fs = nothing: set fso = nothing
Response.Write("Last modified: ")
Response.Write(formatdatetime(dlm,1) & " " & formatdatetime(dlm,3))
%>
JScript:
Of course, when you add this script to a file and save it, the dateLastModified value will become "now()" by
definition.
If you have a common include file, you could put this code in THAT file without changing the calling file. See Article
#2072 if you want the last modified time of the include file instead.
Can I get millisecond accuracy in ASP?
VBScript does not support such fine granularity within formatdatetime, datediff, or dateadd.
But there may still be a way to do what you want, depending on your goal.
If you want to count the number of milliseconds between two dates, there are very few scenarios where you could
make this work in VBScript. Perhaps if two datetime values are stored in SQL Server, including milliseconds, and you
retrieved them using a string... parsing it out and doing all the math yourself (yuck). If that were the case, you
could just as easily do the DATEDIFF within SQL Server, and then you wouldn't have to do the math in VBScript. You
JScript does support a getMilliseconds() method, which gets the milliseconds from the current time:
So it may be possible that you can use Jscript to get millisecond accuracy. Certainly if you define two dates within a
script, and you want to know how many milliseconds passed between them, you can do custom math functions
which will have to vary calculations depending on how many minutes, seconds etc. have elapsed. But the question
If you have milliseconds stored in SQL Server, you can get these values to the ASP page simply by adding a
Of course, once you get that value back to ASP, the greatest utility you will get from it is dumping it to the screen as
text.
How do I display time in military format?
One is to change regional settings to display time in military format. I don't like to do this because it can break
existing code, and can change depending on who is logged into the server (if anyone).
<%
ft = formatdatetime(time(),3)
response.write "Standard time:<p>" & formatdatetime(ft,3)
if right(ft,2)="PM" then
t = split(ft,":")
milhour = clng(t(0))
if clng(left(ft,2))<12 then milhour = milhour +12
mtime = cstr(milhour) & ":" & t(1)
mtime = mtime & ":" & left(t(2),2)
elseif clng(left(ft,2))=12 then ' this handles midnight only
mtime = "00:"
mins = datepart("n",ft)
secs = datepart("s",ft)
mtime = mtime & left("00",2-len(mins)) & mins
mtime = mtime & left("00",2-len(secs)) & ":" & secs
else
mtime = left(ft,len(ft)-3)
end if
response.write "<p>Military time:<p>" & mTime
%>
The advantage with SQL is you could change it to CHAR(12) and get millisecond accuracy (the above scripts only get
There are a few considerations here. Most people funnel into big mathematical equations, dividing the datediff in
days by 365.333333333 and doing all kinds of logic to equate that to an age. In addition, we need to be wary of
leap years and treat those cases differently. While a leap year baby's true age might technically be in the single
digits, the more important piece of data (usually) is that x number of years have passed since they were born. Here
is an example in VBScript:
<%
' use DateSerial(y,m,d) to avoid locale issues
date1 = DateSerial(1974,2,24)
date2 = DateSerial(year(date), month(date), day(date))
And here is an example in T-SQL (note that this example does NOT make special considerations for leap year
birthdays):
SELECT @age
Finally, here is a solution in Access, given a table named DOBs with a column named dob (with only date, and no
time information -- or time set to midnight):
SELECT
dob,
DateDiff
(
"yyyy",
CDate(dob),
Date()
)
- IIF
(
DateAdd
(
"d",
Day(dob)-1,
DateAdd
(
"m",
Month(dob)-1,
CDate(year(Date()) & "-01-01")
)
)
> date(),
1,
0
) AS age
FROM
dobs
How do I convert local time to UTC (GMT) time?
Many people have asked how they can convert local times to UTC format.
Converting the current time is relatively simple, assuming that your server is set up correctly (proper time
zone, and observes daylight savings time if appropriate). This is because the registry stores the offset
between the local time zone and UTC. Here are a few examples:
VBScript - assuming IUSR has read access to registry! If this is not the case, you can use the same logic as
<%
od = now()
set oShell = server.createobject("WScript.Shell")
atb = "HKEY_LOCAL_MACHINE\System\CurrentControlSet\" &_
"Control\TimeZoneInformation\ActiveTimeBias"
offsetMin = oShell.RegRead(atb)
nd = dateadd("n", offsetMin, od)
Response.Write("Current = " & od & "<br>UTC = " & nd)
%>
JScript
Transact-SQL
SELECT GETDATE() AS CurrentTime, GETUTCDATE() AS UTCTime
Converting an arbitrary time is a little more involved. Because the registry only stores the CURRENT bias,
and doesn't keep historical record for previous dates, you may get invalid data if you are NOT in daylight
savings tiem and you are converting a date that is (or vice-versa). These examples will show how to find
GMT time for another date *in 2001* -- I will update this later to apply to any year. Note that for the
VBScript and T-SQL solutions, you should know your regular offset (these examples assume Eastern time
VBScript
<%
' fill in your known bias here!
offset = 5
for i = 1 to 7
if weekday("4/" & i & "/2001")=1 then
startDST = cdate("4/" & i & "/2001")
exit for
end if
next
for i = 31 to 25 step -1
if weekday("10/" & i & "/2001")=1 then
endDST = cdate("10/" & i & "/2001")
exit for
end if
next
' set some arbitrary date
JScript - a little smarter, JScript inherently knows when a date falls within DST, and adjusts accordingly.
Transact-SQL
-- fill in your known bias here!
WHILE @i < 7
BEGIN
SET @dt = '04/0'+CAST(@i AS CHAR(1))+'/2001'
IF DATEPART(weekday,@dt)=1
BEGIN
SET @sdt = '04/0'+CAST(@i AS CHAR(1))+'/2001'
SET @i = 7
END
SET @i = @i + 1
END
SET @i = 31
WHILE @i > 24
BEGIN
SET @dt = '10/'+CAST(@i AS CHAR(2))+'/2001'
IF DATEPART(weekday,@dt)=1
BEGIN
SET @edt = '10/'+CAST(@i AS CHAR(2))+'/2001'
SET @i = 24
END
SET @i = @i - 1
END
Well, VBScript's FormatDateTime leaves a lot to be desired. There really are only two ways to format a date by
itself: VBLongDate and VBShortDate. Below are a few functions that will help you get dates into other relatively
common formats. Many of these functions use the following function, from Article #2300:
<%
Function pd(n, totalDigits)
if totalDigits > len(n) then
pd = String(totalDigits-len(n),"0") & n
else
pd = n
end if
End Function
%>
YYYYMMDD
(This is the preferred date format for passing dates to a database, as it eliminates the need to worry about regional
<%
response.write YEAR(Date()) & _
Pd(Month(date()),2) & _
Pd(DAY(date()),2)
%>
YYYY-MM-DD
<%
response.write YEAR(Date()) & _
"-" & Pd(Month(date()),2) & _
"-" & Pd(DAY(date()),2)
%>
DDMMYYYY
<%
response.write Pd(DAY(date()),2) & _
Pd(Month(date()),2) & _
YEAR(Date())
%>
MMDDYYYY
<%
response.write Pd(Month(date()),2) & _
Pd(DAY(date()),2) & _
YEAR(Date())
%>
DD-MM-YY
<%
response.write pd(DAY(date()),2) & "-" & _
pd(MONTH(date()),2) & "-" & _
pd(RIGHT(YEAR(date()),2),2)
%>
YY-MM-DD
<%
response.write pd(RIGHT(YEAR(date()),2),2) & "-" & _
pd(MONTH(date()),2) & "-" & _
pd(DAY(date()),2)
%>
MM/DD
<%
response.write pd(MONTH(date()),2) & "/" & _
pd(DAY(date()),2)
%>
Month D
<%
response.write MonthName(Month(date())) & _
" " & DAY(date())
%>
DD Mon YY
<%
response.write pd(DAY(date()),2) & " " & _
MonthName(Month(date()),true) & _
" " & RIGHT(YEAR(date()),2)
%>
Weekday, Month D
<%
response.write WeekdayName(Weekday(Date())) & _
", " & MonthName(Month(date())) & _
" " & DAY(date())
%>
Please let us know if there are any other date formats you'd like to see.
Should I use 'BETWEEN' for date queries?
(1) in my opinion, BETWEEN is ambiguous, at least in SQL Server. It is unclear to the casual observer whether
"...BETWEEN '20020104' AND '20020415' will include a record created at 4/15/2002 at 4:00 am; and I think people
EXPECT such a query to return results for all times on 4/15, not just those records that are entered at exactly
midnight. Much clearer to use >= '20020401' AND < '20020415'. This works better because all datetime values are
converted to midnight.
(2) for queries like 'give me all the records for this month', you tell me which is easier to construct:
or
The latter is obviously easier to create, especially when doing so dynamically (you add a month to the beginning
(3) when you are using other clauses in your query, you also have to remember to wrap the BETWEEN clause in
brackets so that its AND isn't confused with other ANDs in the WHERE clause. See Article #2148 for more
javascript.internet.com/calendars/popup-date-picker.html
javascript.internet.com/calendars/window-calendar.html
www.kamath.com/calendar/
www.asp101.com/samples/calendar.asp
http://www.geocities.com/fuushikaden/PopupCalendar/sample.htm
www.dynamicdrive.com/dynamicindex6/dcalendar.htm
www.dynamicdrive.com/dynamicindex6/dcalendar2.htm
www.dynamicdrive.com/dynamicindex6/popcalendar.htm
developer.iplanet.com/viewsource/husted_calendar/husted_calendar.html
www.4guysfromrolla.com/webtech/060599-2.shtml
www.webreference.com/js/column64/13.html
msdn.microsoft.com/library/en-us/dnasp/html/calendar.asp
msdn.microsoft.com/workshop/author/behaviors/library/calendar/calendar.asp
www.clearviewdesign.com/NEWBIE/DemoPopup.asp
And here are some queries that returned lists of other code samples:
scriptsearch.internet.com/JavaScript/Scripts/Calendars/index.html
javascript.internet.com/calendars/index.html
www.aspin.com/func/search?qry=calendars
www.aspin.com/home/webapps/calendars
www.developersdex.com/asp/search.asp?Search=calendar
How do I determine the number of seconds since 1/1/1970?
This is a pretty common one, as many systems use this measure for various date calculations. Here are examples in
VBScript, JScript and Transact-SQL. Note that to get the number of milliseconds since 1/1/1970, you need to
VBScript
<%
timeStart = "1/1/1970 12:00:00 AM"
Response.Write(datediff("s", timeStart, now()))
%>
Transact-SQL
Note also that these solutions do not account for leap seconds. You can add them in manually for leap seconds that
have been known to occur in the past (06/30/1997 and 12/31/1998 are two examples). You can not predict them
programmatically... since leap seconds are defined by the variability of the Earth's rotation, and are declared by the
International Earth Rotation Service as required. See this Google posting and this U.S. Navy article for highly
detailed information about leap seconds and how they affect UTC. After reading these, you may see why your
calculations don't need to be this precise -- especially since your web and/or SQL Server are probably not set to
atomic clocks anyway. :-)
How do I convert a timespan, in seconds, to HH:MM:SS?
Often you want to compare two dates and/or times and express the difference in HH:MM:SS. Except there is no
decent way to express the relationship this way; the built-in DATEDIFF function returns a single integer (seconds,
Here it is in VBScript:
<%
dt1 = "2002-03-27 9:20:25 AM"
dt2 = "2002-03-27 9:20:45 AM"
Several hiccups can happen when trying to INSERT or UPDATE a record with the current date and time. For one,
regional settings and/or SQL Server's dateformat setting can throw you for a loop (see Article #2260 for some
workarounds to regional settings issues), and even delimiters can get in the way when moving between platforms
My suggestion: let the database put the date and time on the record. In SQL Server, you can do this:
This way, you don't have to worry about your application code getting the date format correct (since SQL Server will
automatically generate the current time for you, and store it in its own internal format), and you can have one less
<%
Response.ExpiresAbsolute = #2000-01-01#
Response.AddHeader "pragma", "no-cache"
Response.AddHeader "cache-control", "private, no-cache, must-revalidate"
%>
If you find that you're still getting the old page, after clearing your browser's page and even deleting the file from
the server, then IIS has it cached. You can clear this by going into the IIS Admin interface and unchecking "Cache
ISAPI Applications", hitting apply, uploading the new file, and turning the setting back on. You can also do this by
issuing an iisreset call or unloading your application, at the cost of interrupted service.
How do I execute a DOS command / batch file / exe from ASP?
If you have Windows Script Host installed and enabled, you can do this (sorry, I've only had the time to test .bat
files):
<%
set wshell = server.createobject("wscript.shell")
wshell.run "c:\file.bat"
set wshell = nothing
%>
If you do not have the ability to use WSH, you can use ASPExec or DynuExec.
If you are trying to retrieve output from a command, see the example in Article #2033, which captures the results of
a PING command. Another alternative is to pipe the output to a text file, which you can then read using the
<%
set wshell = server.createobject("wscript.shell")
wshell.run "dir c:\ > c:\dir.txt"
set wshell = nothing
Error Type:
(0x80070002)
/<file>.asp, line <line>
This usually means that the file is not found. Check the name and path of the file you passed to the run method.
Then either one of two things happened. Either the interactive user (whether IUSR_MachineName or an
authenticated user) does not have permissions to the file or folder being executed; or, the interactive user does not
have permissions to one or more of the commands being called within the file.
How do I read the contents of a remote web page?
You can include static txt and HTML files from remote servers by using a component (such as AspHTTP, ASPTear, or
You can also try this method out; it was tested with the MSXML objects which are installed with Windows 2000. For
performance reasons, and just to have "the latest", you should download the new version 4.0 for Windows 2000 or
other operating systems. If you download the newer version, take special note of the new ProgID you should be
using -- MSXML 4.0 now supports side-by-side installation, which means the ProgID below will actually use the older
version.
<%
url = "http://www.espn.com/main.html"
set xmlhttp = server.CreateObject("MSXML2.ServerXMLHTTP")
xmlhttp.open "GET", url, false
xmlhttp.send ""
Response.write xmlhttp.responseText
set xmlhttp = nothing
%>
If you use a URL that doesn't exist, or you are behind a firewall that blocks certain web sites, or the site is behind a
firewall that blocks traffic to port 80 / 443, or you are using a proxy server, or the site requires authentication, you
will receive this error:
msxml4.dll (0x80072EE7)
Server name or address could not be resolved
To correct, you will have to figure out which of the issue(s) is standing in your way, and discuss workarounds with
Don't forget that if your remote page has relative image URLs, or style sheets, or JavaScript files, or frames, or
links, it won't work perfectly when ported to your server(s). To overcome this, you'll want to add a BASE HREF tag
to keep all the images coming from the correct location. For example, the above code (which gets all the text from
espn.com, but is formatted weird and doesn't function 100% as intended), is modified only slightly to work
correctly:
<%
url = "http://www.espn.com/main.html"
For information on increasing or decreasing the time allowed for the XMLHTTP objects to retrieve a response from a
If you need to POST data you can so by adding a header that tells the receiver you're sending FORM data:
<%
url = "http://www.espn.com/main.html"
set xmlhttp = server.CreateObject("MSXML2.ServerXMLHTTP")
xmlhttp.open "POST", url, false
xmlhttp.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
xmlhttp.send "x=1&y=2"
Response.write xmlhttp.responseText
set xmlhttp = nothing
%>
Another thing you may want to do, going back to the original script, is make sure the server is there! If not, you can
display a message...
<%
' deliberate typo:
url = "http://www.espn.co/main.html"
set xmlhttp = server.CreateObject("MSXML2.ServerXMLHTTP")
on error resume next
xmlhttp.open "GET", url, false
xmlhttp.send ""
if err.number <> 0 then
response.write "Url not found"
else
Response.write xmlhttp.responseText
end if
set xmlhttp = nothing
%>
You might want to parse the results, instead of sending them straight to the client:
<%
url = "http://www.espn.com/main.html"
set xmlhttp = server.CreateObject("MSXML2.ServerXMLHTTP")
on error resume next
xmlhttp.open "GET", url, false
xmlhttp.send ""
if err.number <> 0 then
response.write "Url not found"
else
if instr(xmlhttp.responseText,"Stanley Cup")>0 then
response.write "There's a story about the playoffs."
response.write "<a href=" & url & ">Go there</a>?"
else
response.write "There is no story about the playoffs."
end if
end if
set xmlhttp = nothing
%>
You may be interested in performing an asynchronous request, e.g. hitting an ASP page that acts like a batch file
that gets fired but does not need to return any results. You can simply change the third parameter of the open call
<%
url = "http://www.espn.com/main.html"
set xmlhttp = server.CreateObject("MSXML2.ServerXMLHTTP")
xmlhttp.open "GET", url, true
xmlhttp.send ""
set xmlhttp = nothing
%>
Finally, you may want to spoof your user agent, since the MSXML object sends something like "Mozilla/4.0
(compatible; Win32; WinHttp.WinHttpRequest.5)" -- many sites will view this as a spider or 'screen scraper', and for
various reasons, might present alternate content -- here are two samples:
<%
url = "http://www.espn.com/main.html"
br = request.servervariables("HTTP_USER_AGENT")
set xmlhttp = server.CreateObject("MSXML2.ServerXMLHTTP")
on error resume next
xmlhttp.open "GET", url, false
xmlhttp.setRequestHeader "User-Agent",br
xmlhttp.send ""
if err.number <> 0 then
response.write "Url not found"
else
response.write xmlhttp.responseText
end if
set xmlhttp = nothing
<%
set xmlhttp = server.CreateObject("MSXML2.ServerXMLHTTP")
' ... stuff ...
on error resume next
xmlhttp.send ""
if err.number <> 0 then
response.write "Error: " & xmlhttp.parseError.URL & _
"<br>" & xmlhttp.parseError.Reason
response.end
end if
' ... stuff ...
%>
What is this 'ASP 0115' error?
or
or
or
For quick symptom relief, simply restart IIS using the batch file referenced in Article #2087
The ASP 0115 error is IIS' way of saying "there was an error, but I don't know the cause." This is because the error
came from something external to ASP (e.g. a custom COM object or an Oracle database).
Listed below are some of the common causes for ASP returning the 0115 error, followed by some recommended
troubleshooting techniques:
● Permission and authentication issues with files and registry keys. (May also produce 80040150 errors (Could
Errors may occur if the authenticated user does not have sufficient permissions on other files such as custom
ASP scripts are typically executed in the security context of the IUSR_<machine_name> account.
If you believe you are dealing with a permissions problem in the registry, you can use Regedt32.exe to
examine permissions on the various registry keys. In particular, you may want to look at ODBC, Jet, ADO,
and other keys that might be relevant to the problem. If you have a machine that is working properly, try
The first step is to determine if you really are seeing a permissions problem. A good test is to temporarily
add the anonymous logon account (IUSR_<machine_name>) to the administrators group using User
Manager. This gives the IUSR_<machine_name> account administrative privileges on the machine. If this
causes ASP to function properly, you are almost certainly dealing with a permissions issue.
Note: When you have finished debugging, be sure to remove the IUSR_<machine_name> account from the
If you are developing COM objects with Visual Basic, you might create a dependency file and compare the
● Use of ScriptingContext with OnStartPage / OnEndPage - use ObjectContext method instead. See this
tutorial for a simple VB6 ActiveX DLL that uses ObjectContext as opposed to ScriptingContext.
● Use of the ASP Session Object prior to version 1.24.09 of the ASP dll
Q177036 FIX: ASP 0115 Error Occur With The Session Object
Get a new version with the latest version of MDAC from http://www.microsoft.com/data/.
Q191979 PRB: VB Component Not Marked Apartment Produces ASP 0115 Error
Q172925 INFO: Security Issues with Objects in ASP and ISAPI Extensions
Q193310 FIX: ADO 2.0 Generates Error 0115 When Used with IIS 3.0
- ensure Stored Procedure parameters without have correct data types or lengths
If you're looking for info on ASP+, Visual Studio 7, and .NET technologies, look here:
Where can I find out about ASP+ (ASP Plus), Visual Studio 7, and .NET?
For "regular" ASP, there are many sites out there. A beginner's tutorial has been published by Microsoft:
The following list is not exhaustive. All sites are free except for one exception:
Action Jackson
ASP-help.com
ASP 101
ASP Free
ASP Hole
ASP Key
DeveloperFusion.com
Developersdex
DevGuru
4guysfromrolla
15 Seconds
IIS Answers
IIS FAQ
Infinite Monkeys
LearnASP.Com
MSDN Online
powerASP.com
Stardeveloper.com
SWYNK
T-Cubed
Ultimate ASP
VisualBasicScript.com
VisualBuilder.com
www.serverscripting.com
aspfaqs.com
Microsoft has created a list of ASP Knowledge Base articles. While they didn't have enough foresight to make them
LINKABLE, at least you can scan the titles with relative ease.
Short of disabling the entire toolbar, you can't. Even then, people can use Backspace or Alt+left. Instead of trying to
disable the features of a browser, build your application so that disabling those features is not necessary. For
example, if your application inserts a record into a table, and you're afraid that if the user clicks back it will insert
another one, there are at least two things you can do (and likely several others): use a session variable to track the
Web Men Talking recently had a decent article describing the various ways to deal with this behavior.
How do I control printing from ASP?
Printing happens on the CLIENT. Therefore, you can only control anything to do with printing, from the client... and
not from ASP. Changing margins, header & footer, hiding some things on your page from printing, forcing page
breaks, and even initiating the printer at all are tasks that can only be accomplished from the CLIENT side. Please
keep in mind that you can't (and shouldn't want to) FORCE the user to print your page, or do so without telling them
(i.e. allowing them to confirm) that you're about to send data to your printer.
This article does a good job of exposing print templates (assuming clients of IE 5.5+):
http://www.webreference.com/js/column89/
Mead Co. provides an effective solution which adds the ability to control many of IE's printing features from
http://www.meadroid.com/scriptx/index.htm
HTMLPrinting.com also has a product that allows you to control printer settings:
http://www.htmlprinting.com/
http://members.tripod.com/~housten/printing.html
You can hide things from printing by using a different style sheet setting for screen and print media types:
http://www.w3.org/TR/REC-CSS2/media.html
And you can force a page break at certain points in your document:
http://www.w3.org/TR/REC-CSS2/page.html
Now whether the 'other' browser is up to all these tasks is beyond me... and isn't really on-topic here. This is an ASP
Screen Resolution has always been a tough egg to fry. In most browsers, you can't get a screen resolution. But if
you can, what do you plan to do with it? If I have a screen resolution of 1600x1200, it doesn't mean that my
browser is that size. So if you create a table that is 1550 pixels wide, just because you've detected my resolution, if
my browser is not maximized I'm going to have to scroll all over the place to see the whole page.
In IE 4 and Netscape 4, you can detect a much more meaningful variable, the width/height of the browser window.
*****
IE 4+:
<script language="JavaScript">
var w = document.body.clientWidth;
if (w>=650)
{
window.location.href="bigscreen.htm";
}
else
{
window.location.href="smallscreen.htm";
}
</script>
*****
NN 4:
<script language="JavaScript">
var w = window.innerWidth;
if (w>=650)
{
window.location.href="bigscreen.htm";
}
else
{
window.location.href="smallscreen.htm";
}
</script>
Of course this leaves out the 3.0 browsers. Here is how to handle this in IE 3.0, which provides the SCREEN size
*****
IE 3:
<%
smallurl = "smallscreen.htm"
bigurl = "bigscreen.htm"
a = request("http_ua_pixels")
url = smallurl
if instr(a,"x")>0 then
a = split(a,"x")
if clng(a(0)) >= 650 then
url = bigurl
end if
end if
response.redirect(url)
%>
And NN 3.0 has to be different... in that case, to get the screen resolution (again, not the more meaningful browser
*****
NN 3:
<script language="JavaScript">
var Sizer=java.awt.Toolkit.getDefaultToolkit()
var ScrSize=Sizer.getScreenSize();
var ScrW=ScrSize.width;
if (ScrW >=650)
{
window.location.href="bigscreen.htm";
}
else
{
window.location.href="smallscreen.htm";
}
</script>
*****
So, the ASP solution (which, admittedly, only takes into account the two major browsers; Opera and others will need
special considerations):
<%
bigurl = "bigscreen.htm"
smallurl = "smallscreen.htm"
A = LCase(Request.ServerVariables("HTTP_USER_AGENT"))
if instr(A,"msie 5")>0 or instr(A,"msie 4")>0 then
%>
<script language="JavaScript">
var w = document.body.clientWidth;
if (w>=650)
{
window.location.href="<%=bigurl%>";
}
else
{
window.location.href="<%=smallurl%>";
}
</script>
<%
elseif instr(A,"msie 3")>0 then
smallurl = "smallscreen.htm"
bigurl = "bigscreen.htm"
a = request("http_ua_pixels")
url = smallurl
if instr(a,"x")>0 then
a = split(a,"x")
if clng(a(0)) >= 650 then
url = bigurl
end if
end if
response.redirect(url)
<script language="JavaScript">
var w = document.body.clientWidth;
if (w>=650)
{
window.location.href="<%=bigurl%>";
}
else
{
window.location.href="<%=smallurl%>";
}
</script>
<%
elseif instr(A,"zilla/3")>0 then
%>
<script language="javascript">
var Sizer=java.awt.Toolkit.getDefaultToolkit();
var ScrSize=Sizer.getScreenSize();
var ScrW=ScrSize.width;
if (ScrW>=650)
{
window.location.href="<%=bigurl%>";
}
else
{
window.location.href="<%=smallurl%>";
}
</script>
<%
else
respose.redirect(smallurl)
end if
%>
This is a lot of manual work. BrowserHawk will do this for you automatically, taking all browsers into effect.
How do I schedule ASP files?
1. Use the AT command and Windows Scripting Host (or the more rudimentary task scheduler) to schedule a
First, change the ASP to a VBS file. This is accomplished by (1) changing the extension to VBS; (2) changing
all server.createobject calls to createobject; and, (3) removing all <%%> delimiters and any browser-
destined code (for example, response.write statement or client-side HTML). I didn't run into any further
You store the VBS file in the filesystem, and use the AT command to schedule it (this actually schedules its
execution with NT's schedule service). At a command prompt, you can use AT by itself to see a list of tasks
currently in the schedule. You can use AT /? to find out all its syntax possibilities.
For example, to get a file to run every weekday at 9:00 am, I launch this batch file (the first line clears
existing entries):
at /delete /y
at 9:00 /every:m,t,w,th,f d:\net\shared\getdata.vbs
Notice there is no web server involved; the file is accessed directly through the file system. Once I got over
the "a user has to be logged in" and "the tasks have to be reset when rebooted" hurdles (both of which I
believe are problems with the particular machine that is not under our control), all has been running fine for
me.
For an example of using WSH, CDONTS and the Task Scheduler to send out e-mails on a regular basis, see
Q221495.
2. If all you are doing is database work in SQL Server, you might consider using a job. This will allow you to
keep all the processing of the job within your database, and prevent the complications associated with
multiple systems, connections, and adapting ASP code to be non-ASP-like in behavior. See Article #2403 for
using an application variable with the last time the function was run and checking to see if the right amount
Along the same lines, check out this article at powerasp.com - it shows you how to use global.asa, a text file
and datediff to schedule execution... though timing is never exact because it relies on visitors actually hitting
your site.
4. Build an ASP page and leave a browser open on the machine, with a <meta> refresh (this is by far the
ultimate kludge).
Why do I get a 500 Internal Server error for all ASP errors?
If you're using IE5 and/or developing on Windows 2000, you might find it difficult to debug ASP errors in a browser.
This is because IE5 has a ridiculous default option that suppresses errors to a more "friendly" error (which, IMHO, is
a lot more cryptic than what they'd get otherwise). This comes back to the user as a 500.100 Internal Server Error
(ASP 0147), and doesn't leave the user much information to pass on to the webmaster, except to tell them that "The
To circumvent this silliness and get real ASP errors, go to IE's Tools/Internet Options menu, and on the advanced
After you've disabled this default setting, refresh the page in question. There are four possible outcomes: (1) the
page will magically work again; (2) the page will give you a more detailed error (e.g. Stack Overflow or Syntax
Error), including a line number; (3) you will get "Server Application Error" - which means that at some point IIS got
confused about the current application; or (4) you will still see non-descript error messages.
If (4) is what happens, open Internet Services Manager, go to the home directory tab of your default web site or
application, click on configuration, go to the Debugging tab, and make sure "Send detailed error messages to the
client" is selected. Click Apply/OK/OK etc. to get out of there and try your page again.
If (3) is what happens, you can remedy this simply by going into Internet Services Manager. Right-click the
application in question (or Default Web Site, if an application is not relevant), select properties, hit the "Home
Directory" tab, click the "Remove" button, and then click the "Create" button. Follow with Apply/OK etc and get out
of Internet Services Manager. Refresh your page, and all should be well again.
If you are still getting errors like 'page not found' then go into Internet Services Manager, right-click Default Web
Site, choose Properties, and on the Home Directory tab, click the Configuration button. On the App Debugging tab,
Finally, check the event log, as occasionally there is more information there than you will see in the browser. Of
course this is even more true if you can't change the IIS or browser configurations.
Session_OnEnd is unreliable. Do not create applications that rely on Session_OnEnd to occur, because it
doesn't always happen. This is a known bug and seems to have been reduced (if not eliminated entirely) in
IIS 5.0, which ships with Windows 2000. This bug is one of many reasons to not store recordsets,
So is there any way to get around this? Well, let's say you have an application that stores session data in a
database. When the session ends, you want to clean up that data, right? If the user logs out properly, you
can do this from session_onEnd. If leave your site in any other way, the session data will be stored forever.
For this example, let's say your session timeout is the default, 20 minutes.
On every page, you should update the session record(s) to reflect the CURRENT time for that user. This way,
the record will become stale if there's no activity... and if they don't log out, you still have some way of
identifying that their session has, indeed, expired (whether that window is still open, or they've gone to
Then you could create a stored procedure that runs every 20 minutes (or the value of session timeout, or
every hour, or once a day) by scheduling a job through SQL Server Agent. This stored procedure would scan
the table and set inactive (or delete) any records older then <x> minutes. You could also use it to delete
any temporary files or folders that the user was using on the server during their session.
Session_OnEnd does not support the request, response or server objects. The only built-in objects you can
use are session and application. So, for example, if you need to use a server.mapPath directive, store the
fully qualified path in an application variable BEFORE the session ends. However, in limited testing, I was
able to use these objects in files #included in global.asa (for the technique, see Article #2144).
How do I use ASP to [...]
...access a user's favorites list, make my site their home page, delete their cache, delete their history,
remove one item from their cache, remove one item from their history, write to a text file on their hard
drive, figure out their home page, determine their custom settings, put a shortcut to my home page on
their start menu, force them to download a file without a prompt, change their default download folder,
adjust their page margins for printing, automatically print a page without a prompt, change their default
printer, disable the edit button, disable view source, disable the back/forward buttons, change their
default browser/e-mail client/newsreader, "borrow" their e-mail address, read a key from their
registry, change their security settings, force them to save my web page to their hard disk, change their
"browse in a new process" setting, change their screen size/resolution/color depth, force them to
download an ActiveX control/plugin, automatically run an EXE on the client, force them to enable
cookies, grab or delete files from their machine without asking, or force them to view my Java applets
A. You don't. At least not from ASP (and preferably not from anywhere else either!). There are very good
reasons why each of these things is either extremely difficult (usually requiring an ActiveX control) or
impossible. To get a better sense of this, imagine how you would feel if the aspfaq.com site (or any other
site, for that matter) did any of these things to YOUR computer.
How do I control access to an area?
If you want to roll your own permissions, then creating a login for a section of your web site is fairly easy. First,
<%
u = lcase(request.form("username"))
p = lcase(request.form("password"))
'---------------------------------------------------------
'-- check to see that the form was completely filled out--
'---------------------------------------------------------
if u="" or p="" then
response.redirect("loginForm.asp")
end if
'---------------------------------------------------------
'-- check for a match, this could be against a database!--
'---------------------------------------------------------
if u<>"myusername" or p<>"mypassword" then
'access denied
response.redirect ("loginForm.asp")
else
Finally, at the top of each page, you test the session variable that you assigned in the script above:
<%
if not session("login") then
response.redirect("loginForm.asp")
end if
%>
You could also do something similar using a database, where you would only have to check that the username and
password exist in a table of many u/p combinations (in the above example, you have to manually program each
1. Make sure your system date and time are valid. If you post in the future, your question will rarely be
2. Please be specific. Don't say that a page you have "does not work" or is "broke." Define what "does not
work" means. Display your code, particularly the line that is causing the error (along with the exact error
message). If you are getting database errors, copy the error verbatim *and* include the resulting SQL string
that is being attempted (most errors that are posted to the groups would have been caught long before that,
had the user replaced conn.execute(sql) with response.write(sql)). Specify which version of everything
connecting to (and version); and which version of MDAC you are using. The more information people have,
3. Please do not cross-post to all 3 groups just because they're about ASP. Someone will answer your question
if you post it in the right group. If you put a blanket question out across all 3 groups in an effort to get more
response, many people (myself included) will be much less eager to answer your question. This is mainly
because the experts in the database group are not likely able to answer your questions about CDONTS, or at
least that's not their specialty. So to them, your post is just noise as opposed to signal. Also, many server
4. CHECK THIS FAQ FIRST. If you ask a question that's already answered in the FAQ, at least one of the
answers you get will be a link here. Again, this simply wastes time and bandwidth. Please look over the
recent posts in the newsgroup. 80% of new posts are ones that were asked and answered within the past 2
days, and too often within the past 2 hours. Doing a quick scan of the topics in the newsgroup might save
you from posting your question at all, let alone waiting for a response (which, in this case, would often just
be directions to scrolling down to a specific post anyway). Along these lines, Google is an outstanding
resource; I recommend you all try out its advanced search feature. Instead of waiting for answers from
people who may be listening in this group today and have time to post an answer, you can enter your search
criteria, time frame, specific groups (e.g. '*asp* or *iis*') and you can even include (or exclude) a poster's
name from the result set. Their database is humungous, encompassing all posts made to these groups at
least...
5. Please provide a relevant and meaningful subject line. "Help! It's broken." or "I have an ASP problem" are
not very helpful subject lines. To make it easier for people to help you, put something more specific there,
like "SQL Error: Error in update statement" or "DeleteFile() error: object doesn't support this property or
method."
6. Please do not lash out unreasonably at people trying to help you, just because they didn't provide the exact
answer you were looking for, or pointed you to sufficient documentation instead of dropping everything to
write your entire application for you. Keep in mind that these people are volunteers and they are helping you
of their own free will. Mistreat them, and they'll stop helping you. If you don't like an answer they give,
move on to the next one... and if you absolutely *must* call names, do it in private mail.
This question is asked a LOT. "How do I set a session variable equal to something that was just created in client-side
script?" You CAN'T. Because ASP is processed on the server, and JavaScript isn't processed until AFTER the results of
ASP have been passed to the client, the only way to send JavaScript variables to ASP is to invoke another ASP page
(e.g. submit a form, client-side redirect, auto-post to a hidden frame, etc.). Makes sense too, since you can't USE
the new session variable until you load another ASP page anyway.
Having said all that, there are workarounds. Several examples are given in Article #2281
How do I get the user's IP address or browser information?
The user offers up many environment variables without even knowing it. You can learn a LOT about these variables
<table>
<%
for each x in Request.ServerVariables
Response.Write("<tr><td>" & x & "</td><td>")
Response.Write(Request.ServerVariables(x))
Response.Write("</td></tr>" & vbCrLf)
next
%>
</table>
<table>
<script language='JScript' runat=server>
var svColl = Request.ServerVariables();
for(sv = new Enumerator(svColl); !sv.atEnd(); sv.moveNext())
{
var nm = sv.item();
Response.Write("<tr><td>" + nm + "</td><td>");
Response.Write(svColl(nm) + "</td></tr>");
}
</script>
</table>
Note that in the case of the user's IP address, which is stored in the REMOTE_ADDR variable, this value isn't reliable.
Anyone behind a proxy / firewall will reflect the IP address of the proxy rather than their own machine, so people at
big companies can all appear to be the same user. This case is even more significant with AOL, which has a very
limited set of IP addresses representing their entire user base of millions. The best form of unique idenitification is
going to involve sessions (short-term) or cookies (repeat visits), if the user accepts cookies. Over the long term, you
can store a GUID, IDENTITY value, or something else that will uniquely identify this user in the future, in a database.
Keep in mind that the user can, at any time, delete their cookies. So if their information is important, you might also
provide some way for them to re-establish their cookie through a login mechanism.
How do I protect my ASP code?
As we all know, ASP code is relatively safe from surfers. When people try to download ASP code, all they see is the
Many people are concerned, however, about protecting their ASP code from prying eyes (either code they are
You can compile your code into DLLs. Often this isn't an option, because most hosts won't accept custom
components (some, like Data Return, will accept it -- if you turn over administrative control and allow them to
review your source code). Read this article for a tutorial on creating a simple VB6 ActiveX DLL.
The other option is to use Windows Script Encoder. However, before you decide to go this route, make sure you read
this article, which demonstrates how easy it is to reverse engineer these 'encoded' scripts.
Where do I get ASP?
=====================
=====================
The latest version of Personal Web Server / IIS is 4.0. It includes ASP 2.0, and is found in the Windows NT 4.0
Option Pack, which you can download here. If you are running NT 4.0 and IIS 3.0 / Peer Web Services, these
products do NOT include ASP. You will have to find ASP.exe to make ASP work; however, I'm not going to place the
link here because I strongly believe that you should use version 4.0.
After installing the Option Pack, you'll want to [re-]install Service Pack 6a (which, among other things, updates
ASP.dll and fixes several security holes in IIS), and also keep your MDAC components up to date by watching the
=====================
Windows 98
=====================
Find "pws.exe" on the Windows 98 CD. THAT is the version you should be installing, NOT the one from the web site
and NOT the one from the FrontPage CD. After installing PWS, keep your MDAC components up to date by watching
=====================
=====================
Windows 2000 includes a scaled-down version of IIS 5.0. If you didn't install IIS during initial installation, you can
find it under Control Panel >> Add/Remove Programs >> Add/Remove Windows Components.
=====================
=====================
Article #2079 contains a description of Microsoft's lack of support for these configurations, as well as workarounds to
=====================
Windows XP Professional
=====================
Windows XP includes a scaled-down version of IIS 5.1. You will likely have to go to the Control Panel >>
Add/Remove Programs, Add/Remove Windows Components to configure this service, as it's not installed by default.
All "scaled-down" versions of IIS / PWS have the following restrictions: 10 connection limit, you cannot use host
headers / multiple web sites, and you cannot change the port of HTTP services.
How do I manage a session across multiple windows?
You can't do this reliably. This is because Internet Explorer has a setting called "browse in new process" which, if
enabled, forces a new session with each new window. Use a key in the querystring, tied to a cookie or a form, if
session state across windows is a necessity. See the fake volleyball store for a working example.
Microsoft says there is no direct workaround for this, but goes into more detail about how it can happen randomly in
Q196383.
Also, starting with IE 5.01, this setting is no longer in the Advanced Options, but pre-determined -- depending on
The short version: don't rely on maintaining sessions across multiple windows.
How can I give them a better 404 message?
That boring 404 error message leaves a lot to be desired. Wouldn't it be nice to be able to log those errors, find out
where the bad links are coming from, and most importantly, inform the user that the URL they entered is incorrect
(and, where applicable, suggest to them where they should have gone)?
Using a feature of IIS 4 and IIS 5.0, you can do all of these things and more. What you want to do is create a page
in your site called 404.asp. This is the page that will be presented instead of that stock grey page, so you'll likely
want to make this page look like the rest of your site. Here is an example.
One important thing to note is that this page should reference all images and links relative to your root folder, as the
404.asp file will actually execute in whichever folder it's called from. For example, if you have <img
src="image.gif"> and that image only exists in your root folder, then if someone calls /folder/no-exist.htm, the
image will show up as a broken link because it does not exist in /folder/ ... therefore you should always use <img
src="/image.gif"> or <img src="/images/image.gif">. Same goes for links, they should be relative to the root URL,
Once you have built a standard 404 page, you can make IIS intercept 404 errors and present this page instead by
7. In the URL> textbox, type the relative path of your 404 page
You may want to add a bit of flexibility to this code; for example, you can display the page they were trying to reach
Keep in mind that once you hit the 404 ASP page, you no longer have access to any anchor or querystring
There are many other things you could do, such as send an automated e-mail or append a log file every time
someone hits a 404. After collecting data on non-existent pages that are queried often, you could anticipate them by
checking the value of b above and suggesting where they should go instead.
For a more in-depth article on building a 404-centric application, see the following:
http://msdn.microsoft.com/library/en-us/dnvb600/html/404track.asp
Note that .NET Server, at least in the few builds following Beta 3, does not support URL for custom 404 error pages.
Still waiting on word about why this was left out, whether it will return before IIS 6.0 is released, and if not, what it
will mean for users upgrading and expecting to continue using URLs for this purpose.
Why do I get an HTTP Header or Object Moved error when trying to redirect?
Header Error
The short answer is to execute any response.redirect calls before any client-side code, including the opening <html>
tag. The long answer is to use response.buffer = true first, which can allow you to display content before
redirecting...
Object Moved
When using Response.redirect with certain browsers, you can get the Object Moved error message. One way to
prevent this from happening is using response.clear first (note that buffering must be enabled):
<%
Response.Expires = 0
Response.Buffer = true
' ...
Response.Clear
Response.Redirect "http://www.domain.com"
%>
This is an undocumented 'feature' of IIS 4.0 (but fully documented in IIS 5.0).
There are several components which will allow you to determine if cookies or javascript are SUPPORTED. You can
even do it yourself, with minimal knowledge of browser versions (and where in the dev timeline these features were
introduced). But knowing whether or not they're supported is far less than half the battle: the key is to detect
There is a component that tells you whether these things are enabled (NOTE: it does NOT do this exclusively from
the SERVER), and many other important facets of a unique user's browser. It's called BrowserHawk:
http://cyscape.com/products/bhawk/bcadv.asp
You can do this yourself as well, but it's a much lengthier coding process.
Cookies:
Set a cookie, redirect, and try to read that cookie. If you can't, then cookies are disabled. Here is some
sample code:
cookietest.asp:
<%
response.cookies("enabled")="1"
response.redirect("cookietest2.asp")
%>
cookietest2.asp:
<%
if request.cookies("enabled")="1" then
response.write("cookies are enabled")
else
response.write("cookies are not enabled")
end if
%>
You can also try code like this, on any but the first page accessed:
<%
cook = Request.ServerVariables("HTTP_COOKIE")
if len(cook)<2 or cook="" then
response.write("Your cookies are disabled.")
else
response.write("Your cookies are enabled.")
end if
%>
JavaScript:
Populate default.asp with a hidden form (with POST method, action=default2.asp) and a link to default2.asp
in <noscript> tags. Use JavaScript to post to the next page, then try and read from the request.form
collection. If it's empty, they used the link (and Javascript is disabled). One caution: Netscape 2.0 will
scripttest.asp:
<form method=post action=scripttest2.asp name='hiddenform'>
<input type=hidden name=enabled value="1">
</form>
<script>
document.hiddenform.submit();
</script>
scripttest2.asp:
<%
if request.form("enabled")="" then
' user bypassed checker
response.redirect("scripttest.asp")
else
if request.form("enabled")="1" then
response.write("scripting is enabled")
else
response.write("scripting is not enabled")
end if
end if
%>
This solution is a little messy, as most users will see the first page for an instant. However if this information is
While ASP runs best, easiest and completely on IIS with NT Server / Windows 2000, there are some products
available which allow you to run ASP on other web servers (and even other platforms).
ChiliSoft (www.chilisoft.com) have several ports for other platorms, including O'Reilly's, Apache and Netscape on NT,
Apache::ASP is a port for Apache that allows you to run ASP (only PerlScript is supported).
YES.
(a)
<%@language="vbscript"%>
<%
Response.write("3<p>")
%>
(b)
<%@language="JScript"%>
<%
Response.Write("3<p>");
%>
Pay particular attention to the order of the numbers that display on the screen, and compare it to the order of
execution in your scripts. In the best case, by mixing the two techniques you could have odd displays such as this.
In the worst case, you could have functions that are undefined, because you actually called them before you
initiated them.
My biggest recommendation: don't mix <%%> and <script runat=server>. Use one or the other.
I see these questions all the time, here are the answers:
A. IIS 5.0 is not available for Windows 95, Windows 98, Windows ME or Windows NT 4.0. If you need
any of the new functionality in ASP 3.0, then you also need to upgrade to Windows 2000. IIS 5.0
cannot be downloaded... it is on the media CD for your OS, and can be installed by going to
A. PWS does not exist in the world of Windows 2000. Pro, Server and Advanced Server all ship with IIS
5.0. There are still connection limitations when you run IIS from Professional (as with PWS on
Workstation).
3. Where else can I learn about IIS 5 and ASP 3.0? (A.K.A. what's the difference between IIS 4 and
IIS 5?)
There are three valid answers to this, depending on how you want to handle it.
(1) If you need to make sure that someone has been to a.asp before you will process anything on b.asp, you can set
a session variable in a.asp that flags a.asp as having been visited. Check for this session variable in b.asp; if it
(2) If you need to make sure that someone goes directly from a.asp to b.asp, check
Request.ServerVariables("HTTP_REFERER") in b.asp. If it's not a.asp, then they didn't arrive here through the proper
route.
(3) If you need to make sure that b.asp is only accessed once per session, you can set a session variable that says
("been_here_already")=true ... in that page, check to see if that variable exists... if it does, that means they've
If you have disabled Anonymous access, then you should be able to retrieve the value from:
<%
Response.Write Request.ServerVariables("LOGON_USER")
' or
Response.Write Request.ServerVariables("AUTH_USER")
%>
Note that your users must be using Internet Explorer to support NT Challenge/Response (IIS 4.0) or Windows
If you need to support Netscape as well, then you can access these variables by enabling Basic Authentication as
well as Windows Authentication. Note that this method of authentication is slightly less secure, since the password is
sent in plain text (with Windows Authentication, IE encrypts the password as it is being sent across).
If you can't disable Anonymous access, then there is a possible alternative, provided you're not using DHCP. If your
users have static IP addresses, you could store their usernames in a table and do a lookup against their IP:
<%
Response.Write Request.ServerVariables("REMOTE_ADDR")
%>
If you can't enforce either of those things, then you may have to resort to forcing your users to log in (even only
once, then storing a cookie). I suppose this depends on balancing the importance of knowing who is on the site
This usually occurs with new methods or functions, such as FileSystemObject, try{} or With. The cause is that you
have a script library that is older than the documentation or sample code you are working from. Here are the errors
or
or
or
or
Keep in mind that there is always the outside chance that you misspelled the property/method, or are using a
If you are running Windows 2000 or better, then it is almost certain you are either misspelling the method/property,
or using a method/property that doesn't exist in the component you are accessing. An exception might be new
methods introduced in the 5.5 or 6.0 scripting engines, such as JScript's new push method for arrays.
For information on comparing versions between machines, see Article #2133.
For information on when features were introduced, see these charts for VBScript and JScript.
Well, they say there's no such thing as a free lunch. In most cases, I'd have to agree. Most web hosts out there
expect something in return for their 'free' web space, usually in the form of mandatory advertising (often including
Below is a list of ISPs offering free web space on NT servers, allowing you to use ASP. Most have limitations, be it
performance, or space, or database access, and of course almost always advertising. But filter through them, you
might find one you like. Let us know if you find a great deal.
www.webhostme.com
www.brinkster.com
http://www.actionjackson.com/hosts/results.asp?cost=0
You can also search for good deals at www.webhostdir.com and www.hostindex.com ... they might not be free, but
many are under $10/month (and/or have a "one-time, lifetime" fee). For example:
www.atfreeweb.com
www.atrax.ws/hosting
www.awebhosting.net
www.gzinc.com
www.hostingplans.com
www.sharpwebservices.com
www.usware.net
www.webstrikesolutions.com
www.2globalmart.com
www.cheap-web-hosting.g2gm.net
asp4free.ravenna2000.it/ (Italian)
A word of warning though: don't sell this kind of deal to a client. For your own personal web space, they might be
okay (and even then, I would stick to those who have a refund policy)... Experience has proven to me, time and
time again, that you get what you pay for. Once in a while you'll find a gem, but when it's someone else's business
Methods like Server.Transfer and Server.Execute are not in Visual InterDev 6.0's IntelliSense (the little drop-down
that completes methods and properties for you). Many people have asked why this is, especially when InterDev is
installed on Windows 2000 with IIS 5.0 and ASP 3.0 installed.
I will try to explain what is going on, from my limited vantage point. IntelliSense itself gets its information from type
libraries; in this case, it is a file with a .tlb extension. Since InterDev 6.0 does not detect which operating system /
web server version it is being installed alongside, it ships with ASP 2.0's type library, not ASP 3.0's -- hence the
So how do you get the new type library to be recognized by Visual InterDev? Is it even possible?
Yes, it is possible; observe the screen shot at left. It includes server.transfer and
server.execute.
(Note also that Server.URLPathEncode, an undocumented method, has been removed from the new type library.)
The instructions for creating this TLB file are documented in Q261101 -- but the methodology behind it requires
OLEView and midl.exe to be installed and enabled on your machine. Also, if your machine isn't running ASP 3.0 (e.g.
you develop on NT 4.0 and deploy to Windows 2000), you have to copy the new asp.dll to your local machine... I
It seems that with the Visual Studio.NET Betas and Windows XP Professional (RTM Build 2600), you don't need to
add this type library at all.
Of course, if you are afraid of messing up your system, do not alter your environment. This operation is not officially
supported by Microsoft.
What is wrong with Request.ServerVariables("HTTP_REFERER")?
The situations where this servervariable works include the following methods of a browser loading a URL:
Here is the code I used to test with. Ref.asp contains the following code:
<%
response.write "Referer: " & request.servervariables("http_referer")
%>
<meta
http-equiv='refresh'
content='10;URL=ref.asp'>
<a
href='ref.asp'>Go
there - straight link</a><p>
<a
href='#'
onclick='window.location.href="ref.asp";return false;'>Go
there - javascript href</a><p>
<a
href='#'
onclick='window.location.replace("ref.asp");return false;'>Go
there - javascript replace</a><p>
<a
href='#'
onclick='document.getform.submit();return false;'>Go
there - javascript GET</a><p>
<a
href='#'
onclick='document.postform.submit();return false;'>Go
there - javascript POST</a>
You'll notice that in Netscape 4, the referer is translated properly in all cases. With this exception, I think we're all
glad, overall, that the number of users with Netscape 4 is quickly diminishing! :-)
When I run a page in my browser, why does the ASP code not execute?
There is usually a limited number of reasons why this would happen. They are as follows:
■ you did not save your page with an .asp extension (or a file type associated with asp.dll)
■ you did not place your ASP page in your web root / structure, or you put it in a place that does not have
■ you accessed the file by typing c:\<path>\<file>.asp or by double-clicking it within Windows Explorer (ASP
■ you expected to response.write code using <% varname %> syntax when you should use <%= varname
■ you tried to preview your ASP page in a WYSIWYG editor (like InterDev or FrontPage)
If none of the above applies to you, then it is likely that you have some kind of misconfiguration (or else you are
getting an error message). Post your configuration, code and any errors you get to
I have been using custom 404 error pages for a long time -- I wrote an article on the technique for MSDN in October
of 1999, and have a straightforward example in Article #2162. I have tried several experiments using this feature,
One of these is that when you add a favorite in Internet Explorer, the browser searches for the web site's "favorites
icon" - or favicon.ico. (For more info, see www.favicon.com.) This is how your favorites end up with branded icons
for some of the web sites you go to, and how http://www.msn.com/ and other web sites have their icon in IE's
address bar.
Note that you can use a different ICO file if you prefer... favicon.ico is only the default. You can override it
Well, if the browser searches for this file and can't find it, you can capture the event with a custom 404 page. I
<%
' ... valid connection "conn" established
qs = lcase(Request.ServerVariables("QUERY_STRING"))
if right(qs,12)="/favicon.ico" then
conn.execute("EXEC FAQ_bumpFavIcon")
end if
' ...
%>
I'll leave it as an exercise to the reader how I display it on the home page. For most people, this should be pretty
self-explanatory. :-)
How can I stop Photoshop from opening ASP files?
This is a known issue, but only occurs if you install Photoshop AFTER applications such as Visual InterDev (or,
shudder, FrontPage). When setting up a new system, do yourself a favor, and be sure to install Photoshop FIRST.
Here are the steps to fix your problem the right way:
(6) Next time an asp page is double-clicked, you will get the "Choose a program to run this file" window. Choose the
application that you would like to use to edit the code (e.g. Notepad or InterDev) and make sure that the check box
Netscape is much more stringent about properly formatted HTML documents. If, for example, you use a <table> and
there is no closing </table>, the table will not be displayed. If Netscape is in your target audience, make sure that
all HTML tags (at least those that require it) have closing tags. Mind you, it's not a bad habit to follow even when
For most timeout situations, my first suggestion would be to improve the code's efficiency so that it does't time out
at all. I realize, however, that some timeout issues are beyond the developer's control (e.g. waiting for a response
from a remote server). Also, there are many scenarios where you would want to increase the session timeout so
Server.ScriptTimeout
This value indicates how long, in seconds, the server will let an ASP script run until it is stopped by the server. The
default value is 90 seconds. An error occurs if a script requires more time than this value allows (see Article #2366),
but you can override the default. To change this value in an individual ASP page, you can add this code:
<%
Server.ScriptTimeout = 180
%>
To change this value for an entire application, open Internet Services Manager, go to the Home Directory tab of the
application, click on configuration, and alter the ASP Script timeout field on the "App Options" tab. If you do not
have access to the web server directly, you can also use ADSI to change this value on a per-application basis. Keep
in mind that with this method you can only override the default with a value GREATER than that stored in the
metabase - if you try to lower the current value, the change will not take effect.
The minimum value for ScriptTimeout is 0. If you try to set it to a negative number (e.g. -1 is often associated with
The maximum value for ScriptTimeout is 2^32-1, or 2147483647. If you try to set it to 2147483648 or higher, you
(or even browser) will ever wait that long for a page to render.
Session.Timeout
This setting controls how long, in minutes, a user's session will last. While it is wise to keep this value short for
efficiency's sake, there are cases where that's just not enough time for users to get things done in your application
(for example, if you have a client-side tool where the user is changing properties but not making requests to the
server until they are done). The default is 20 minutes, and once 20 minutes of inactivity has occured, the session
expires and all session variables are lost. You can increase the session timeout in an ASP page or in global.asa's
<%
Session.Timeout = 45
%>
To change this value for an entire application, open Internet Services Manager, go to the Home Directory tab of the
application, click on configuration, and alter the Session timeout field on the "App Options" tab. This metabase
conn.connectionTimeout
Where conn is an ADODB.Connection object that is not yet open, the connectionTimeout property will indicate the
amount of time, in seconds, to wait for an ASP application to initially connect to the data source. The default is 15
<%
Set conn = Server.CreateObject("ADODB.Connection")
conn.ConnectionTimeout = 120
conn.Open <connectionString>
%>
commandTimeout
CommandTimeout tells the server how long to wait, in seconds, for completion of any command sent to the data
source. This value is editable before and after the connection has been opened. The default is 30 seconds, but you
<%
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open <connectionString>
conn.CommandTimeout = 120
%>
Note that you can also apply a commandTimeout value to a command object, and it will behave independent of the
commandTimeout value associated with the connection object. So for specific stored procedures or other commands
being executed explicitly through the ADODB.Command object, you could have a longer timeout than that of the
connection object. The syntax for the command object is almost identical:
<%
Set cmd = Server.CreateObject("ADODB.Command")
cmd.CommandTimeout = 120
%>
Note that Oracle drivers do not support the commandTimeout property, up to and including MDAC 2.7. See Q251248
XMLHTTP timeouts
For information on increasing or decreasing the time allowed for the XMLHTTP objects to retrieve a response from a
According to Q245574 and Q297795, you can configure IIS to handle DNS lookups. Barring that ability, here are
AspDNS (ServerObjects)
ASPDNS
BrowserHawk
DNS LookUp
DSDNS
DynuDNS
HexDNS
LyfDNS
Simple DNS+Traceroute
Remember that reverse DNS lookups on every request can be a significant burden on your server... so only
implement one of these solutions if you really need it! Also remember that performing a reverse DNS lookup doesn't
necessarily give you accurate results. Statistics Server, for example, returns the state that AT & T's regional office is
Often you will want a user to download / save a TXT, XLS, GIF, HTML or other file type that the browser would
normally simply open and display within the browser (either from an HREF, a response.redirect, or a save target as).
Here is one method I've used to force a prompt, in a file called ml1.asp:
<%
fn = "ml1.jpg"
FPath = "c:\" & fn
Response.ContentType = "application/asp-unknown" ' arbitrary
Response.AddHeader "content-disposition","attachment; filename=" & fn
Set adoStream = Server.CreateObject("ADODB.Stream")
adoStream.Open()
adoStream.Type = 1
adoStream.LoadFromFile(FPath)
Response.BinaryWrite adoStream.Read()
adoStream.Close: Set adoStream = Nothing
Response.End
%>
And from the page where you want to launch this Save As dialog, you can do any method of redirection. Here are a
few examples:
<%
response.redirect("ml1.asp")
%>
<script>
window.location.replace('ml1.asp');
</script>
Now keep in mind that the user can just decide to open it if they choose.
Why am I having problems with Server.Execute and/or Server.Transfer?
If you are running IIS 4.0 / PWS 4.0 (or earlier), you will get the following error when trying to use server.transfer:
In IIS 4.0, use Response.Redirect, since Server.Transfer and Server.Execute were introduced in IIS 5.0.
If you are trying to pass QueryString values to Server.Transfer or Server.Execute, you will receive the following
error:
Workarounds include using session variables to keep passing parameters between pages, using Response.Redirect
instead, or making sure that the *source* page is called with QueryString values. For example:
Source.asp
<%
server.transfer "target.asp"
%>
Target.asp
<%
Response.Write(Request.QueryString("foo"))
%>
Now load http://localhost/source.asp in your browser, and then add the QueryString parameter ?foo=1. You will
notice that pages executed by the transfer and execute methods still have access to the QueryString parameters of
If you try to use an absolute URL, in IIS 5.0 you will get the following error:
or
In IIS 6.0 you get the MapPath problem mentioned above; assumedly, they changed the order of
parsing/interpretation so that the : character actually prevents the call from being made at all.
Since you can only transfer to, or execute, ASP pages on the same application, there is no reason to use a fully
qualified URL. If you need to go to a server (even if it's the same machine) by a different domain name, use
Response.Redirect.
One other difference between Response.Redirect and Server.Transfer / Server.Execute is that if the target page uses
variables from the Request.ServerVariables collection (e.g. PATH_INFO, SCRIPT_NAME and URL), they will reflect
the URL of the *source* page using the newer methods, while Response.Redirect with reflect the URL of the
*target* page.
Can I run IIS on Windows Millennium or Windows XP Home?
Many people switching from Windows 98 to Windows ME or XP Home Edition have been disappointed to find that
PWS is not included. Microsoft also does not support the installation of any web server on these products. Many
people have witty answers like "install Apache" or "switch to Linux." These might be realistic workarounds in their
eyes, but are clearly not an option for most people who would be asking the question in the first place.
Q266456 Personal Web Server Is Not Included with Windows Millennium Edition
Q304197 Personal Web Server Is Not Included with Windows XP Home Edition
The latter KB article, when it was numbered Q310090, actually described how to work around the problem in
Windows XP. They have since removed that article and renumbered it, and the advice they gave has vanished
without a trace (that'll teach me to not copy it locally!). Their current statement is that the only way to get web
server support within XP Home is to upgrade from a previous 9x-based OS with a web server installed.
If you're going to go that route (or if you have yet to upgrade to XP Home), here is a helpful link for getting the
http://billsway.com/notes_public/PWS_WinMe.txt
(If you have Windows 95 or Windows 98, and don't have a web server installed, see Article #2075 for info on
On the other hand, if you want to experiment some, you can see this advice from Richard Sandoz, who explains how
http://tinyurl.com/3ahy
Finally, just for giggles, here is a tutorial for setting up an Apache web server on XP Home:
http://rain.prohosting.com/~starman2/apache.shtml
If you need ASP support, I recommend developing on Windows 2000 Pro or Windows XP Pro. If your machine can
support it, I would use Windows 2000 Server or Advanced Server. You should use whatever environment would
most accurately reflect your true production setting. I don't know too many commercial web sites that are running
on Windows 9x, but I do know that there are several problems reported daily by people developing in one
architecture and deploying on another... these are mostly due to security / permissions issues on the deployment
A minor change introduced with IIS 5.0 may, in obscure situations, cause Netscape to render a page a bit slowly
(though never by a minute, which I believe was an exaggeration by one individual). However, Microsoft did not to do
this to thwart Netscape. All they did was change the response.buffer default property to "TRUE" (it was "FALSE" in
IIS 4.0).
When response.buffer is set to TRUE, the entire page is processed and stored in a "buffer" -- not being sent to the
client until the entire page is processed. When response.buffer is set to FALSE, the page is "streamed" to the client
as it is processed - so you will some lines of text, then the server will churn out some more lines and they will
So, if you are having this problem, try setting the following at the top of the page (you can also uncheck "Enable
buffering" in the configuration/app options tab of any application, or the default web site, in Internet Services
Manager):
<%
Response.Buffer = False
%>
If you are running Windows 2000 you can easily fix this by bringing up the task manager, selecting the
Process tab, right-clicking the netscape.exe image and setting the priority to Low which means that the IIS
Remember that, in either case, Netscape is always going to have problems with HTML rendering, particularly with
complex tables and CSS. This is just a fact of the Internet: Netscape has a terrible rendering engine (which makes it
a GREAT test browser). I have found that, the more complex the page, the longer Netscape will take to load it... and
this is independent of web server settings, web server software, and even platform.
What do I need to know about Response.Redirect?
■ Basics of Response.Redirect
■ Is there an alternative?
Basics of Response.Redirect
When you request a page from a web server, the response you get has some headers at the top, followed by
the body of the page. When viewed in your browser the headers are never seen, but are used by the
<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0">
</HEAD>
<BODY>
<p>Hello</p>
</BODY>
</HTML>
When I request that from the web server this is the reply I get;
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Mon, 19 Mar 2001 15:07:44 GMT
Connection: close
Content-Length: 134
Content-Type: text/html
Set-Cookie: ASPSESSIONIDQQGQQJWO=OMCJFABDNCDLLBKAPNHJBKHD; path=/
Cache-control: private
<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0">
</HEAD>
<BODY>
<p>Hello</p>
</BODY>
</HTML>
The first line returns the status of the response, in this case "200 OK" which means everything is fine. The
Name: Content
So in our example the web server is identifying itself as Microsoft-IIS/5.0, it also sends its date and time,
the content type and it also instructs the browser to store a cookie. This cookie contains your session ID and
is used by IIS to remember who you are. After the headers there is a blank line then the actual HTML to be
If I request a page that does not exist then we get this back;
HTTP/1.1 404 Object Not Found
Server: Microsoft-IIS/5.0
Date: Mon, 19 Mar 2001 15:11:54 GMT
Connection: close
Content-Type: text/html
Content-Length: 3243
<head>
<style>
a:link {font:8pt/11pt verdana; color:FF0000}
a:visited {font:8pt/11pt verdana; color:#4e4e4e}
</style>
As you can see, the status is now "404 Object Not Found" and the HTML is generated for us by the web
server. So the web server uses different response status types to inform the browser the nature of the
<%
response.redirect "test2.asp"
%>
<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0">
</HEAD>
<BODY>
<p>Hello</p>
</BODY>
</HTML>
Note the code at the top of the page that does a "response.redirect". If we request this page we get the
following;
<head><title>Object moved</title></head>
<body><h1>Object Moved</h1>This object may be found <a HREF="test2.asp">here</a>.</body>
The status is "302 Object moved" and note that the actual HTML following the Response.Redirect is not sent
to the client. After all, if the page is going to be redirected why send any content? When Internet Explorer
gets this type of response it gets the file to be directed to via the Location header
Location: test2.asp
And issues another request to get test2.asp. IIS does something clever for us here as well; just in case your
browser does not understand 302 headers it generates HTML giving the user a link that they can manually
click on. If you are going through a firewall you may have actually seen this code sometimes in your
browser. However Internet Explorer doesunderstand 302 headers so shows no page, but simply requests
That is the simple mechanism where by the web server responds to your requests for pages, however IIS
gives us two delivery mechanisms; buffered or non-buffered. When buffering is off IIS sends HTML to the
client as it is generated. With buffering on, IIS holds all HTML in a buffer until the page has finished
processing, it then hands the HTML to the client in one big batch.
Try this code;
<%
for i = 1 to 10000
Response.Write i & "<br>" & vbCRLF
next
%>
</BODY>
</HTML>
Notice that we are programmatically setting the buffering mode to True, i.e. we want the page buffered.
When you navigate to this page the browser will sit and wait, and all of a sudden you'll see 10000 lines in
Then you will see the page gradually grow in size until all 10000 lines have been written. Note that IIS4 and
IIS5 have different default options for buffering. In IIS4 it is off by default, and in IIS5 it is on by default.
This means that if you omit the "Response.Buffer =" code then IIS4 will default to false, and IIS5 to true.
Let's look at how buffering and redirection can come into conflict. In the following sections I'll explicitly turn
buffering on or off so that the code works the same under IIS4 and IIS5.
<p>Hello</p>
<%
Response.Redirect "test2.asp"
%>
</BODY>
</HTML>
The response.redirect is now mid-way through the page. If we view this page we see the following in the
browser;
Hello
Header Error
/Examples/test.asp, line 12
The HTTP headers are already written to the client browser. Any HTTP header
modifications must be made before writing page content.
Why do we get this error? Remember that a normal page is sent with "200 OK" in its response headers, and
a redirect is "302 Object moved". When IIS hits the first piece of HTML output;
<HTML>
it deduces that this is a standard "200 OK" page and that the browser should be sent output as it is
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Mon, 19 Mar 2001 15:07:44 GMT
Connection: close
Content-Length: 134
Content-Type: text/html
Set-Cookie: ASPSESSIONIDQQGQQJWO=OMCJFABDNCDLLBKAPNHJBKHD; path=/
Cache-control: private
<HTML>
As more HTML is generated it is also sent to the browser to be displayed. When it hits the response.redirect
command it has a problem. In order to re-direct it needs to send a "302 Object moved" header but it can't
as it has already sent a "200 OK" header, thus the error about not being able to modify the headers.
<p>Hello</p>
</BODY>
</HTML>
And try again, this time it works. With buffering on, IIS stores all output in a buffer until the page has
completed. So by the time it gets to the redirect it will have this in its buffer;
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Mon, 19 Mar 2001 15:07:44 GMT
Connection: close
Content-Length: 134
Content-Type: text/html
Set-Cookie: ASPSESSIONIDQQGQQJWO=OMCJFABDNCDLLBKAPNHJBKHD; path=/
Cache-control: private
<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0">
</HEAD>
<BODY>
<p>Hello</p>
However it has not sent anything to the client yet. So when it hits your redirect it simply throws away
<head><title>Object moved</title></head>
<body><h1>Object Moved</h1>This object may be found <a HREF="test2.asp">here</a>.</body>
Remember that if you are using IIS4 you have to turn on buffering programmatically, if you use IIS5 it is
done by default. However if you rely on buffering being turned on you should always explicitly add it to your
code. That way you won't get problems when you move to a different web server or when Microsoft decide
to change the default again. You can also configure buffering to be on or off by default via the IIS MMC.
...redirect to a frame?
HTTP is a request/response technology. The browser requests a page from the server, and the browser
displays the response. For a page to appear in a frame, it is that frame that must make the request. You
can't make a request in one frame and have the response piped to another. Only the requester can get the
response.
To work around this you should create HTML that does the frame manipulation on the client;
<html>
<body>
<script>
parent.targetframename.location.replace('page2.asp');
</script>
</body>
</html>
This will cause "targetframename" to request page2.asp, thus showing the response in that frame.
When you submit a FORM with the GET method, your browser does not do anything clever, it simply
appends the FORM elements to the end of the page in the ACTION parameter. When you submit a FORM
using the POST method an entirely different mechanism is used. When your browser gets the 302 response,
it reads the new location from the Location header and simply requests that page. You can simulate a GET
submission by simply tagging on the parameters yourself, thereby emulating what the browser does. So if
you do;
<head><title>Object moved</title></head>
<body><h1>Object Moved</h1>This object may be found <a
HREF="test2.asp?myparam=123">here</a>.</body>
So the browser requests "test2.asp?myparam=123", thus passing on your parameters. Emulating a POST,
however, is not that simple and the Redirect process simply does not support it.
The workaround is similar though, you should generate the appropriate HTML to do the job yourself;
<html>
<body>
<script>
document.frmTest.submit();
</script>
</body>
</html>
The only problem with this is that you are relying on scripting support from the client.
Is there an alternative?
Yes, IIS5 gives us an alternative to doing a response.redirect. You can now use the Transfer or Execute
method of the Server object. So instead of doing response.redirect "page2.asp" you can simply
server.transfer "page2.asp". It is all done at the server so will work even if you have already sent some
Another option is to emulate a redirect using a COM object that lets us request another ASP page within our
You might find that your machine becomes corrupt occasionally. IIS is still running, most functions work (HTML
pages and images etc. still serve correctly), but ASP pages no longer run properly. These scripts seem to timeout
endlessly, never returning an error message or timeout warning (unless generated by the browser).
What may have happened is that your Windows Access Method (IWAM_<machine>) account has gone out of sync,
or your application has changed such that IWAM can no longer function correctly. The easiest solution, according to
Stefan B. Schachner, is to re-sync the IWAM account. You can do this in a couple of quick steps at a command
prompt:
For more information on the IWAM account, and more verbose steps at producing the same results as above, see
After the above failed, one thing that has worked for some people is to change the Application Protection level of the
Default Web Site to Low. This is found by right-clicking the application, choosing Properties, and moving to the
You may also want to check out Article #2180 if your pages use FileSystemObject and you have Norton Anti-Virus
installed.
Finally, if you only have certain ASP pages that are hanging the system, you'll want to check them for infinite loops.
<%
do while not rs.eof
' stuff
loop
%>
Since there is no rs.movenext right after the 'stuff line, the ASP code will just keep processing the same record over
Not with ASP alone. You can use client-side code, to some extent. Here is an example for Nav3+ and IE 3.02+ that
<%
' This ASP section pre-determines which
' script to send ... VBScript or JavaScript
a = lcase(request.servervariables("http_user_agent"))
if instr(a,"msie")>0 then
if instr(a,"98")>0 or instr(a,"95")>0 or instr(a,"nt")>0 then
ie32="true"
' IE 3 or greater on 32-bit
end if
elseif instr(a,"mozilla/3")>0 or instr(a,"mozilla/4")>0 then
if instr(a,"opera")<=0 then
nn="true"
' NN3 or greater
end if
end if
if ie32 then
%>
<script language="vbscript">
if scriptEngineMajorVersion > 1 then
on error resume next
FIn=(IsObject(CreateObject("ShockwaveFlash.ShockwaveFlash")))
if FIn then
msgbox "Flash Installed!"
else
msgbox "Flash not installed."
end if
end if
</script>
<%
elseif nn then
%>
<script language="JavaScript">
FIn = navigator.plugins["Shockwave Flash 2.0"];
if (FIn)
{
alert("Flash Installed!");
}
else
{
alert("Flash not installed");
}
</script>
<%
end if
%>
Of course instead of alert and msgbox statements, you would document.write <object> and <embed> tags or
If this seems like too much work, you may consider looking at BrowserHawk which allows you to detect all kinds of
There is a free component that will help you do this; it is called WaitFor 1.0 and is available at ServerObjects Inc.
If you are using SQL Server (or have access to one), you can try out the followign script (adapted from a post by
Pierre W):
<%
Set conn = Server.CreateObject("ADODB.Connection")
' note that you don't need to indicate a database:
conn.Open "<connection string>"
' if you neede more than 59 seconds, you will need to adjust the SQL:
conn.Execute "WAITFOR DELAY '00:00:" & sleep & "'"
Visual InterDev's debugging features are useful for some people (I am not one of those people). But many people
have problems getting them working, and keeping them working. This can be due to misconfigured systems from the
get-go, or from installing software afterward that causes conflicts or errors that may not be visible.
One common misconception is that if you can install VI, you can run the *server* debugging tools. AFAIK, VI's
debugging tools only run on Windows NT and Windows 2000... they will not run on Windows 9x clients (though client-
only debugging can function). Also, they work best if they are running ON the server that is hosting the ASP files.
Remote debugging works -- allegedly -- but reports I've heard grade it mediocre at best.
If you've already installed InterDev's server extensions, search for a file called VIDDBG.exe. From a command
This will run diagnostics on the debugging tools, and may help you determine why they're not working. In any case, if
you're going to be fixing them, you'll likely need to (re-)install the server extensions.
If you're running NT 4.0, you'll want to reinstall SP4 or greater before starting. Next, find the file SETUP.EXE in the
\VID_SS\ folder on the CD-Rom (this will be on Disc 1 of Visual InterDev, or Disc 2 of Visual Studio Pro or
Enterprise). Double-click this file to launch the installation. Finally, reinstall the latest service pack for Visual Studio
For more information on Visual InterDev debugging, see the following articles:
If you are getting errors (e.g. 'Unable to contact web server') or are having trouble making debugging work, see the
following KB articles:
Q220166 PRB: Troubleshooting "Unable to Contact Web Server" in Visual InterDev
Q195954 PRB: "Unable to Contact Web Server" Error Creating New Project
Q191560 FIX: Contacting Web Server to Open Web Project Never Times Out
Why does global.asa not fire?
Well, that depends on which part of global.asa is failing. If it is only session_onEnd(), then see Article #2078 for an
If none of the methods in global.asa are firing, then there are two common scenarios:
(b) the global.asa file resides in a folder that is NOT marked as an application.
If (b) is not the problem, I suggest extracting the subs by themselves and running them in an independent ASP
The only way this could be a concern for you is if you're still running NT 4.0 with Service Pack 3. <shudder>
Eliminate the problem by installing Service Pack 6a. More info here:
Q232449 Sample ASP Code May be Used to View Unsecured Server Files
How do I access my server's registry from an ASP page?
Here are some components available to do read from and/or write to the registry:
ADEX Registry
ASP MagicBundle
DES RegEdit
RegEdit Library
Stonebroom.RegEx
Should I use VBScript or JScript for ASP?
This question is asked fairly regularly. Really, it comes down to preference. If you come from a VB or VBA
background, VBScript would probably be the logical choice. If you're already familiar with JScript on the client side,
or have a strong background in C, C++ or Java, then it's the language you should use. However, if you anticipate
asking or looking for help with code issues, that might tip the scales back to VBScript. This is because most samples
you'll find online, and most people hanging around the newsgroups to help you, deal with VBScript. In any case,
VBScript
JScript
■ array has built-in sort method, which has proven useful to me at least
For those planning large, high-traffic sites, you might want to take into account that each language is proficient at
■ Peter Hanus discovered that VBScript is faster in almost all operations except pattern-matching and bit-
shifting (these results are published in an article on ASPToday.com, which charges for access).
■ In another article, greyMagic software goes to great lengths to show that, in their experiments, JScript is
faster.
Personally, I prefer VBScript. I like its more English-like syntax, its error handling, and collection enumeration. I
don't recall ever using both languages in the same page, though I do have a few pages in a recent application that
are written completely in JScript (one such page puts a list of files from a folder into an array, sorts them into
alphabetical order, and returns them to an application). To be completely sold on VBScript, you may just have to
http://www.kamath.com/columns/my3cents/mtc002_scripting.asp
Why won't my session variables stick?
Session variables can stop working (or never start), or your sessionID can change from page to page, for a variety
1. Make sure cookies are enabled in your browser - session variables require cookies.
2. If you are using IE 6.0, the new privacy policy settings might be biting you in the rear. You will need to read
up on P3P, discover how IE is affected in Q293222 and this article, and create your own policy file(s).
3. If you are using IE 5.5 or IE 6.0, and your local server name has an underscore or other non-alphanumeric
character (other than a dash) in the name, then cookies will not work correctly. One workaround is to access
the machine by IP address; others include renaming the server or at least adding an entry to the clients'
hosts files, or alter your local DNS/WINS, or add a new host header to the web server, with a more friendly
4. Make sure you haven't disabled session state in Internet Services Manager, and that it has an appropriate
timeout value:
■ Open the Internet Services Manager MMC tool, and expand 'Web Sites'
■ Right-click 'Default Web Site' or the application in question, and select 'Properties'
■ On the 'Options' tab, make sure 'Enable session state' is checked and that the timeout value (in
minutes) is sufficient.
■ IIS applications
■ Protocols (e.g. http:// <-> https://, even on the same server - use a database to maintain state, as
■ Domain names, again, even on the same server - cookies are sensitive to domain name changes
6. If you are relying on session or application variables created in global.asa, make sure that global.asa is in
7. If you are running McAfee, it may be scanning global.asa constantly, and preventing your application from
8. Finally, check to see that you aren't manually disabling the user's session, either by a coding/logic/database
error, a misplaced <%@ ENABLESESSIONSTATE = False %> (which will affect a specific page), or an errant
To work properly, session variables require that cookies be enabled on the client's browser. Many people have
disabled cookies, usually after reading a Jesse Berst article where he evangelizes how cookies are the devil and
judgment day is coming soon for all those who use them.
How many people out there are seriously afraid of cookies so much that they'll disable them entirely, yet
trust your site enough to give you their credit card number online?
The discussion always goes further than that, of course. Sometimes the boss -- who always knows best -- just wants
it that way. Sometimes the programmer doesn't understand the difference between asking someone to store
temporary information on their machine and handing over their credit card information. Sometimes shopping carts
are created and then, at checkout time, there is an option for the user to fax or phone in their credit card details to
In any case, there are times where session variables would come in handy even when cookies are disabled. Many
You have a database table strictly for generating numbers to replace the usual session ID. Once a user has one of
Anything they do, and any items they add to their cart, are inserted into a separate table with the session ID
There are other ways to get around missing cookies, of course. This, IMHO, is the simplest to implement, and would
require minimal changes to your existing architecture. You would use the database structure for retrieving session
information and do away with the session variables altogether (unless you don't already get enough of the 'code two
web sites for the price of one' dilemma with Netscape and IE).
There is a sample application now available online at http://www.aspfaq.com/cart/ - you can play with the cart and
Not reliably.
Depending on the method of the user's connection, he or she may be sharing a single IP address with dozens of
other users (as in the case of a corporate office accessing through a single server) or with thousands of other users
You could uniquely identify users by storing their information in a database on your side, and store a cookie on their
machine that simply stores the primary key of the table so you can look up their data easily. You could also store all
information on their machine in a cookie. Both of these solutions, of course, require that cookies are enabled (which
A method I've used for maintaining state without cookies / session variables is to pass primary key information from
page to page in hidden forms. For a simple example of doing this, check out our cookieless shopping cart article. You
could easily extend this to add a username and password so people could look up their data on a successive visit.
How do I embed apostrophes (') and quotes (") in a string?
Even though they're not necessary in some cases, you have several options for returning HTML with proper quotes
In server-side JScript/JavaScript:
There are many components that will allow you to do this. Remember that these components are designed to allow
ASP to zip and unzip files that are on the SERVER. If you are interested in zipping / unzipping files that the client
has, these components will be no good to you unless you first upload the files to the server!
RemoteZip/ServerZip
Xceed Zip
SA-Archive
DynaZIP
WaspZip
aspEasyZIP
Zip/Unzip
I've used the last component in the list, Zbitz' Zip/Unzip. It is simple yet robust and very reliable. After much
Using PKZip Command Line with Windows Script Host - sample code!
Assuming, of course, that you have Windows Script Host installed, and that you have the ability to install programs
and alter configuration on your server, you can call PKZip Command Line from Windows Script Host (which, in turn,
you can run from ASP). Here are some instructions to get you started:
e. Under 'User variables for <USER>', you will find the PATH variable
i. Add a semi-colon to the end of the path, and paste the PKZip folder without the quotes
Now, provided IUSR_<machine> has appropriate permissions to the destination folder, you can run a script like
this:
<%
set shell = server.createobject("WScript.Shell")
zipCommand = "pkzipc -add " & server.mappath("/") & "\test.zip c:\*.log"
shell.Run zipCommand, 1, true
set shell = nothing
%>
Why can't I pass querystring information AND links to #bookmarks?
ASP has issues with #bookmarks. These #bookmark references (and, depending on the syntax, all querystring
variables) are usually ignored when passed in combination with querystring information, e.g.:
<a href="default.asp#bookmark?id=1">GO</a>
OR
<a href="default.asp?id=1#bookmark">GO</a>
A workaround is to pass the bookmark as a querystring value, retrieve it and move to the bookmark using client-
<a href="default.asp?id=1&bk=bookmark">GO</a>
Then in default.asp:
<html>
<body>
<a name="bookmark">Bookmark</a>
</body>
<%
bk = request.querystring("bk")
if bk <> "" then
%>
<script>
self.location.hash="#<%=bk%>";
</script>
<%
end if
%>
</html>
I tend to put the script at the end of the page, rather than at the beginning, since Netscape has a nasty habit of
#EXEC directives are processed by ssinc.dll, not asp.dll... therefore, this directive is only supported in .shtml files (or
files with any other extension that is mapped to ssinc.dll through IIS). Like ASP files, .shtml files can only be
This error usually indicates that you have used ADOVBS.inc in two different #INCLUDE directives (possibly nested
within other includes). Another possible cause is that you have defined an identical constant in a page that also
#INCLUDEs ADOVBS.inc. You can demonstrate this with the following example:
<%
Const adOpenForwardOnly = 0
%>
<!--#INCLUDE file='ADOVBS.inc'-->
My suggestion for avoiding this, is not using ADOVBS.inc at all... it incurs a lot of overhead for a few constants. Find
out which constants you "need", copy them into your script (or your own reduced-size include), and leave
Another possibility is that you tried to defined a constant, giving it a name of a pre-defined object, such as the
<%
Const Response = "Response message."
%>
Can I create an array's size dynamically?
VBScript's arrays have quite a few limitations. You may have noticed if you try this:
<%
x = 15
Dim demoArray(x)
%>
To work around this, you need to declare the array without a size (or with a constant size, e.g. 0), and then re-
dimension it from the variable. Here are two examples; one using a simple array, the other a multi-dimensional
array:
<%
x = 15
Dim demoArray()
ReDim demoArray(x)
%>
<%
x = 15
y = 10
Dim demoArray()
ReDim demoArray(x,y)
%>
Note that if you want to increase the size of an array within a loop, and want to preserve the existing values
assigned to the array, you need to use the Preserve keyword (otherwise, the array gets erased and re-created, thus
<%
Dim demoArray()
for i = 1 to 5
ReDim Preserve demoArray(i)
DemoArray(i) = "test" & i
next
%>
How do I time my ASP code?
Many people have asked how they can time their ASP script, to see how efficient they are (even down to the
millisecond). Here are two techniques to do this (one has millisecond accuracy, and one can be used to derive it
indirectly).
The first method uses the timer() object, introduced in VBScript version 5.0. This returns the number of seconds
that have passed since midnight -- sounds useless, but the value contains a sparsely documented gem:
milliseconds! Yes, you heard right. So you can use script like the following to measure the number of milliseconds
that pass between the start and end of some arbitrary block of code.
<%
' get timer before task begins:
starttime = Timer()
Make sure you put some substantial code in there to test that this really works (most simple tasks don't take long
enough to measure a difference; it took 350000 iterative addition calls to register 1 second of process time on my
machine). Change the number in the loop above and you will see the difference.
The second method measures the number of times a certain task can be executed within a given number of
seconds.
<%
' set number of seconds:
numSeconds = 5
' set endtime to some time more than numSeconds in the future:
endtime = dateadd("n", numseconds + 1, starttime)
' loop until numSeconds have passed, resetting endtime each loop:
Do Until DateDiff("s", starttime, endtime) = numSeconds
endtime = Now()
counter = counter + 1
Loop
[Side note A: Don't forget to weigh in datetime and other calculations within your loops, they add CPU time to the
process as well. To see the difference, add the 'sometime = Now()' within the first script above, and note the
performance difference. In my tests, a simple call to Now() more than doubled the time required to finish the loop.]
[Side note B: Speed of a script is a very small percentage of the battle -- you need to test your scripts inder heavy
load and make sure the server can handle it. Because if the server goes down, Joe User isn't going to be waiting an
extra millisecond or two to get his results; he's going to be waiting until your pager goes off at 4 a.m. and you fix
the problem!]
Why can't I browse localhost without a connection?
If you use a dial-up connection, IE defaults to "modem." Unfortunately, when it does this, it can't see the web server
In Internet Options, on the Connections tab, change "modem" to "LAN" - the only side effect is that you can no
strStatus = "offline"
if InStr(strPResult,"TTL=")>0 then strStatus = "online"
If you are running Windows XP or Windows.NET Server, you can use WMI's new Win32_PingStatus namespace,
e.g.:
<%
url = "www.espn.com"
WMI = "winmgmts:{impersonationLevel=impersonate}"
If for some reason you can't use the shell or WMI, there are many components that can handle the task.
ASPPing
C2GPing
DesPing
DSPing Pro
DynuDNS
kutil
NETDLL
w3 Utils
How do I make my ASP page refresh?
You can use the Response.AddHeader method to force a timed reload or delayed refresh / redirection in an ASP
page. The following code will cycle the current page every ten seconds (and is functionally equivalent to adding a
<%
Response.AddHeader "Refresh", "10"
%>
<%
Response.AddHeader "Refresh", "35;URL=http://www.aspfaq.com/"
%>
Note that this also prevents referer information from being passed (see Article #2169 for other examples).
How do I use extensions other than .ASP for ASP files?
■ In the extension box, type the extension you want to map to asp.dll
■ Close Internet Services Manager (if it asks you to save console settings, say yes)
■ Make an ASP file with your new extension, and navigate to it via http://servername/filename.newExtension
How do I stress test my ASP application?
The following is a list of tools and services you can evaluate. Most of the software products have evaluation versions,
and most have shortcomings. One or another may be right for you depending on your needs - some are designed to
simulate multiple users, heavy traffic from few users, and even dialup speeds.
TOOLS
DUES
Empirix e-Load
Evolvable WebTest
Innovate-IT PureLoad
Ladybug(.NET)
OpenSTA
ParaSoft WebKing
Radview WebLoad
Rational SiteLoad
SR TestWorks
Technovations' WebSizr
WebART
WebPerformance Trainer
SERVICES
WebSitePulse
Other resources
[Keep in mind that the following components are used for file GET/PUT operations between the SERVER and an FTP
server. These components do not facilitate FTP operations to/from the client's system.]
ASPInet (ServerObjects.com)
DynuFTP
etiveFTP
PowerTCP FTP
Mabry's FTP/x
"Integer range" is betweeen -32768 and 32767. If you expect that the number you are converting to an integer may
be outside of that range, use cLng() instead (to convert it to a Long datatype, which has a range between -
2,147,483,648 and 2,147,483,647). If your number is outside of THAT range, it probably isn't stored as a number in
the database, since the Long datatype has the same range as SQL Server 7's "INT" datatype. :-)
How do I send a MsgBox or InputBox from ASP?
Come on, we've all done it. Tried to send a MsgBox from ASP. And received this lovely message:
MsgBox and InputBox are client-side interaction element. To work in an ASP environment, someone would have to
be sitting at the web server terminal day and night, clicking OK on every alert that comes up. Yes, I know, that's
exactly the scenario you'd like to have when debugging (and are used to with traditional client-server or stand-alone
applications). However, ASP simply doesn't allow it. Here are a few code samples that show how to use a client-side
alert (recommended for browser reach) or MsgBox. These bring up a couple of other answers too, such as how you
can nest script tags and embed single- and double-quotes in a string. Plenty of languages for everyone!
<%
msg = "Umm, I guess, EOF?"
Response.Write("<" & "script>alert('" & msg & "');")
Response.Write("<" & "/script>")
%>
Server-Side JavaScript -> Client-Side VBS MsgBox
<%
msg = "Umm, I guess, EOF?"
Response.Write("<" & "script language=VBScript>")
Response.Write("MsgBox """ & msg & """<" & "/script>")
%>
Where can I find out about .NET?
aspfaq.com is not a source for learning about Visual Studio.NET and ASP.NET, at least not yet. There are, however,
several useful places for you to get more information about .NET. The most comprehensive are Microsoft's .Net
Home Page, ASP.NET Home Page, and a newly-implemented .NET Article Index ... other information is available
http://www.dotnetwire.com/
http://www.devx.com/free/press/2000/vsresources.asp
http://www.asp.net/
http://www.aspnextgen.com/
http://www.aspng.com/aspng/index.aspx
And of course there are some great newsgroups covering ASP.NET on Microsoft's public news server:
microsoft.public.dotnet.framework.aspnet
microsoft.public.dotnet.framework.aspnet.webservices
microsoft.public.dotnet.languages.csharp
microsoft.public.dotnet.languages.jscript
microsoft.public.dotnet.languages.vb
microsoft.public.dotnet.languages.vc
microsoft.public.dotnet.samples
microsoft.public.dotnet.scripting
microsoft.public.dotnet.academic
How do I refresh global.asa without restarting the application?
Plenty of people have asked how they can manually force global.asa to fire, without stopping and starting the web
server or application. They already know they can't hit global.asa directly with a browser; this ends up in a 500-15
There is a way around this. This example will deal with forcing the application_onStart() routines to fire, without
The trick is to keep the logic in an #include file, and store that as an ASP page you can hit manually. Yes, many
people aren't aware that you can #include a file in global.asa, but you certainly can. Here is some syntax within
global.asa:
The trick is that both global.asa and the #include file must have matching script types... no <%%> delimiters. The
ASP page should also include straight script (no subs or functions) and not use any response-related methods. So
Now this code will fire in the application_onStart() event, but you can also override it by hitting
http://yourserver/admin/start-app.asp as well.
This is very handy; however, I first employed this technique back during one of the first global.asa-related security
exploits.
How do I protect my client-side JavaScript code?
This has been asked thousands of times over the past few years. Everyone wants to know how they can prevent
nosy people from viewing or stealing their JavaScript. I have always responded with:
"If you don't want people to steal your JavaScript, don't put it on the web."
"If your Javascript is so revolutionary, you should probably be able to figure this out too."
In the past, many people have suggested ways to slow people down, and discourage casual, non-persistent people.
But there has never been a way to stop anyone with a little more resourcefulness than your average house fly. <G>
Some of the solutions that have been offered in the past, and their workarounds:
Load the JS in a new window, with toolbar disabled - view source by right-clicking within the window
- load 'view-source:<url>'
Use JavaScript across frames, iframes, ilayers - not difficult to find the proper page
'Hide' the script using a remote JS file - the .js file is in your cache
Other legitimate ways to view JavaScript sent to the browser is by using packet sniffing technology,
Put the source on a floppy disk and bury it in a mason jar in your back yard. Make sure you delete
all references to it from your system. To be extra sure, bury it blindfolded at night so you can't
remember where it is in case foreign spies try to beat the location out of you.
Of course, you could write it as a Java Applet to make it tougher to figure out, but then, if you knew
how to do that you'd probably also be able to write something really worth protecting...
Jeff
PS: The mason jar trick works well to protect your .GIF files from being downloaded too...
After many attempts at hiding my JavaScript code, I was thwarted every single time - by people using tactics
Once again, I have to tell you... if you need to hide your JavaScript code, you probably shouldn't put it on the web.
How do I access all active sessions on the server?
You can't access one user's session from another session (even if you are physically at the machine). However, you
can follow our cart example to some extent, and store all the session data in a database. Keep a column called
'active' which indicates whether the user is still there. When the user logs out (or after a specified time period, which
you can schedule using a job in SQL Server), you turn the active flag to off. Passing a single integer session variable
around, and querying the database when you need to, is much more efficient and scalable than storing all those
values in session variables anyway (even if you need all the values on every single page!). I just finished re-working
a very major application to handle things this way (partly for performance, and partly because the session variables
Remember, don't use sessionID as a key, and expect to uniquely track users (see Article #2085 for more info).
What is this error 'An unhandled data type was encountered'?
This usually happens when you mix up names of string/numeric variables and array variables. For example:
<%
f = "hello"
'...
f = split("foo","bar")
'...
response.write(f)
%>
To get rid of this error, check the line it occurs on for any variables used, then scan through the file for any other
use of that same variable name. If the error returns line 0, you will have to do a bit more digging through your file --
you can narrow your search down to look at the names you give complex data types (e.g. objects or, more
commonly, arrays).
Should I use sessionID to uniquely identify users (e.g. primary key)?
NO. SessionID is a "random" string and, as noted in the documentation for all versions of IIS, can be repeated. So if
you store information for a user based on the SessionID value, be very aware that a new person next week might
happen to get the same SessionID value -- this will either violate a primary key constraint, or mix two or more
people's data.
Why won't QueryString values work with Server.Execute?
IIS 5.0's Server.Execute command is fairly useful. However, as many people have pointed out, if you use a
Invalid URL form or fully-qualified absolute URL was used. Use relative URLs.
Even when your URL *IS* relative (removing the QueryString value makes the page function properly).
Unfortunately, Microsoft has yet to recognize this officially as a bug. So in the meantime, you must rely on session
variables or database entries to retrieve any information you would like to pass to the target page.
Can I run IIS 5.0 / ASP 3.0 on Windows NT 4.0 or Windows 9x?
No. See Article #2075 for information on obtaining web server products for these operating systems.
Why does DLLHOST.EXE take all my memory and/or CPU?
COM Objects
Since DLLHOST.EXE keeps references to your COM objects, it may be that these objects have memory leaks. Pore through your
custom objects and make sure you release all references. If you are using C++/ATL, make sure to use "." as opposed to "->" when
destroying pointers. If you are using Visual Basic, make sure that you have read Q264957, Q281630 and that you have set Retain
in Memory and Unattended Execution. If you are using a third party vendor's object, check their web site to see if they have
If you are uploading large files with ASPUpload, for example, see PS01041741 for suggested fixes.
Sometimes this can be caused by MDAC components - either by poor implementation, a faulty install, or a true memory leak in the
component. Eliminate the possibilities by getting the most recent version from http://www.microsoft.com/data/.
ASPTrackThreadingModel
One reason could be that your Metabase has a setting of 0 for ASPTrackThreadingModel. For optimal performance, in IIS 4.0, this
setting should be 1. If you are running IIS 5.0, you shouldn't adjust this setting.
To verify / solve this issue, download the Metabase editor (MetaEdit 2.2) from Q232968.
Once it is installed:
- Expand 'Schema'
- Expand 'Properties'
Perhaps you simply expect one server's resources to handle too many web sites / applications.
Bonehead Mistakes
Yes, we've all made these. Before you go opening a ticket with Microsoft, make sure your code doesn't look like this:
<%
Dim i
i = 1
Do While i < 10
Response.Write "I'm going to steal your memory."
Loop
%>
Other examples are very large loops (e.g. for i = 1 to 10000000000), forgetting to close / set objects to nothing, circular
references, forgetting rs.movenext within a do while not rs.eof / loop construct, recursive functions, returning massive amounts of
data from SQL Server or Access to an ASP page, storing ADO objects in the session or application objects...
Still haven't solved it?
Well, you could use performance monitor to at least narrow down the application(s) causing the problem. Under Internet Services
Manager, try changing the application protection under Internet Services Manager to High (isolated). Right-click the Default Web
Site (or the troublesome application), choose Properties, and on the Home Directory tab, change the application protection option.
Hit Apply, OK, exit ISM and try and reproduce the problem. This may take some experimentation.
If you have several COM objects and you can't narrow down which is causing the problem, put each in its own application, and wail
on each one (in a realistic environment) until you reproduce the problem.
There is a free component that will help you do this; it is called GUIDMaker and is available at ServerObjects Inc.
If you have access to SQL Server from your ASP page, you can call the following script:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connection string>"
set rs = conn.execute("SELECT newid()")
Response.Write rs(0)
' ...
%>
<%
Function GetGuid()
Set TypeLib = Server.CreateObject("Scriptlet.TypeLib")
GetGuid = TypeLib.Guid
Set TypeLib = Nothing
End Function
Response.Write GetGuid()
%>
What is this 'Cannot detect OS type' error with NT 4.0 Option Pack?
When installing IIS 4.0 on a computer running Windows NT Server, the NetLogon and Computer Browser services
must be running. If these services are not running, you will receive a dialog that says "Cannot detect OS type" and
Several people have reported error number 8007053D recently. There is a KB article that goes into depth about it
Q238665
There is another that applies to similar W3SVC errors that might appear in your event log.
Q200514
Can I host multiple sites in Workstation or Professional?
No. These operating systems have some limitations compared to their server counterparts. One is the way that PWS
(NT 4.0) and IIS (Windows 2000, Windows XP Pro) are configured... two of the biggest obstacles people find with
the lower-grade OS is that client connections are limited to 10, and that you can only host one web site. You also
cannot configure the HTTP service's port from its default of 80; this means anyone on a cable network (e.g. @home,
RoadRunner) that has shut off port 80, is going to have to bite the bullet and upgrade (or switch providers).
How do I get the lbound/ubound of the nth element in a multi-dimensional array?
This one is a tricky one, and displays another weakness in VBScript's array object. While most things in VBScript are
0-based, the "collection" of elements in a multi-dimensional array is actually 1-based. The default element for
lbound/ubound values (these give the lowest and highest numbers in the array) is the first element. So, when you
have a multi-dimensional array, the following commands do the exact same thing:
<%
dim demoArray(10,20)
Response.Write ubound(demoArray)
Response.Write ubound(demoArray,1)
%>
The ,1 indicates that you want the ubound for the FIRST element in the array. So, extending that, here is how to get
<%
dim demoArray(10,20,30)
Response.Write ubound(demoArray,2)
Response.Write ubound(demoArray,3)
%>
Is there an easier way to patch my server(s)?
For information on obtaining the lastest service packs for Windows 2000, see Q260910 (Windows 2000).
For information on obtaining service packs for Windows XP, see Q322389. Everyone running Windows XP should
not be without Service Pack 1 (see a list of fixes here - this page is linked to from Q324720 and release notes
are listed in Q324722). You can also see a subset of the fixes, namely the security-related updates, at this
Technet page.
For information on obtaining the latest service packs for SQL Server, see Q290211 (SQL Server 2000) and
http://msdn.microsoft.com/netframework/downloads/updates/sp/default.asp.
■ Due to a flaw in SMB Signing, there is an update available in Security bulletin MS02-070 and Q309376.
■ And likely most important in this bunch, there is a critical update to Microsoft's Virtual Machine - if you are
running the VM, please read Security bulletin MS02-069 and Q810030. You can obtain this patch through
Windows Update.
December 4, 2002
■ There is a patch available for Internet Explorer. For more information, see Security bulletin MS02-068 and
Q324929.
■ There is a patch available for Outlook XP / 2002. For more information, see Security bulletin MS02-067 and
Q331866.
■ Microsoft released an MDAC patch for a potential buffer overrun exploit. If you haven't upgraded to MDAC
2.7 yet (http://www.microsoft.com/data/), please do so, after applying this patch. For more information, see
■ There is a cumulative patch available for Internet Explorer 6.0. See Security bulletin MS02-066 and
■ Cumulative patches for IIS 4.0, 5.0 and 5.1 has been released. This patch is recommended for all systems...
■ MS released a fix for a potential unchecked buffer exploit on Windows 2000 and Windows XP systems. See
■ They also released a fix for a potential Windows 2000 trojan horse exploit; see Security bulletin MS02-064
and Q327522.
■ A cumulative patch for Outlook Express hits the web. The most notable fix here is that OE finally has a
delete button that is usable within newsgroups (previously we had to use bizarre and tedious workarounds to
prevent, for example, a post-dater's message from appearing at the top of our message list for weeks). See
October 16, 2002 - SQL Server users should install the cumulative patch from Security bulletin MS02-061 and note
that the related KB articles (Q316333 for SQL Server 2000, and Q327068 for SQL Server 7.0) have been updated
yet again.
October 10, 2002 - If you are running Windows XP or have Outlook Express 5.5 or 6.0, see Security bulletin MS02-
October 2, 2002 - All Windows users should review Security bulletin MS02-055 and Q323255.
October 2, 2002 - Those running Windows 98, ME or XP should review Security bulletin MS02-054 and Q329048.
September 25, 2002 - If you are running FrontPage Server Extensions, review Security bulletin MS02-053 and
Q324096.
September 18, 2002 - Please update your system(s) with the JVM patch from Security bulletin MS02-052 (and see
Q329077 for more details. If you are running Terminal Services or Remote Desktop, please see Security bulletin
September 13, 2002 - Anyone running Microsoft Word and concerned about recent security problems should read
this reponse.
September 9, 2002 - For those of you running Windows XP, you should get SP1 as soon as possible. If you can't,
(see Q328940 for more information) -- however this won't completely solve the problem. For more information, see
this explanation.
September 5, 2002 - Microsoft updated Security bulletin MS02-050 and Q328145 / Q328691, outlining a critical
September 4, 2002 - Security bulletin MS02-049 and Q326568 describe a potential exploit for Visual FoxPro users.
August 28, 2002 - Microsoft released a patch for a new exploit involving digital certificates. See Security bulleyin
August 22, 2002 - Microsoft released a patch for a new denial of service exploit, in Security bulletin MS02-045 /
Q326830 and a new version of the TSAC ActiveX control in Security bulletin MS02-046 / Q327521. In addition, they
released a cumulative patch for Internet Explorer, which should be applied to, at minimum, development
workstations. This patch is made available in Q323759 and described in Security bulletin MS02-047.
August 21, 2002 - If you are running Project Server, Commerce Server, BackOffice Server, ISA Server, or BizTalk
Server, and/or are using Office Web Components, you should see Security bulletin MS02-044, which fixes a few
potential security issues (as explained in Q322382 and Q328130).
August 7, 2002 - Microsoft released a patch for Content Management Server 2001 in Security bulleting MS02-041
(for Q326075).
August 1, 2002 - Microsoft released Windows 2000 Service Pack 3. This release fixes many security issues and is a
July 31, 2002 - Microsoft released a patch for MDAC 2.5 through 2.7 in Security bulletin MS02-040 (for Q326573).
July 24, 2002 - If your server is running SQL Server 2000, you'll want to install the Q316333 Cumulative Patch and
June 26, 2002 - Microsoft released Security bulletin MS02-033, which is applicable to those of you running
June 12, 2002 - Microsoft released Security bulletin MS02-028, which deals with HTR requests (Q321599).
June 6, 2002 - Microsoft released Security bulletin MS02-026, which involves the ASP.NET worker process
(Q322289).
April 29, 2002 - Microsoft updated Q294370 with a few new fixes for specific invalid requests.
April 10, 2002 - Microsoft released Secutiry bulletin MS02-018, outlining a cumulative patch applicable to NT 4.0 /
IIS 4.0, Windows 2000 / IIS 5.0, and Windows XP / IIS 5.1 - details are available in article Q319733.
And you can get version 2.5 of the URLScan utility (updated April 12, 2002):
Q307608
Previous patches
On January 30th, Microsoft made a cumulative patch for Windows 2000 / IIS 5.0, encompassing all post-SP2
security issues. You can download the patch, and read more about it, at the following page:
If you are running NT 4.0, then you can download the May 14th cumulative path which fixes all security issues from
NT 4.0 Service Pack 5 onward. This patch should be much easier to apply than combing through the many individual
Here is where you can download the patch and get more information:
And on November 14, 2001, Microsoft issued version 2.1 of the IIS Lockdown Tool:
You should also watch the following URLs for updates, some of which may affect your environment:
Further, you can download the Security Hotfix Checker, allowing you to determine which machine(s) on your
network are missing security patches. The download is found in Q303215 and Q & A is found in Q305385.
What kind of object is Response.Crackers?
I'm sorry if you're out there Mike, but I had to exploit this one a bit and have some fun with it (at your expense). On
November 12th, Mike posted the following after tirelessly searching for information about "crackers":
I got 5 hits, the second was #2058 "How do I check for enabled cookies..."
if request.Crackers("enabled")="1" then
response.write("cookies are enabled")
else
response.write("cookies are not enabled")
end if
As it turns out, Mike is running cookie-blocking software called Proxomitron -- if you have this software installed,
and you are viewing code samples on the web, you might be surprised to see references to objects like
'Request.Crackers' and 'Response.Crackers'. Fact is, this software is simply intercepting the HTML that comes into
your browser, and replacing all instances of '.cookies' with '.crackers' ...
So, if you go to Oreo.com, you might be a little confused when you see the following recipe:
Of course, this won't happen, but I wanted to embellish the story a little bit. Some people were a little mean, and
called Mike "crackers" in response. A little confused, and understandably so, but not crackers. In any case, it was
quite funny -- and I hope nobody is disgruntled about me sharing it here. After all, it could have happened to you.
How do I embed ASP delimiters (<% or %>) in a string?
Many people get choked up when trying code similar to the following:
<%
Response.Write "<table width=100%>"
%>
This is because the ASP engine tries to parse all the <%%> blocks first, and ...width=100%> looks like the end of
an ASP block (which of course isn't the case). There are several ways to work around this; for example, you can use
valid HTML by enclosing the width attribute's value in quotes, you can leave a space after the percent sign, you can
organize your element tag in a different order, or you can escape the sequence using a backslash.
To wit:
<%
response.write "<table border=0 width='100%'>"
response.write "<table border=0 width=""100%"">"
response.write "<table border=0 width=" & chr(34) & "100%" & chr(34) & ">"
response.write "<table border=0 width=100% >"
response.write "<table border=0 width=100%\>"
response.write "<table width=100% border=0>"
%>
For simplicity's sake, I prefer the first method - partly because it's much easier to read, and partly because any
HTML attribute that isn't a number should be enclosed in quotes of some kind.
This is a trivial example, of course; but the same principals apply when creating an ASP file using FileSystemObject.
Can I dictate the load order of files on the client from ASP?
Some people have asked whether or not they can specify that certain HTML elements (such as images, Flash,
While there are certainly ways to handle this with client-side code (DHTML and JavaScript), the answer is NO. ASP
runs on the server, and simply returns HTML to the client. That's it.
How do I comment blocks of ASP code?
Many people get frustrated because VBScript doesn't allow you to comment blocks of code; instead, a REM or ' is
required before every line. Other languages, such as Java, JavaScript, Perl, and T-SQL allow the use of delimiters
such as /* comments */ which allow you to comment single or multiple lines without much fuss. So is there a way to
Yes! Here are two ways of preventing a block of code from writing... the first requires two comments to make it run
<%
Sub doNotExecuteThisCode
For Each x In Request.Form
Response.Write x & "<br>"
Next
Response.End
End Sub
%>
Now, to execute the code again, simply comment the Sub and End Sub lines:
<%
'Sub doNotExecuteThisCode
For Each x In Request.Form
Response.Write x & "<br>"
Next
Response.End
'End Sub
%>
Now, to execute the code again, simply change the value of ExecuteCodeBlock to TRUE:
<%
ExecuteCodeBlock = TRUE
If ExecuteCodeBlock Then
For Each x In Request.Form
Response.Write x & "<br>"
Next
Response.End
End If
%>
FWIW, the latter method is a little less efficient, since the If logic is actually tested. I use it all the time, however,
since it's much easier to set a debug flag on or off to affect a group of blocks of code.
To make this command visible, the 'Text Editor' toolbar must be checked under Tools / Customize... / Toolbars.
Where can I find out about running Perl in IIS?
ActiveState has helpful documentation and downloads that will help get you up and running with Perl in no time.
How do I warn people when their session is about to expire?
Many people want to use response.redirect in Session_OnEnd() to trap the session event (whether to give the user a
prettier message than telling them they're not logged in, or to clean up database activity, etc). As explained in
Here's one idea. Use client-side script to send the user a warning just before the timeout. The following example,
used on each page, demonstrates how to warn the user two minutes before their session will expire. You are free,
obviously, to change the code that happens within the JavaScript (e.g. call a function), and to change the
advanceWarning value.
<%
advanceWarning = 2
jsTimeout = (session.timeout - advanceWarning) * 60000
%>
<script>
window.setTimeout("alert('Session is about to expire');",<%=jsTimeout%>);
</script>
This example assumes the page is sitting idle, and doesn't have any components (FRAMEs, parent documents,
IFRAMEs, IMGs) that are interacting with the server. In those cases, users might be warned far earlier than their
You might be careful if you use this kind of code (say at exactly the session timeout, rather than two minutes
before) to clean up database or other session data by calling a separate ASP file from JavaScript (e.g. in a hidden
IFRAME, new window, or even by changing the SRC attribute of an IMG). The JavaScript code will only fire if the
1. If you are running Windows 2000, you are running IIS 5.0 / ASP 3.0.
2. If you are running Windows XP, you are running IIS 5.1 / ASP 3.0.
3. If you are running Windows NT 4.0 or Windows 9x, you can determine which version of IIS / PWS you are
<%
response.write(Request.ServerVariables("SERVER_SOFTWARE"))
' returns "Microsoft-IIS/4.0" for IIS 4.0 + ASP 2.0
%>
OR
Look for Windows NT 4.0 Option Pack in Add/Remove Programs (Control Panel). If it is there, you
are running IIS 4.0 (NT Server) or PWS 4.0 (NT Workstation or Win9x).
OR
So, some schmuck in a newsgroup told you to go see Q191987, and now you're wondering how to get there?
DUH! That's simple, it's http://support.microsoft.com/?kbid=191987 ... okay, that DUH was funnier back when the
KB was in a more cryptic format, with slashes inserted at various intervals in the middle of the 6-digit article
number.
If you have MSDN Library installed, you can open it and search for the Qxxxxxx string. (If you don't have an MSDN
Library subscription, I highly recommend getting one. They're relatively cheap, and easily outperform searching the
online versions of the KB and MSDN -- by about 50 times.) You can also search Google for the Qxxxxxx string; you
will often find a cached version of the article, and Google's database is known to be more reliable than the
knowledge base (currently there is a large flux of articles that are "in migration" -- in other words, missing). You can
also switch to the groups interface to see discussions from newsgroups involving the article in question -- pretty
neat feature.
The following URL format is still valid, but will stop working at some point -- so you should consider migrating away
from it (particularly if you store these URLs in your own knowledge base or web site).
http://support.microsoft.com/?scid=kb;en-us;Q191987
We actually store only the KB article, and the formatting is handled at generation time. This way, when Microsoft
changes their URL format (which they do often), we only have to change one line of code and the entire FAQ is
immediately updated.
Not so hard, right? Now, to do this bit at will, copy the following piece of code and save it in a local HTML file,
A fellow MVP (Alex Angelopoulos) has created HTA files for this same purpose, e.g. to search Google for specific KB
articles:
http://dev.remotenetworktechnology.com/mvp/googles.zip
How do I make the search engines index my ASP pages with QueryStrings?
As you may know, most of the popular search engines -- for various reasons -- do not index pages that have
querystring parameters. So, what site owners must resort to in order to get into the search engines is to make static
While you can do this yourself manually (e.g. write an admin interface that uses FileSystemObject to re-write HTML
files when data changes), there are ISAPI filters out there that will help you index with the search engines and still
keep all of your content fresh. Here are a few that I have come across:
ASPSpiderBait
IISRewrite
ISAPI_Rewrite
URLReplacer
XCache
XQASP
This code currently assumes IE as the browser... I will work on porting the code to work in Netscape. But it does
<p><a href="ClientSession.asp?random=<%=server.URLEncode(Now())%>">Refresh
this page</a>
<script language="VBScript">
sub SetSessionIMG
sValue = trim(document.all("txtSession").value)
set objImg = document.all("imgDummy")
objImg.src = "SetValue.asp?SessionVar=" & Escape(sValue)
end sub
sub SetSessionIFRAME
sValue = trim(document.all("txtSession").value)
set objIFrame = document.all("iframeDummy")
objIFrame.src = "SetValue.asp?SessionVar=" & Escape(sValue)
end sub
sub SetSessionXMLHTTP
sValue = trim(document.all("txtSession").value)
set obj = CreateObject("Microsoft.XMLHTTP")
obj.Open "GET", "SetValue.asp?SessionVar=" & Escape(sValue), False
obj.Send
end sub
</script>
<%
Session("myVar") = Request.QueryString("SessionVar")
%>
Why do I get 800A0414 errors?
If you are calling a simple subroutine, or a function that has no return value, you omitted the CALL keyword before
the function/sub call, or added parentheses. For example, the following code:
<%
set fso = Server.CreateObject("Scripting.FileSystemObject")
fso.CopyFile("c:\test.txt", "c:\test.bak")
set fso = nothing
%>
Error Type:
Microsoft VBScript compilation (0x800A0414)
Cannot use parentheses when calling a Sub
/<file>.asp, line <line>, column <column>
fso.CopyFile("c:\test.txt", "c:\test.bak")
-----------------------------------------^
<%
set fso = Server.CreateObject("Scripting.FileSystemObject")
fso.CopyFile "c:\test.txt", "c:\test.bak"
set fso = nothing
%>
or
<%
set fso = Server.CreateObject("Scripting.FileSystemObject")
CALL fso.CopyFile("c:\test.txt", "c:\test.bak")
set fso = nothing
%>
How do I round a number *properly* with VBScript?
Admittedly, VBScript's built-in round function leaves a lot to be desired. Thanks to Anthony Sullivan and Mike Trinder
for helping me to build the following function, which seems to work a lot better in all cases:
<%
Function roundit(number,decPoints)
decPoints = 10^decPoints
roundit = round(number*decPoints+0.1)/decPoints
End Function
%>
How do I know which version of VBScript my server is running?
<%
response.write "VBScript Engine: "
response.write ScriptEngineMajorVersion
response.write "."
response.write ScriptEngineMinorVersion
%>
If you have direct access to the machine (either physically or through remote control software), you can do one of
the following:
1. Use Windows Scripting Host, if it is installed. Save the following script as .vbs and double-click it:
2. Use client-side VBScript. Save the following as .html, and open with Internet Explorer:
<script language='vbscript'>
msgbox ScriptEngineMajorVersion & "." & ScriptEngineMinorVersion
</script>
3. Finally, you could right-click vbscript.dll, which should be in %windir%\system32\, choose Properties,
General | Database-related
■ If possible, put web root, temp, database, system and pagefile on separate partitions. If separate physical
drives, even better. If you can use RAID 5 or RAID 0 + 1, by all means, do so. Keep your drives
■ Don't generate large strings in single variables -- concatenation is very expensive. If you are looping through
an array or recordset and building a string, only to dump it to the screen at the end of the loop, use
response.write within the loop instead. Time it, you might be surprised how inefficient concatenation can be
(and this is exponential as the size of the string goes up). If you are looping through and building a string
that will need to be used in multiple places later on, consider an array of smaller strings. VBScript's string
■ Avoid storing too much data in session variables, and also make sure your session.timeout is reasonable.
This can use up significant amounts of memory on the serevr, and session variables hang out long after the
user closes the browser. In my testing, scenarios where large amounts of session variables were used,
performance was enhanced by replacing this with a single session variable (e.g. an IDENTITY value, or a
GUID()) used to store / retrieve "session" data in a database. See our cookieless shopping cart for
somewhat of an example. And of course, just about every object you will use in ASP should not be stored in
■ Try not to do too much work, especially interaction with a database or remote server(s), in a single script.
Consider optimizing the code and perhaps spreading the work over multiple pages. I often see errors like
this happening:
<some error>
/file.asp, line 1294
This seems to be *way* too many lines of ASP code to (a) manage and (b) expect to run efficiently. I've
worked on some pretty big ASP applications and I don't recall ever having an ASP script more than 250 lines
long.
■ Try to avoid nested loops of any kind.
■ Use Option Explicit. Yes, this can be a pain in the butt and slow down development slightly, but it forces you
to use locally declared variables. As Eric Lippert [MS] explains in this Google post, using declared variables is
more efficient. (As an aside, Option Explicit helps prevent seemingly simple typographical errors, which
always turn out to be a big production because you can't figure out why myNumber is blank, when in
■ Compare apples to apples! Instead of making the ASP engine implicitly cast values for you, force it. Also,
instead of this:
Store this variable locally... both to avoid multiple cast operations, and to avoid looking up the value from
whatever = clng(rs("whatever"))
if whatever = 1 or whatever = 6 then
■ Use readall() instead of while not AtEndOfStream / readLine. I've seen this kind of logic far too often:
The following is much more efficient. You get the whole string in one call, and this allows you to close the
strings / arrays, to prevent duplicates and other problems in forms. You may recall in SAMS' Active Server
Pages 2.0 Unleashed, I wrote a section about using data from the server in client-side validation, to prevent
duplicates and such WITHOUT requiring a round-trip. But this is only useful with smallish data sets. The
client side can only handle so much JavaScript before it croaks. The Mac will crash and burn far earlier than
the PC. I haven't seen this cause problems per se on the PC, only sluggishness.
ASP Guidelines
Database-specific | General
■ Make sure you're using MDAC 2.7 (see Q300420 for one of the problems with 2.6) and the most recent SQL
Server service pack. For information on keeping your server secure and healthy, see Article #2151.
■ I am currently using many of the suggestions described in the SQL Server 2000 Operations Guide. If you're
using SQL Server 2000, please take a look at this excellent article.
■ Use a DSN-less connection string (see Article #2126), and enforce TCP/IP (avoiding name resolution) by
using Network=DBMSSOCN in the connection string (see Article #2082). Use ADO and OLEDB, not DAO or
ODBC. Use the EXACT same connection string throughout your application, in order to make the best
■ Open your connection just before needing it, and close it as soon as you're done with it. Your motto should
■ Avoid adovbs.inc (see Article #2112). If you absolutely must use named constants, define the subset you
■ If your SQL queries take too long to run, consider moving the queries themselves to stored procedures,
instead of using ad hoc queries (see Article #2201). Use indexes on any column(s) used in the WHERE or
ORDER BY clauses (see Article #2231). Increasing timeouts is not a solution! It's more like painting your car
This prevents "1 row(s) affected." messages from being sent back over the wire, prevents SQL Server from
working to obtain those numbers, and prevents ADO from tripping over recordsets that aren't really
recordsets. If you need to know the number of rows affected while debugging, set up PRINT @@ROWCOUNT
calls on the following line, or store them all in a table and SELECT from them *after* all your other SELECT
statements, that way you can view such results in Query Analyzer without interfering with your ASP code.
■ Make sure your network connection between web server and database server is not the bottleneck. You can
test this by sitting at (or terminal serving into) the database machine directly, and executing the query from
its own Query Analyzer, and comparing the times with your ASP results (see Article #2092 and Article
■ Limit resultsets when possible. For example, if you are returning a student directory, make the user restrict
the results to those whose last name starts with G or M. This will reduce the work you are forcing your
database and web server(s) to perform, and also making the interface more usable (it's very rare that the
end user needs to see the entire table in one shot). You could also consider paging (see Article #2120) -
■ Do not nest recordsets in ASP! The database is faster at grouping/processing rows. If you think you need
nested recordsets, consider a JOIN. If you are having problems with an appropriate JOIN strategy, please
post your table structure(s), sample data and desired results, and others will help you get a correct query
working.
■ Do not use rs.movefirst when using a default, forward-only recordset. Set up SQL Profiler when doing this,
to see why.
■ Avoid extra ADODB objects unless necessary (see Article #2191). If you are using ADODB.Recordset or
■ Use the adExecuteNoRecords constant for INSERT, UPDATE and DELETE queries:
■ When designing your tables, use the narrowest columns possible. If you have a numeric value that will be
from 1-10, use a tinyint, not a smallint or an int. If it can only be 0 or 1, use a BIT. If you have a column for
a Social security number, use INT or CHAR(9) (or CHAR(11), if it has to be string formatted for some
Article #2354). If second- and millisecond-accuracy are not important, use a smalldatetime instead of a
datetime.
■ Avoid cursors if possible - most cursor solutions have a more efficient, set-based alternative. If you can't find
a set-based solution, post your table structure, sample data, and desired results to
microsoft.public.sqlserver.programming and someone will help you. If you still need to use a cursor, or if the
cursor solution is faster (rare, but possible), make sure you close and deallocate the cursor when you are
■ Only get the data you need - avoid unnecessary columns, frivolous JOINs, and the all-too-common SELECT *
■ Use local temp tables in place of global temp tables. Make sure you drop all #temp tables at the end of your
■ Choose constraints over triggers when given the choice. Assuming you have decent error-handling in place,
this is a more efficient way to regulate your data and prevent invalid data from entering into your database.
■ Test the performance differences of IN vs. EXISTS, ISNULL vs. COALESCE, each of which can provide the
exact same results, but often the optimizer can choose a better plan in one case or the other. The
performance can differ based on the datatype(s), size of table, number of relevant rows, and other factors.
■ Try to avoid non-sargable conditions in the WHERE clause, such as "IS NULL", "IS NOT NULL", "OR", "<>",
"!=", "!>", "!<", "NOT", "NOT EXISTS", "NOT IN", "NOT LIKE", "LIKE"... any non-sargable arguments will not
use an index and will almost always result in a table scan (trust me, you don't like table scans).
■ Make sure you are either committing or rolling back all T-SQL transactions. If a query is not fully committed
or rolled back, you can exceed the concurrent query limit, and can cause blocking for all other SPIDs. Check
@@TRANCOUNT within the SPID's connection, if possible, and issue a ROLLBACK if it is not equal to 0.
■ For tips on optimizing Access performance, see MSDN's Ways to optimize query performance.
What is Event ID 36, and how can I get IIS running again?
Many people have discovered Event ID 36 in their Event Log, and associate it with a time where ASP stopped serving
on their IIS 5.0 server (HTML continues to serve fine). This is usually associated with a failure with an error number
of 80004002, 8002801c or 80040154. This particular error is often associated with the message "No Such Interface
Supported"... which is a bit ambiguous, to say the least. It can also be "The server failed to load application
'/LM/W3SVC/1/Root'" or "The specified metadata was not found." Here are the various solutions that have worked
- Check that IIS' Application Mappings for ASP are intact, under IIS' Home Directory / Configuration interface.
- Move the Application from high to medium to low application protection, re-testing your ASP pages each time.
- Re-synchronize your IWAM account by running C:\inetpub\Adminscripts\synciwam.vbs (see Q255770 for more
info).
regsvr32 c:\winnt\system32\oleaut32.dll
regsvr32 c:\winnt\system32\inetsrv\asp.dll
- Apply Windows 2000 SP2, MDAC 2.6 or 2.7, and all relevant security fixes.
- If you have Crystal Reports 8 installed, uninstall IIS 5.0 from Add/Remove Programs, install the Seagate fix,
Not if you value any of the ASP code they contain. There are sites all over the web that use .inc file to hold their
database connection info and other data that should remain on the server side; they think that information is safe
because they haven't told anybody where it is. Well, I remember several times I would get an error on someone's
Error Type:
Microsoft VBScript runtime (0x800A01B6)
Object doesn't support this property or method: 'rs.moveOver'
/includes/database.inc, line 3
So, I would type in the URL to /includes/database.inc file and, lo and behold, the browser would dump out the ASP
code (in a few cases, with sa passwords!). In fact just now, it took me all of five minutes to find a username /
password through a search on Google, register the server in SQL Enterprise Manager, and be tempted to execute a
command like:
Of course I didn't do this, and promptly informed the server's owner about the hole (and that they should change
There are two decent workarounds that will prevent this from happening to you. One way is to leave your .inc files
alone, and simply map .inc extension to asp.dll (as described in Article #2124). The other is to simply use the .asp
extension on all files that include ASP code. If you need to differentiate between "includes" and normal interface
pages, simply change your naming scheme a bit. In one project I was involved in, the authors had realized the
security concerns and quickly renamed all files such as "include.inc" to "include.inc.asp" - so the files were still easily
One concern people have raised in the past about the extension workaround is that now asp.dll will have to process
all "includes" as ASP files... even the ones that don't contain ASP code. Never fear, IIS 5.0 and above have much
smarter interpreters that only invoke the ASP engine when necessary.
Can I perform simple encryption / decryption in ASP?
Sure, however before you get into the code please understand that this is *not* secure in any way. In fact, it is
more like encoding than encrypting; it will protect the string from casual viewing, but will not be safe against anyone
with a purpose (and 5 minutes to kill). If you need encryption, see the list below the code for several free and
commercial offerings.
Basically, this code takes the ASCII value of each string, offsets it by a number you can control (don't go too high
though; I'd stick in the range of -47 -> 133 to be safe, as CHR() will return errors if you reach > 255). This
assumes, of course, that the original string contains ONLY alphanumeric characters. You will have to play with that
range if you are using other characters, and you will also want to test this code if you are using a non-default
character set.
<%
str = "ZzAa019WHOLEbunch"
offset = 8
for i = 1 to len(str)
newStr = newStr & chr((asc(mid(str,i,1))+offset))
next
for i = 1 to len(newStr)
oldStr = oldStr & chr((asc(mid(newStr,i,1))-offset))
next
Components Available
a1asp.crypto
ABCCrypto
ASPEasyCrypt
ASPEncrypt
ASP MagicCrypt
Cast 128
CEncrypt
CryptToy
DigiSign 2.0
DynuEncrypt
EncryptCC
Hash
Polar Crypto
SloppyCode.Net
How do I change document names / extensions in IIS / PWS?
Open up Internet Services Manager. In IIS / PWS 4.0, this is on the Start Menu under Windows NT 4.0 Option Pack / Internet
Services Manager. In IIS 5.0 / Windows 2000, this is under Administrative Tools in the control panel. (I have a shortcut to this
tool on my taskbar; if you do a lot of work in the MMC tool for IIS, you may want to do the same.)
To change/add document names, for example if you want http://www.yoursite.com/ to display that way, but actually load
2. Choose 'Properties'
4. Select the 'Add...' button, type in the filename you wish to override default.asp
5. Click OK, then click the up arrow next to the file list to push your new file to the top of the list
6. Click Apply, OK, and close IISAdmin (if you are asked to save console settings, choose Yes).
To add a documentation extension, for example if you want <file>.inc to actually run as an ASP file and be parsed by
2. Choose 'Properties'
6. For 'Extension' type in the extension you wish to use (including the leading period)
7. The only verbs you should need are GET and POST (see MSDN for information on HEAD, OPTIONS, TRACE etc.)
8. Click OK, Apply, OK, and close IISAdmin (if you are asked to save console settings, choose Yes).
How do I detect the browser's encryption level / cipher strength?
<%
Response.Write Request.ServerVariables("HTTPS_KEYSIZE")
%>
Note that this information is not available to ASP until a SECURE connection has been negotiated.
Why do I get 'HTTP/1.0 Invalid Application Name' errors?
After installing / removing applications, or manually removing Client for Microsoft Networks directly, you may find
that IIS 5.0 suddenly will not start. Errors that may appear in the event log are:
■ The service did not respond to the start or control request in a timely fashion; or,
There is a comprehensive article at IISFAQ.com that will help with this problem.
I have plenty of RAM, why do I get an 'Out of memory' error?
or
This message has little to do with the amount of RAM you have. I have a workstation with 1 GB of RAM, and I have
VBScript has inherent limitations on the amount of space it can allocate to, for example, arrays. So, if you try this:
<%
dim bigArray(7000,7000)
' or
el1 = 50
el2 = 400
el3 = 475
dim hugeArray()
redim hugeArray(el1, el2, el3)
%>
Some other possible reasons exist... see Q174634, Q174685, and Q191099.
And I loved this response.
Why do I get non-database-related 80004005 errors?
Here is a verbose collection of information surrounding 80004005 errors that have nothing to do with a database. If
Luckily, most 80004005 errors that are not database-related are trivial to fix, though a few are a but more tricky
than that...
If there is no message associated with the error, there is at least one possible culprit: you tried to assign a negative
value to Server.ScriptTimeout. Make sure you pass Server.ScriptTimeout a value between 1 and 2^32-1 (see Article
#2066).
This is not usually about physical memory -- Article #2196 has some information about this error. In addition, if you
are getting this error when trying to connect to Project 2000 Server, make sure you are connecting to the root (e.g.
or
This can be caused by passing an undeclared variable into Request.Form or Request.Querystring, e.g.
<%
Dim str
Response.Write(Request.QueryString(str))
' or
Response.Write(Request.Form(str))
%>
To correct, make sure your str variable is defined and populated with a valid incoming form variable (and make sure
This can happen if you have a function in a VB or C++ DLL that is expecting an INT or LONG value, and you
accidentally pass a string. Check your argument(s) and make sure they match the datatypes that the component is
expecting. If you debug using response.write, you may see a value that *looks* numeric but you should always be
This is often caused by using a numeric argument in Request.QueryString or Request.Form, without checking that
<%
' ...
sql = "SELECT id FROM table"
set rs = conn.execute(sql)
do while not rs.eof
response.write Request.Form(rs("id"))
rs.movenext
loop
%>
At the line where this error is happening, response.write the index you are passing to the array or structure. It
should be clear that this number is either null or outside the dimensions of the array. For more information on
or
Each form field is limited to 102kb (according to Q273482)... for a verbose workaround, see
or
You have somehow embedded an opening script tag (<%) without closing it. A simple method to reproduce is as
follows:
<%table width=100>
If your code is really complex and you can't find the offending tag, start at line 2 and move a response.end
or
<%
Without ever closing the tag. You may even have done this:
Notice the missing close brace (>)... it might have been accidentally carriage-returned to the next line.
Similar to the above case, you either forgot to close your object tag at all, or left off the trailing close brace (>),
e.g.:
Of course, you need to tell ASP the ProgID or ClassID of the object you want to use, otherwise it will have no idea
or
<OBJECT RUNAT=CLIENT>
If you are mixing server- and client-side scripts and/or objects, keep in mind that you only have to specify the
RUNAT property for those you wish to be interpreted server-side. Since client-side is the default, just leave the
Again, this error is self-explanatory. Such elements need to have an ID associated with them, so that you can
actually refer to it later to call methods and properties (I prefer set foo = Server.CreateObject("Prog.ID"),
<script runat=server>
' ...
</script>
You need to specify the language. If you mean to use the default language, use <%%> delimiters instead. See
Article #2045 for information on how switching delimiter style can affect the way your script executes.
This is another helpful error message. Check the line the error points to, and see if there are any attribute values
<!--#include file=foo.asp
<% ' stuff %>
or
Make sure your comments and include directives are closed correctly, using -->.
<!--#include=whatever.asp-->
You are attempting to use a language that is not installed, e.g. PerlScript. Make sure that you have installed all
or
See Article #2412 for a decription and resolution of these error messages.
Active Server Pages, ASP 0133 (0x80004005)
Invalid ClassID attribute
/<file>.asp, line <line>
The object has an invalid ClassID of '<classID>'
or
Usually, this is due to using a ProgID or ClassID that isn't properly registered on the system. For more information
Like the error suggests, an #include file is being included by itself somehow. While it may not contain an #include
reference to itself, it may include a file that does. This may take a while to find; if so, it may be a hint that you need
You are using an <OBJECT> tag and gave it an ID=Response or ID=Application. Make sure you avoid using intrinsic
names (others are Server, Session, and Request). You should also avoid using VBScript keywords like for, e.g. the
You cannot place <% ' code %> within global.asa. Any code that executes within global.asa must do so within one
client-side script:
To work around this, you can either (a) use <%%> delimiters for the server-side script, or (b) break up the client-
Again, a self-explanatory error message. Make sure you haven't done this:
Like the error states, you can't place an @ directive tag after any other content in an ASP page.
Active Server Pages, ASP 0141 (0x80004005)
The @ command can only be used once within the Active Server Page.
This error means just what it sounds like; you should only have one @ directive in any ASP page. You should check
any includes, if you only see one @ symbol in the base page. This is probably due to including two files that both use
@language or other @ directives, but it is possible that you have two directives in the same page. It is also possible
that you "need" two @ directives, for example if you need to specify a language and a codepage. You can work
around this by using <script runat=server> and/or session.LCID syntax. See Q307190 for more info.
You can't use response.redirect or response.cookies after sending any HTML content to the browser, unless you have
enabled response.buffer = true. For more information, see Article #2011 and Article #2217. This error can also
happen if you write HTML content to the browser before setting a response. property, e.g.:
If you have enabled buffering at the site/application level, you can not disable it at page scope. See Article #2262
for more information.
See Article #2375 for more information on this specific error message.
This can happen if you have disabled buffering and try to use buffer-dependent methods such as response.clear() or
response.flush(). See Article #2262 for information about enabling buffering at the site / application level.
This often happens when using JavaScript to retrieve Request.Form or Request.QueryString values, and assigning
This error message is pretty self-explanatory. You have used server.mappath() but you have either not passed it a
value or the variable you passed did not contain the value you expected. Debug the line that is causing the error by
changing this:
lPath = Server.MapPath(vPath)
To this:
Response.Write(vPath)
Response.End
'lPath = Server.MapPath(vPath)
generate a local/physical path from a web structure. So, if you have the physical path already, don't bother calling
Server.MapPath().
or
If you are trying to give a reference to a file in a parent folder, you might have to work around this problem by using
a path relative from the root, rather than relative to the current folder.
Check that the path you are passing into the MapPath method makes sense. Try to use a path relative to the root.
Active Server Pages, ASP 0194 (0x80004005)
An error occurred in the OnEndPage method of an external object.
See Article #2053 for several links on the limitations of components written using the apartment model.
When you are uploading files, you need to use a different mechanism to get the text from any 'regular' form
the object no longer exists. You should peruse the links in Article #2053 to see why you should rarely be using any
As the error states, the only valid scope values are Page, Session and Application. However, it is very rare that you
would be creating an object for application or session scope (see Article #2053), and since the default is page, you
won't normally need to include the scope attribute in your object tags at all.
or
This can happen if you try to specify an LCID / CodePage that your system isn't set up to handle, e.g. on a default
install:
<%
ShowLocaleDate 1041, "Japan"
To fix, either use a different LCID, or you will need to install support for far east languages (in the case of Japan) or
whatever codepage you need to use (e.g. support for right-to-left languages, if you need to support Arabic or
Indian), through the "regional settings" applet in the control panel. Here is where these options live in .NET Server:
If <text> is:
SCRIPT LANGUAGE="VBSCRIPT"
The SCRIPT keyword is redundant here. Your tag should look like this:
runat=server
The runat option is unnecessary in an <%@ %> directive, because the tag is already interpreted only in server-side
script. The runat=server option only belongs in a server-side script defined with the following syntax:
Page Language="vb"
You are attempting to use VB.Net in an ASP page. You should be using an ASP.NET page, with an ASPX extension.
Active Server Pages, ASP 0223 (0x80004005)
METADATA tag contains a Type Library specification that does not match any
Registry entry.
or
Double-check your METADATA tags. One of them points to an invalid file or ProgID. For valid METADATA tags to use
or
Check the URL that you are passing to the transfer or execute method. Make sure it is a relative URL, and that you
can hit it with a browser. Response.Write a Server.MapPath on the value you are passing, and make sure it produces
or
With Server.Transfer and Server.Execute, you cannot pass absolute (e.g. http://url/ or c:\file\) URLs, nor can you
pass querystrings. The target page will have access to If you are meaning to execute a page on a different server,
see Article #2173 for a small tutorial on using the XMLHTTP object.
This can happen when you try and implement the following code:
The following syntax doesn't choke, but the #include file does not execute.
<%
<!--#include file='rw.asp'-->
%>
There is no reason to put the #include directive inside an existing ASP code block.
Active Server Pages, ASP 0238 (0x80004005)
No value was specified for the 'version' attribute.
You are probably including a METADATA tag for XML in the following format:
However a more appropriate fix would be to install / use a more recent version of the XML SDK.
Check your METADATA tag, it is either missing the PROGID attribute entirely, or it is empty.
Make sure you have installed language support for the language you want to use, and that you have correctly set
the Session.LCID or Session.Codepage to the appropriate value. See Q245000 for more information.
Active Server Pages, ASP 0240 (0x80004005)
A ScriptEngine threw expection 'C0000005' in
'IActiveScript::GetScriptState()' from 'CActiveScriptEngine::ReuseEngine()'.
or
Sounds like another case of a malformed METADATA tag. Check your syntax and double-check it against the source.
If you know of any other 80004005 errors that we haven't covered here, please let us know...
How do I solve 'The specified procedure could not be found' errors?
This issue will come up if you fixed the "f**k Government" worm issue, outlined and patched at the following URL:
MS00-086
You can resolve this issue simply by (re-)applying NT 4.0 Service Pack 6a.
Visit Microsoft's security site for other security bulletins, patches and updates.
How do I display the Euro symbol ( ) in my ASP pages?
Being a relatively new concept, the Euro is not covered under any special Session.LCID value (at least not that I
know of) for use with VBScript's FormatCurrency function. But there is an HTML entity to represent the character, so
it is trivial to wrap a presentation layer for such currency in your own function. I tend to use emphasis on the
<%
Function FormatEuro(n)
If IsNumeric(n) Then
FormatEuro = "<b>€</b>" & FormatNumber(n,2)
Else
FormatEuro = "<b>€</b>" & n
End If
End Function
Response.Write(FormatEuro(48.24))
%>
How do I log / track ASP errors on my web site?
In IIS 5.0 and above, there is a new object called the ASPError object, which allows you to have more control over how your
errors appear to users, and how you can track these errors and prevent them from occuring in the future. This article will
quickly guide you through creating a tiny application that will track 500 errors and allow you to review them at the end of the
day. The article assumes you are using SQL Server as your database backend, but could be modified easily to work with
Before we start, let's force a pretty simple 500 error (save it as <wwwroot>\force500error.asp) with the following code:
<%
functionThatDoesNotExist()
%>
Now, let's create a simple example of a 500 error handler page. First, create the following ASP page:
<%
Set ASPErr = Server.GetLastError
Response.Write ASPErr.Description
%>
Save this file in <wwwroot>\500_try.asp (the default file in IIS for handling custom 500 errors is called 500-100.asp - we
should leave this file alone, in case we need to restore the default behavior at some point). Now, go into Internet Services
Manager, right-click the Default Web Site (or any web site or application where you want to isolate this test), choose
properties, on the Custom Errors tab, scroll down to 500;100 and hit Edit, verify that Message Type has "URL" selected, and
make sure the URL points to /500_try.asp (or wherever you placed it).
Once you've done this, you should get a prettier error when you hit force500error.asp:
That's just for starters. You can customize the 500 handler page later by adding your web site graphics, navigation, and even
other content. Keep in mind that, like the custom 404 error page described in Article #2162, path is important when defining
the images and other references in your 500 handler page. If this page is expected to handle errors in ASP pages in the root
of your site / application as well as sub-folders, all possible locations need to be aware of the exact path to any referenced
files.
All right, so let's create a table that will store the information we might want to track. In addition to the extra information
provided by the ASPError object that is not available with the traditional ON ERROR RESUME NEXT architecture, it might also
be useful to store referrer, user agent, ip and post data as well. So our table will look like this (and since we are going to
query by date after the fact, we put a clustered index on the datetime column):
And the following stored procedure will facilitate inserting data into this table when an error occurs:
So now that we have a table to store our data, and a stored procedure to interface with it, let's create the following ASP page
<%
Response.Status = "500 Internal Server Error"
This ASP page will store all the relevant data. You can test it by hitting force500error.asp again, and then using Query
You should see the results of any 500 errors that have occured since you set up the table and new 500 ASP page.
Now here is a stored procedure that will return all of the 500 errors that occurred during the current day (you can modify this
to go back to the beginning of the week, or the beginning of the month if you like):
I'll leave the extended details page as an exercise for the reader.
Note that Server.GetLastError and the ASPError object are *only* available in the custom 500 error handler. In your normal
ASP pages, they will not work as illustrated above. For more information, see the ASPError Object and Server.GetLastError
This seems to be a pretty common error with (shudder) FrontPage 2000 Server Extensions.
This is usually because session state has been disabled, either through the Internet Services Manager GUI, or with
the @EnableSessionState directive. To use the Session object, session state must be enabled for the application.
How do I get the computer name / IP address of the server?
Here is a small code sample using WScript to get the computer name, in addition to members of the ServerVariables
collection for returning the domain name entered in the browser and the IP of the server.
<%
Set pc = Server.CreateObject("Wscript.Network")
response.write pc.ComputerName
Set pc = nothing
response.write " (currently connected as "
response.write ucase(Request.ServerVariables("SERVER_NAME"))
response.write " on "
response.write Request.ServerVariables("LOCAL_ADDR") & ")"
%>
Of course, you may want the actual computer name (from system properties), not the server name as requested
through a web server (which could be an IP address, or an alias, or a fully qualified domain name). So, you can use
WMI:
<%
Set Loc = Server.createobject("WBEMScripting.SWBEMLocator")
Set nms = Loc.ConnectServer()
WQL = "SELECT CSName FROM Win32_OperatingSystem"
Set cs = nms.ExecQuery(WQL, "WQL", 48)
For Each pc In cs
Response.Write pc.CSName
Next
set cs = nothing: set nms = nothing: set loc = nothing
%>
And here is a sample for getting the name of a SQL Server machine:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open "<connectionString>"
set rs = conn.execute("SELECT @@SERVERNAME")
response.write rs(0)
rs.close: set rs = nothing
conn.close: set conn = nothing
%>
Why do I get 'HTTP 500-12 Application Restarting' errors?
If you have extremely long processes that are processed during Application_OnStart or Session_OnStart, you might
occasionally see this message. If so, consider moving such processes to an offline source (e.g. for pure database
processing, use a job in SQL Server; for ASP tasks, see Article #2143 to learn how to schedule a VBS file instead).
This could be due to McAfee running on the server. According to Q303881, it marks global.asa as modified during
each scan, and this causes IIS to reload global.asa. If this is the case, you need to reconsider your virus protection.
Like Norton, using a desktop application on a server can have unexpected results.
See Q236446 if you are running Site Server 3.0, and try commenting out any instance of the 'ReloadSite' method.
If none of the above solves the problem, you could check your event log for any ASP-, IIS- or MTS-related errors.
Other than that, even Microsoft doesn't have much to offer. They say "the message is harmless" and that "there is
In short, you can't. If you don't want people using your images or layout ideas, don't put them on the web. There
are many ways to retrieve images, so don't bother using client-side JavaScript to trap right-click events (I can get
around that simply by disabling JavaScript, using a browser without onContextMenu support, or using my
'PrintScreen' button). However, to get an exhaustive tutorial for preventing casual surfers from using the common
Most people organize their message lists by date received, descending. This is so that the most recent posts are at
the top. Unfortunately, most NNTP servers do not reflect the date / time a post is received, but rather figure it out
based on the date / time the client SAID they posted it, adjust for THEIR timezone, then perform an offset for the
client's timezone.
Recognizing this, many people set their clocks ahead (by hours, days or even years) in an attempt to keep their
posts at the top of the list. Others simply have their timezone set incorrectly. In both cases, the post remains at the
top of the list for a short amount of time or, in extreme cases, until the server purges the messages (this is a few
When confronted, inevitably someone rushes to their defense and says "well, maybe they're in the UK, and it's five
hours later there." Incorrect; the news server adjusts for that. Note that with normal clock settings, a conversation
between myself (East Coast USA) and someone in the UK would look like this to me:
+ my post 2:30 pm
- uk post 2:42 pm
+ my post 7:30 pm
- uk post 7:42 pm
+ my post 11:30 am
- uk post 11:42 am
This is the beauty of the newsgroup... posts follow a logical chronological order, no matter where you're viewing the
conversation. You send a post at 2:00 am your time, and I answer you at 2:45 am your time. In your newsreader, it
shows the entire conversation took place in 45 minutes. Was I up at 2:45 am? No. Did you wait only 45 minutes for
the response? Yes. Maybe it was 6:00 pm my time when I read your initial response. I answered at 6:45 pm my
time. My newsreader also shows the entire conversation took place in 45 minutes. This is not a coincidence people.
Some suggest ignoring such posts, but I am a firm believer in education. If you ignore the post, they post again
(likely still with an incorrect clock), thinking that nobody saw their original message. And often people don't notice
the post is future-dated, and respond. This prevents the original poster from knowing their clock is wrong (maybe it
was an honest mistake), and if they do know, it sends the message that it's okay to violate netiquette rules. So get
Using Outlook Express, there are at least four ways to eliminate this clutter from the top of your message list:
■ hit Shift+F3, click Advanced Find..., fill in the criteria that will allow you to identify the post(s), click Find
Now, right click the message(s) and choose "Move to Folder" then browse to the deleted items folder;
■ block the sender entirely (this is a bit harsh though!) using Message | Block Sender;
■ set your options in Tools | Options | Maintenance to delete message more than x days old (it does a diff, not
an absolute, so will filter messages from the future also - but will only filter messages beyond today); or,
Event ID 5 seems to be the generic catch-all ASP error logged to the event log. There is probably a lot more out
there than what we've compiled here, but hopefully the following list of symptoms / resolutions will help you.
or
or
This is usually an access violation error, but since it's coming from a component that the error message fails to
identify, the only advice I can offer is to follow a similar debugging path as the ASP 0115 error (see Article #2171
and Q262187). Sometimes the error is accompanied by an Event ID 4014 error, which can often contain CLSID
information that will lead you to the component causing the error (you can search for the IID / CLSID string in the
registry).
Before you go the long debugging route, however, consider upgrading the server to the most recent version of
and reboot... and make sure your server has the latest updates (see Article #2151). While this is not a proper, 'feel-
good' debugging effort, the above steps have often resolved the problem entirely.
or
Uninstall Norton CrashGuard, as it is infamous for 'inventing' errors in kernel32.dll with this error code.
or
or
or
This is probably a permissions issue. Whatever these pages are trying to write to, make sure the anonymous user
(IUSR_MachineName) or the authenticated user(s) have access. For example, if you are trying to create a
Lotus.NotesSession object, the IUSR account needs write access to the Lotus\Notes\ folder.
If you are using Project Server, you might have the following errors:
File /<file>.asa Line <line> Out of memory. Unable to allocate required memory.
and
This is often caused by global.asa being 'scanned' by Anti-virus programs and other services that might modify
and/or lock various files on your server. See Q323019 for a description and possible workarounds.
If this error is happening from Outlook Web Access, you're probably getting one of the following errors (among
others):
or
You should be able to eliminate the error by reinstalling OWA and the latest Exchange service pack(s), though
sometimes it may require reinstallation of IIS as well. Also, see Q196016 (XWEB: Outlook Web Access Fails
or
One possible kludge, or at least help in narrowing down the component causing the problem, is to increase the
This message can be ignored (see Q290612), and re-registering asp.dll will probably not eliminate the error
message anyway.
How do I change a list into a set of table rows and columns?
Developers often have sets of things they want to present to a user, and the typical way is to process each element
and then put some kind of vertical space (e.g. <hr> or <p>).
Many times, these developers want an easy way to display more information without all the vertical space. Image
thumbnails, for example, that are 20 pixels wide, don't need their own 'row' in most HTML interfaces. So what we
want to do is say "let's display n of these per row, and when we reach n, start a new row." Here is a code sample
that will handle any number of elements, and any number of columns. Download the code and try it out; just change
the first two values and play with it. This code should be easy to adapt to your recordsets, array processing, etc.
<%
' number of elements, e.g. recordcount
numElements = 11
If you were using recordset data, you would only adjust a few items. The following code assumes an open recordset
<%
numCols = 5
i = 0
do while not rs.eof
needFooter = true
if i mod numCols = 0 then
response.write "<tr>"
end if
response.write "<td>" & rs(0) & "</td>"
if i mod numCols = numCols - 1 then
response.write "</tr>"
needFooter = false
end if
i = i + 1
rs.movenext
loop
if needFooter then
response.write "<td colspan=" &_
numCols - (i mod numCols) &_
"> </td></tr>"
end if
response.write "</table>"
%>
How do I convert exchange rates in ASP?
Here are three tools that will look up exchange rates in real-time, allowing you to perform conversions and other
calculations:
IISCartEX
VoltoFXP
http://www.oanda.com/products/fxml/
As an alternative, while I don't advocate slurping from other sites, you could rig up the MSXML objects or some
http://finance.yahoo.com/m3?u.
Why does session.abandon not take effect right away?
You might notice that the results of the following code are quite peculiar:
<%
Response.Write Session.SessionID & "<BR>"
Session.Abandon
Response.Write Session.SessionID
%>
While one might expect that, immediately after a session is abandoned, a new session is created, in actuality the
first session is not really discarded until the current page goes out of scope... meaning the user will not get a new
When you add custom headers through IIS or using Response.AddHeader, you might expect them to be available in
the ServerVariables collection. To understand why these values are not available to the requested ASP script, we
need to understand how HTTP connections to ASP pages evolve. First, the client makes a request for an ASP page.
At this point, it sends its request headers to the server, populating Request.ServerVariables() and other collections.
Once it has this information, IIS sends back a response, including standard and any custom headers. The HTML
page, or Excel spreadsheet, or ZIP is rendered to the client and/or offered for download. At no point are the headers
associated with the response object available via the request object.
Custom headers are for sending information to the browser, not for retrieving it. If you're sending custom headers,
e.g.:
<%
Response.AddHeader "blah", "blah"
%>
<%
Response.AddHeader "blah", "blah"
Response.Write Request.ServerVariables("HTTP_blah")
%>
When you already know the content of the header? The above is logically the same as:
<%
Response.AddHeader "blah", "blah"
Response.Write "blah"
%>
Sometimes you want to make your HTML source code more readable, or insert consistent indents into non-HTML
elements like an e-mail. You can that with either of the two following commands:
<%
Response.Write(vbTab)
' or
Response.Write(CHR(9))
%>
To embed a TAB character into results coming out of SQL Server (e.g. for use in a tab-delimited text file based on a
Keep in mind that this output will not look the same in HTML as it does in View Source, notepad or Query Analyzer.
Why do I get 'The RPC Server is Unavailable' messages?
This can happen if you have objects in application scope (even under MTS). This is why Article #2053 explains you
should avoid storing non-thread-safe objects in session or application variables. If you are using MTS, and believe
your object is safe to store in session or application scope, then make sure your package is set "Leave Running
If you have a component that is running out of process in IIS 4.0, take a look at Q290822... you may be able to
obtain a hotfix from Microsoft if the problem can't be fixed with a service pack.
In Control Panel / Services, ensure that Remote Procedure Call (RPC) Service is set to startup automatically.
As with cookies / sessions not working, the fact that your development server has an underscore in the name might
be the cause. See if the errors go away when you access the machine by IP. If you have renamed your web server
(from or to any name, including names with underscores), see Q234142 for possible solutions.
Check the logs for any further info you might be able to obtain about this problem. You might see a web log entry
Event ID 37
Out of process application '/LM/W3SVC/2/ROOT' terminated unexpectedly.
You can also poke around Q224370 for a potential problem residing outside of IIS entirely.
Of course the problem usually goes away by simply refreshing the page. If it doesn't, you can unload the application
/ web site in Internet Services Manager, or use iisreset from the command line to restart the entire web service.
You can also attempt to change the application protection level of the application / site temporarily, to see if this
resolves the problem. If you still get the problem repeatedly, you may have a corrupt mediabase. Try reinstalling IIS
(though only after you've exhausted all of the above), but keep in mind you will have to reconfigure plenty of
<%
ss = "one,two,three,four,five,six,seven,eight,nine"
ds = "ten,eleven,twelve,thirteen,fourteen,fifteen,sixteen," & _
"seventeen,eighteen,nineteen"
ts = "twenty,thirty,forty,fifty,sixty,seventy,eighty,ninety"
qs = ",thousand,million,billion"
Function nnn2words(iNum)
a = split(ss,",")
i = iNum mod 10
if i > 0 then s = a(i-1)
ii = int(iNum mod 100)\10
if ii = 1 then
s = split(ds,",")(i)
elseif ((ii>1) and (ii<10)) then
s = split(ts,",")(ii-2) & " " & s
end if
i = (iNum \ 100) mod 10
if i > 0 then s = a(i-1) & " hundred " & s
nnn2words = s
End Function
Function num2words(iNum)
i = iNum
if i < 0 then b = true: i = i*-1
if i = 0 then
s="zero"
elseif i <= 2147483647 then
a = split(qs,",")
for j = 0 to 3
iii = i mod 1000
i = i \ 1000
if iii > 0 then s = nnn2words(iii) & _
" " & a(j) & " " & s
next
else
s = "out of range value"
end if
if b then s = "negative " & s
num2words = trim(s)
End Function
Response.Write num2words(0)&"<br>"
Response.Write num2words(196)&"<br>"
Response.Write num2words(789)&"<br>"
Response.Write num2words(-32768)&"<br>"
Response.Write num2words(999999)&"<br>"
Response.Write num2words(1000000)&"<br>"
Response.Write num2words((2^16)-1)&"<br>"
Response.Write num2words(2^16)&"<br>"
Response.Write num2words(2147483647)&"<br>"
%>
Here are some examples in T-SQL, some of which may need some modifications (and some of which will require SQL
Server 2000):
http://www.xephon.com/oldcode/Q014A05
httphttp://www.users.drew.edu/skass/sql/nameMoney.sql.txt
http://groups.google.com/groups?hl=en&selm=4bc601c193ca%242fd61280%249ae62ecf%40tkmsftngxa02
http://groups.google.com/groups?hl=en&selm=%23rkSX%23wUAHA.244%40cppssbbsa05
How do I generate a treeview from ASP?
While we construct a useful and portable example, here are some relevant links:
CodeProject.com
comobjects.net
DarkFalz.com
obout.com
TreeGen.com
xefteri.com
Why do I get the error Object Required: ''?
When mixing code from several different sources, you may come across this error:
This is usually because you've tried to close or set to nothing an object that hasn't been defined. To reproduce, try
this code:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.open <connection string>
' ...
objConn.close
set objConn = nothing
%>
Another possible cause is using a reserved object name as a variable name. For some reason, ASP lets you do this:
<%
dim response
%>
<%
dim response
response.redirect("http://www.foo.com/")
%>
But oddly enough, not when you do this:
<%
dim response
response.write("http://www.foo.com/")
%>
The line number in the error message should make it fairly easy for you to track down which object's name you've
messed up. My advice is to always open late, close early... close your objects as soon as you're finished with them.
But if you have sloppy code that doesn't keep track of its objects, or you are using a catch-all at the bottom of the
page, how do you prevent these errors? You can check if the object exists by using the isObject() function:
<%
if isObject(objectName) then
set objectName = nothing
end if
%>
If the object has open/close methods, you can also check their state to see if they're open, and make sure that you
close them properly. This can be done, for example, with recordset and connection objects:
<%
const adStateOpen = 1
if NOT(rs IS NOTHING) then
if rs.state = adStateOpen then rs.close
set rs = nothing
end if
%>
How can I increase the amount of connections in Workstation / Professional?
NT 4.0 Workstation, Windows 2000 Professional and XP Professional all come with an inherent limit of 10
simultaneous connections. This is because it is a business / development operating system, not a Web server. This
OS should be used for testing your ASP applications, not deploying them or hosting them in the real world. You will
800A0001
or
Persits.Upload.1 (0x800A0001)
Unspecified error
This is usually because you used request.form while also using ASPUpload. If you need to retrieve files *and*
800A0002
Check that the server name specified in your .Host property is valid, and if it is a domain name, that it can be
resolved from the web server (not your workstation). As a workaround, use the IP address instead.
800A0003
Make sure your <FORM> is using the POST method. If you are trying to prevent this error during the first load of a
form that POSTs to itself, use the .IgnoreNoPost property (see PS01040429).
800A0004
Check the server name you are using is valid and populated in the .Host property, that the SMTP service is running
(and on the default port), and make sure it allows relaying (either anonymous or only from within the network).
If the SMTP service is running on a port other than 25, you can use the .Port property to specify the correct port
number. If your SMTP server requires outgoing authentication, you can use the .username and .password properties
This is a Winsock error in the ASPEmail component, but it is rare and has been difficult for the development team to
800A0005
or
You need to allow IUSR_machineName access to your project / DLL through DCOMCNFG. See Q255502 and, more
importantly, the workaround section of Q259725.
See Article #2101 for a brief discussion of this specific error message.
or
Make sure the folder you are defining in the .Save method exists, and that IUSR_machineName has write
permissions. If you are trying to upload to a network share, you can use the .LogonUser method (see PS01032618).
Persits.MailSender.4 (0x800A0005)
Software caused connect abort.
This can happen when the mail message you are sending is larger than the SMTP server allows. Send a smaller
800A0006
or
overflow error when trying to use Cint(); use CLng() instead, which has a much higher upper bound
(2,147,483,647). If you need even more than that, use CDbl(). Beyond that, convert to a string, because you
Persits.MailSender.4 (0x800A0006)
501 5.5.4 Invalid Address
Make sure your .From address is in proper format. Maybe you left out the @ symbol, or left the .From property
blank.
Persits.MailSender.4 (0x800A0006)
503 5.5.2 Need Rcpt command.
Make sure you are using AddAddress, AddCC or AddBCC at least once, and that they contain e-mail addresses in
800A0007
This is usually caused by creating a very large array - see Article #2196 for more information.
Persits.MailSender.4 (0x800A0007)
The system cannot find the path specified.
You are using the AddAttachment method, and passed in a blank path, a client-side path, or a mapped drive letter
path. Make sure the path is valid, IUSR_machineName has read access to the file and folder, and that you use UNC
paths if you need to attach files from network shares. To make sure IUSR_machineName has access to UNC paths,
Persits.Upload.1 (0x800A0008)
Size of file <file> exceeds the maximum allowed size of <n> bytes.
You have uploaded a file that is larger than the .SetMaxSize property has been defined. Either increase the
.SetMaxSize value, upload a smaller file, or display a friendly error message (see PS01092671).
800A0009
This usually means you tried to reference an element of an array that is outside of the upper and lower bounds of
800A000A
If you trying to define an array's size dynamically after you have populated some elements in it, make sure you use
Redim Preserve. If you are changing the number of dimensions, do so BEFORE you populate any elements in the
array. If you are changing the upper bounds of individual elements, see Article #2067 for techniques for redeclaring
800A000B
<%
' ...
denom = clng(request.form("foo"))
if denom <> 0 then
quotient = num / denom
else
quotient = 0
end if
%>
800A000C
Although our audience has searched for this error message many times, we have not been able to find any useful
information about this error on the web, in Microsoft's Knoweldge Base, or in the entire archived history of
newsgroups (according to Google). If you have any information about this error code, please let us know.
800A000D
This error usually comes because you have bad syntax which leads ASP to believe you are trying to use a function as
an array, or a string as a function. Here are a few examples that will cause this error:
<%
dim foo(5)
foo(0)
' or
foo
' or
In the first case, it looks like the code is attempting to call a sub or function called "foo" - when VSBcript is expecting
the code to asign a value to the first element of the array "foo." The second example looks like an attempt to call a
sub or function called "foo", when no such function exists. The last method "borrows" the keyword "var" from
J/JavaScript. Variable declaration does not use the "var" keyword in VBScript.
800A000E
This can happen when you are trying to concatenate a large string together, and exceed the server's capacity to
hold your string in memory. To work around this, if you are writing to the screen, use multiple response.write()
statements instead of holding onto all of that string in the buffer and response.writing it in one line. It you are
writing to a text file, use multiple fs.writeline commands instead of one. You can demonstrate this error by running
Let it fly, and watch Task Manager until the error times out. In Windows 2000, memory usage for
inetinfo.exe/dllhost.exe should go through the roof (in .NET Server, watch w3wp.exe).
This error can also happen with arrays. If you are creating very large arrays because you don't know how many
elements will populate them, consider breaking the work across iterations of re-using a smaller array. Also, as soon
as you are done working with the array, empty it out by using the following command:
<%
Erase ArrayName
%>
Persits.Upload.1 (0x800A000E)
Fatal error: can't find next separator.
This is a bug in IE 5.0 and/or ASPUpload 2.0. It is fixed by ASPUpload version 2.1.0.1, or 3.0. See PS01071762 for
more details.
800A000F
As far as we can tell, this error is not in use and is reserved for "unknown" errors. If you have received this error,
please feel free to provide us details about the code that caused it, any text associated with the error code, and
what you may have done to resolve or work around the problem, and we will update this article with that
For several reasons, mostly performance and the ability to handle 500 errors more flexibly, buffering is enabled by
default in IIS 5.0, 5.1 and 6.0. In IIS 4.0, buffering was disabled by default, so you could enable it and disable it at
will (however countless people walked into the problem described in Article #2011). Now, when you create an ASP
<%
Response.Buffer = False
' ...
%>
When buffering is enabled by default, you cannot override it at page scope. To do so, you have to disable buffering
for the entire web site / application, then enable buffering in each page as required. To do this, open Internet
Services Manager, right-click the web site or application, and select properties. On the Home Directory tab, click the
Configuration button, and on the Options tab, uncheck "Enable Buffering"... but don't be surprised if the rest of your
<%
set fso = Server.CreateObject("Scripting.FileSystemObject")
for each drive in fso.Drives
cDriveName = fso.GetDriveName(drive)
set cDrive = fso.GetDrive(drive)
if cDrive.DriveType=2 then
response.Write cDriveName & " - " &_
formatnumber(cDrive.FreeSpace/1024/1024,0) &_
" MB free<br>"
end if
next
set fso = nothing
%>
If you are concerned about the space on your SQL Server machine, you can do this:
EXEC master..xp_fixedDrives
drive MB free
----- -----------
C 2435
D 7596
E 6097
F 7892
G 7393
Note that this is an undocumented extended stored procedure, and should not be used in production code (since its
behavior may change in future versions). Another alternative, for a single drive, is to parse the results of the
following statement:
This is a very trivial example of using the Response.IsClientConnected property. Basically, you have a file where you
are doing a lot of processing, and you only want to continue if the user is still connected to the ASP page. You can
test this while the page is still executing; in the following example, we have one big loop, followed by another.
Before entering the second loop, we test if the client is still connected, and then again after the second loop. This is
just to populate the text file, to demonstrate how this works. Load this page once, and let it finish. Then, hit
Ctrl+Refresh and close the browser (or hit Stop) before the page finishes executing, and examine the different lines
<%
set fso = server.createobject("Scripting.FileSystemObject")
set fs = fso.openTextFile(server.mappath("/log.txt"),8,true)
fsStuff = now & " - gone during first loop"
for i = 1 to 1000000
boo = ""
next
if response.isClientConnected then
fsStuff = now & " - gone during second loop"
for j = 1 to 1000000
hoo = ""
next
if response.isClientConnected then
fsStuff = now & " - " & i+j
end if
end if
fs.writeline fsStuff
response.write fsStuff
fs.close: set fs = nothing
set fso = nothing
%>
If you're like the rest of us, you don't want to re-invent the wheel or learn socket programming just to do a Whois or
nslookup query.
With a component:
ACIWhois
ASPWhois
ASPWhois II
C2GWhois
DesWhois
DNSQuery
DynuWhois
EasyWhois
ECS Whois
etiveWHOIS
HexTcpQuery
IP*Works
WebWhois
WhoisDLL
And without a component:
In Visual InterDev 1.0, choose Options from the Tools menu, and then select the HTML tab. In the Default scripting
In Visual InterDev 6.0, right-click the name of the project in the Project Explorer, and then choose Properties.
Choose the Editor Defaults tab, and then under Default script language, select a new default.
In Visual InterDev.NET, there are two methods (though neither work for me yet):
(a) with an ASP file open, click View, Property Pages, and on the General tab there is a dropdown at the bottom for
server-side scripting language. When I attempt to change this to JScript, InterDev shuts down with a "Microsoft
Development Environment has encountered a problem and needs to close. We are sorry for the inconvenience." blah
blah blah...
(b) with an ASP file open, click View, Properties Window, and in that environment you'll see one of the properties is
'defaultServerScript' with a dropdown next to it. When I attempt to change this to JScript, I get the error "Invalid
Property Value: Value null was found where an instance of an object was required."
I'm running Beta 2 on XP Pro, and the RC on XP Advanced Server 3541. YMMV.
How do I pad digits with leading zeros?
Here is one way. Basically, it compares the number of desired digits with the length of the number being padded,
<%
Function PadDigits(n, totalDigits)
if totalDigits > len(n) then
PadDigits = String(totalDigits-len(n),"0") & n
else
PadDigits = n
end if
End Function
%>
And in JavaScript:
Sample usage:
<%
Response.Write(PadDigits(46,8)) ' returns 00000046
Response.Write(PadDigits(4,5)) ' returns 00004
Response.Write(PadDigits(32,6)) ' returns 000032
Response.Write(PadDigits(22,1)) ' returns 22
%>
And here is a technique that can be useful for sorting on a varchar column that might contain numbers (you can't
To see how this can change a query, consider the following example (SQL Server):
SET NOCOUNT ON
CREATE TABLE #foo(bar VARCHAR(5))
INSERT #foo VALUES('1')
INSERT #foo VALUES('2')
INSERT #foo VALUES('9')
INSERT #foo VALUES('10')
INSERT #foo VALUES('20')
Depending on your configuration, there can be many causes for application variables to suddenly become
unpopulated.
■ re-saving global.asa
Now, if you have code in ASP pages that rely on these application variables being populated, you should have a
contingency plan. When confronted with this situation, I used a separate ASP page which could be hit directly to
regenerate the application variables from scratch. This file was also included in global.asa (to reduce redundancy
and ensure consistency), in the application_onStart event, as described in Article #2144. And to allow applications
to redirect to this paeg and then get sent back to the referer, it itself is also included in another ASP page.
Basically, it goes something like this. In your ASP page that *depends* on the application variables, you have this
logic:
<%
if Application("StringVariable") = "" then
response.redirect("/globalInclude.asp")
else
StringVariable = application("StringVariable")
end if
' ... continue page
%>
In global.asa, of course, you would only include global.asp in the application_onStart() method, since otherwise it
The 8000ffff error is actually quite common, and can have a variety of causes / resolutions. Outside of the trappable
Make sure to use the latest version of MDAC (http://www.microsoft.com/data/). If you are using SQL Server, see
If you are using a DSN or otherwise ODBC-dependent connection string, use a DSN-less connection string (and in the
case of Access, use JET/OLE-DB instead of the generic Access driver). See Article #2126 for sample connection
strings.
Do not store connection or recordset objects in the session or application variables. See Article #2053 for more
information.
If you are using a server-side cursor with an ADODB.Recordset and a transaction, consider using a client-side cursor
(adUseClient) or using a default recordset against the connection object (see Q187942). Conversely, this error might
be caused by issuing an .update or .AddNew call against a default, forward-only recordset - in which case, you should
If you are retrieving OLE, BLOB, TEXT or IMAGE columns, make sure they are left out of your resultset when they are
NULL. You can do this in the WHERE clause by stating:
For SQL Server, if you are connecting using a SQL Server UID/PWD, make sure the database is set up for Mixed
Mode authentication, as opposed to just Windows authentication. Also make sure that the SQL Server service is
started, and that you are connecting to a valid instance name (if connecting to an instance other than the default).
If you are using Oracle oo4o (OracleInProcServer.XOraSession), you might see this error also:
Make sure the oo4o libraries are installed on the server, and that you are connecting to the server correctly
If you are using Commerce Server and/or BizDesk, you might see this error:
or
This can happen if you've changed the password of the user who installed commerce server after it's been installed -
make sure under Component Services / Security that the components are set to the interactive user, not a specific
user account.
If you see an 8002802b error on any other COM object, make sure you are using set objectName =
Server.CreateObject("Valid.ProgID"). If you are using an <OBJECT Progid=> tag, this error can be more likely to
occur. Also, make sure you are not setting a COM object to a session variable unless it has been explicitly optimized
for doing so (see Article #2053). Make sure that the DLL(s) in question have been registered on the system ... try re-
registering them with regsvr32 <path>\file.dll. Next, check the permissions on the DLL - make sure 'everyone' (if
using Windows auth) or IUSR_<machineName> (if using anonymous access) has read and execute permissions. A
temporary workaround would be to set the IIS process isolation to low until you find the real solution.
If you are using Access, you might have seen one of the following:
or
This is usually because you created an ADODB.Recordset, grabbed a query that JOINed two or more tables, and then
attempted to perform an AddNew or Update through the recordset object. To resolve this issue, use an INSERT or
UPDATE statement directly against the connection object, and if using SQL Server, use a stored procedure call.
If you are using JMail, this is the error code for just about every error there is. I'm going to list out a few of them,
Make sure the SMTP server is valid, and that it accepts mail relayed from your domain and/or address. You might
need to use a component that supports outgoing SMTP authentication, if your SMTP server is blocking relays. <--
FWIW, this is a good thing... as is anything that can reduce the number of SPAMs I get each day.
Cannot open file
Make sure that if you are using the AddAttachment method, you pass it a valid path and filename.
This should be placed at the top of any page that is accessible by http:// but that you want accessed only through
https://
<%
if Request.ServerVariables("HTTPS") = "off" then
srvname = Request.ServerVariables("SERVER_NAME")
scrname = Request.ServerVariables("SCRIPT_NAME")
response.redirect("https://" & srvname & scrname)
end if
%>
Why does IsNumeric() return true for some strings that contain characters?
VBScript's IsNumeric() function does not actually determine whether an expression contains only numeric digits; it
determines whether the expression could be evaluated as a number. In the following cases, isNumeric returns true:
<%
response.write isNumeric("3e4") & "<p>"
response.write isNumeric("3d4") & "<p>"
response.write isNumeric("&H05") & "<p>"
response.write isNumeric("$45,327.06") & "<p>"
%>
To see why, you should notice that d was once used to denote double precision, e is used to express scientific
notation, that &H is used to express a hex number, and of course $ , and . are used to express currency (other
currency symbols in other regional settings will be subject to the same problem). You will also see this problem with
numbers beginning with & (e.g. Octal numbers), numbers with parentheses (denoting negative), and numbers with
<%
response.write 1 * 3e4 & "<p>"
response.write 1 * clng("3d4") & "<p>"
response.write 1 * &H05 & "<p>"
response.write 1 * cdbl("$45,327.06") & "<p>"
%>
(While the use of the d and e characters produce the same result, it should be noted that d requires explicit
So, to test if these 'numbers' really are numeric, one workaround is to write your own isNumeric() function that
expression and tests each character (digits 0 through 9 have ASC values between 48 and 57), e.g.:
<%
function isReallyNumeric(str)
isReallyNumeric = true
for i = 1 to len(str)
d = mid(str, i, 1)
if asc(d) < 48 OR asc(d) > 57 then
isReallyNumeric = false
exit for
end if
next
end function
You'll have to adjust if you want to accept certain characters as 'numeric', e.g. commas, decimal points, + and -
signs, etc.
Note that JScript/JavaScript's isNaN() function has similar limitations; it also sees 'e' as scientific notation, for
example.
T-SQL also has this problem (where D and E are interpreted as numbers), for example:
SELECT ISNUMERIC('0E30')
To get around this, you can wrap your own isnumeric function that uses the following logic:
CREATE FUNCTION isReallyNumeric
(
@num VARCHAR(64)
)
RETURNS BIT
BEGIN
RETURN CASE
WHEN PATINDEX('%[^0-9]%', @num) = 0 THEN
1
ELSE
0
END
END
GO
SELECT isReallyNumeric('0002')
SELECT isReallyNumeric('00E2')
SELECT isReallyNumeric('00F2')
Of course you could use the same kind of logic within T-SQL, e.g. in a WHERE clause or CASE expression, so you
There are several variations of the 80020005 error. Here are a few of them:
Server object error 'ASP 0193 : 80020005' (or 'ASP 0193 : 80020009')
OnStartPage Failed
/<file>.asp, line <line>
An error occurred in the OnStartPage method of an external object.
This is typically an error coming from a custom COM object / component. One way around it is to re-write the object
using ObjectContext, instead of onStartPage / onEndPage (which are deprecated as of the release of Windows
2000).
This can happen if you try to set a date column to a value that is not in proper date format, e.g. "UPDATE tableName
SET dateColumn='31/31/2002'"... it can also happen when using a command object and named constants (e.g.
adDate and adLongVarChar); I will always recommend using a straight EXEC statement against a connection object,
rather than go through the hassle and rigorous code required by an ADODB.Command object.
This can happen if you try to response.write data coming out of a blob / ole / image column, or response.binarywrite
non-binary data. To get around the blob scenario, you can upgrade to the latest MDAC
(http://www.microsoft.com/data/), which allows you to avoid the GetChunk / AppendChunk methods. See Q258038
for (admittedly VB-based) samples of avoiding GetChunk and AppendChunk, and Q276488 for sample code using
ASPImage when a JPG file was over 575kb (though I don't know why an image that big would ever be on the web,
much less in JPG format at all). One workaround would be to use a different object (maybe ADODB.Stream) to
Finally, see Article #2099 if neither of the above situations applies to you.
How do I highlight words in a string?
<%
' define the string to replace:
strR = "ho"
One problem with this solution is that it is case sensitive. So, if you were to have multiple upper/lowercase versions
of the target word, it would only replace those that exactly match the case of your variable. The following example
takes a slightly different approach, by looping through the string and inserting the highlighting tags (in this case
<%
' define the string to replace:
strR = "ho"
loop
This question has been asked frequently (and variants of 'decode' keep showing up in this site's search logs). While
the Server object provides a Server.URLEncode() method, they didn't bother providing a Server.URLDecode() or
Server.URLUnEncode() method. I assume they didn't think it would ever be needed, but they were wrong... I've
been in a few situations where this function would have been very handy. Here are both URLEncode and URLDecode
In VBScript:
<%
Function URLDecode(str)
str = Replace(str, "+", " ")
For i = 1 To Len(str)
sT = Mid(str, i, 1)
If sT = "%" Then
If i+2 < Len(str) Then
sR = sR & _
Chr(CLng("&H" & Mid(str, i+1, 2)))
i = i+2
End If
Else
sR = sR & sT
End If
Next
URLDecode = sR
End Function
Function URLEncode(str)
URLEncode = Server.URLEncode(str)
End Function
function URLDecode(str)
{
return unescape(str);
}
function URLEncode(str)
{
str = escape(str);
while (str.indexOf("/")!=-1)
{
str = str.replace("/","%2F");
}
return str;
}
</script>
How do I make hyperlinks out of plain text URLs and e-mail addresses?
Many times you will get data out of a database and expect the browser (like e-mail) to automatically make http://
URLs and mailto: addresses clickable. The browser doesn't work that way, but it is fairly trivial to work around when
the entire column is a hyperlink (see Article #2188). However, it is not so simple when the items you need clickable
are buried in the middle of other text. The following example shows how to highlight a body of text's *valid* links
(URLs, \share\s, and e-mail addresses) and should also prevent munging up links which already have correct HTML
formatting (if you find a scenario that falls through the validation, please let us know).
<%
' let's make a big fictitious paragraph, and pretend
' it came out of a database or other external source
words = split(trim(oldParagraph))
words(i) = fixLink(lcase(trim(words(i))))
next
newParagraph = join(words)
for i = 0 to ubound(words)
words(i) = fixLink(lcase(trim(words(i))))
next
function fixLink(w)
if _
(left(w,4) = "http" and len(w) > 11 and instr(w,"://") > 0) _
or _
(left(w,2) = "\" and len(w) > 3 and instrRev(w,"\") > 2) then
elseif _
(instr(w,"@") > 0 and len(w) > 4 and _
instr(w,"@") = instrRev(w,"@") and _
instr(w,"href=mailto:") <= 0) then
This was a fairly recent question on the newsgroups, and I initially set out to solve the problem with a SQL Server
stored procedure. However, I thought it would be more useful to provide a VBScript example, then it wouldn't
matter if the data came out of Access, Excel, SQL Server, MySQL, or a text file. This finds URLs, e-mail addresses
and file share links that are preceded by a space or carriage return. If they are embedded in HTML, you will have to
error '80020009'
Exception occured
or
This can often happen if you reference a recordset object that was created on a different page, or in session scope.
If the former, you will need to re-query the database for this recordset; if the latter, you should consider another
plan for implementation... you should never store a recordset object in session scope (see Aticle #2053).
or
No current record.
See Article #2246 for more information about this error message.
Try to avoid using DAO, if possible. DAO isn't highly recommended for use from ASP, even through a DLL.
This could indicate an underlying problem with SQL Server, but is often an issue with the receiving script. Make sure
you are connecting with OLEDB (see Article #2126), have installed the latest MDAC
(http://www.microsoft.com/data/), and have the latest Service Pack for SQL Server (see Article #2151). For more
or
or
or
No Recipients.
First off, use CDO.Message instead (see Article #2026, under 'Windows XP', for an explanation).
Make sure any variables you are passing between ASP <-> component are declared as variants within the DLL...
What could cause all of my session or application variables to disappear?
Typically, all active sessions will be terminated if IIS is reset, the application is unloaded / re-created within Internet
In addition, this can happen when global.asa is changed. Not only when a human physically changes the file, but it
has been known to be triggered by FrontPage Server Extensions, Index Server, several varieties of anti-virus
If you are having trouble tracking down the event that caused the problem (e.g. in the Windows Event logs and IIS'
own log files), check the last modified date/time of global.asa, and see if it was changed at around that time. This
should at least give you a bit of a direction in finding out what happened.
Here is a function that will handle this conversion. It will work for normal names and concatenations, like O'Brien
and Moseley-Williams. It will not, however, work for joined names like VanWyck.
<%
Function ProperCase(strIn)
strOut = ""
boolUp = True
For i = 1 To Len(strIn)
c = Mid(strIn, i, 1)
if c = " " or c = "'" or c = "-" then
strOut = strOut & c
boolUp = True
Else
If boolUp Then
tc = Ucase(c)
Else
tc = LCase(c)
End If
strOut = strOut & tc
boolUp = False
End If
Next
ProperCase = strOut
End Function
%>
Sample usage:
<%
Response.Write(ProperCase("o'brien")) ' returns O'Brien
Response.Write(ProperCase("moseley-williAMS")) ' returns Moseley-Williams
Response.Write(ProperCase("VanWyck")) ' returns Vanwyck
%>
Why am I having problems installing Visual Studio.NET RTM?
Rather than outline all of the potential hiccups, I'll point you to a verbose article about one man's experience with
If that doesn't answer your question(s), you can probably go to the vstudio setup group for help:
microsoft.public.vstudio.setup
FWIW, I installed the Enterprise Architect version on three machines. One was XP Pro, one was .Net Enterprise
Server 3590, and the last was .Net Enterprise Server 3604. All had beta versions of .Net installed, and even though
I did not follow the advice of the readme file to the letter, I haven't had any problems on any of the machines.
Why do I get 'Object doesn't support this property or method' errors?
Usually, this is because you used a property or method that doesn't exist, or doesn't exist in the version of the
object you're using. For example, if you have MSXML 3.0 installed, and you try this code:
<%
Set xmlhttp = Server.CreateObject("MSXML2.ServerXMLHTTP")
xmlhttp.setRequestHeader "User-Agent","foo"
%>
Or if you are running IIS 4.0, and you try this code:
<%
Server.Transfer "foo.asp"
%>
And finally, if you tried to assign a variable to your own 'version' of an intrinsic object, e.g.:
<%
response = "foo"
%>
Can I mimic VBScript's trim, ltrim, rtrim in server-side JScript / JavaScript?
Here is a sample that uses regular expressions to replicate the functionality of VBScript's *trim functions in
JavaScript:
function ltrim(str)
{
return str.replace(/^[ ]+/, '');
}
function rtrim(str)
{
return str.replace(/[ ]+$/, '');
}
function trim(str)
{
return ltrim(rtrim(str));
}
</script>
While the HTML might look like it trimmed too much in the first two cases, if you view the *source* of the output,
Subscript out of range usually means you tried to access an element of an array that was either greater than its
<%
str = "hello,there,you"
strs = split(str,",")
response.write strs(12)
%>
Looking at the code, it's easy to see that the highest element of the strs() array would be 2 (since split() returns a 0-
based array). To prevent the error from happening, always consult the ubound of an array before blindly trying to
<%
str = "hello,there,you"
strs = split(str,",")
if ubound(strs) >= 12 then
response.write strs(12)
else
response.write "Sorry, there is no 12th element."
end if
%>
Why do I get 'A script block cannot be placed inside another script block' errors?
You might have seen this error when using ASP to generate client-side script:
Or this:
One way to alleviate this is to use the @language directive and use <%%> delimiters throughout:
Or this:
<% @language="jscript" %>
<%
Response.Write("<script>alert('foo');</script>");
%>
If you are using both languages in the same page, you may have to use this alternative workaround, where you
Or this:
You will likely need to call these pages as a user other than the anonymous IUSR. This is because extra privileges
are required for reading the registry and/or WMI. Here is a method for obtaining the information from the registry:
<%
set oShell = server.createobject("WScript.Shell")
stn = "HKEY_LOCAL_MACHINE\System\CurrentControlSet\" & _
"Control\TimeZoneInformation\StandardName"
The following WMI script should work on Windows 2000 and up -- not sure about NT 4.0. Note that you may have to
parse the output, as you probably get more than you want...
<%
Set Locator = Server.CreateObject("WbemScripting.SWbemLocator")
Set NameSpace = Locator.ConnectServer()
WQLQuery = "SELECT caption,daylightBias FROM Win32_TimeZone"
Set TZCollection = NameSpace.ExecQuery(WQLQuery, "WQL", 48)
For Each Item In TZCollection
response.write Item.caption & " (if daylight time, add " & _
Item.daylightBias/60 & " hours to the abs(offset).)"
next
%>
Instead of calling this dynamically, you could use a scheduled VBS file to run every day, or every week, to check the
current timezone and store this information in a flat file or a database (after all, how often does the time zone
change?). That way you only have the 'hit' to the registry / WMI so often, and you don't have to worry about the
permissions of IUSR.
How do I prevent 'Invalid use of Null' errors?
When working with database or array values, you may have seen this:
<%
x = replace(rs("x"), something, somethingElse)
%>
Do this:
<%
rsx = rs("x")
if len(rsx) = 0 then
x = ""
else
x = replace(rsx, something, somethingElse)
end if
%>
For database values, try preventing NULLs from coming out of the database in the first place. See Article #2073 and
Unlike VB, VBScript does not support the OPTIONAL keyword, so you cannot set up a function to conditionally accept
<%
function test(foobar)
foos = split(foobar,",")
response.write foos(0)
if ubound(foos)>0 then response.write foos(1)
end function
test("1,2")
test("1")
%>
2. pass in empty strings or other token values that tell the sub or function to ignore that value, e.g.:
<%
function test(foo,bar)
response.write foo
if bar <> "" then response.write bar
end function
test("1","2")
test("1","")
%>
JScript, on the other hand, allows you to pass optional arguments, as demonstrated in the following code snippet:
<script language=JScript RUNAT=SERVER>
function test(foo,bar)
{
Response.Write(foo);
Response.Write(bar);
}
test('1','2');
test('1');
</script>
How do I solve 'The Requested Resource is in Use' errors?
Check your code, make sure all objects are closed and set to nothing. This includes any command or ADOX objects
using an Active.Connection property, make sure this property is set to nothing as well.
If you are using thisPage.NavigateURL method, use response.redirect or server.transfer instead - avoid the thisPage
object.
Install the latest service packs and security releases (see Article #2151), and make sure you have the most recent
If you have 'Cache ASP applications' checked, try unchecking it temporarily (see Q182059 for more information).
Try changing the address space and/or isolation settings for the application / site in question.
In addition to these suggestions, if you are getting Event ID 36 in your event log, see Article #2226.
If you are getting 'The RPC Server is Unavailable', make sure you are not accessing a machine with an underscore in
the name, and otherwise follow the suggestions in Article #2147 -- most importantly to restore the IWAM account.
Why do I get ASP 0113 / Script timed out errors?
If you have some longer pages, usually with database activity, you may have come across this error:
A 'bandaid' is to increase the value for Server.ScriptTimeout (see Article #2066). The default value is 90 seconds, so
something in your script must be taking longer than 90 seconds. (I do not recommend doing this for your entire site,
as the error message suggests, as this may mask other problems for you.)
Now that you've identified a page that takes longer than 90 seconds to run, you need to work on it, rather than
simply relying on the bandaid. If you have nested recordsets, consider a JOIN. If your SQL queries take too long to
run, consider indexes, using GetRows(), and moving the queries themselves to stored procedures. If you are using
consider using the connection object by itself. If your page is 500 lines long, consider optimizing the code and
perhaps spreading the work over multiple pages. If you post your code to microsoft.public.inetserver.asp.general,
there will be people there who will help you optimize your code.
Why do I get 80010108 errors?
There are a variety of error messages that go along with the 80010108 error code.
site. See Article #2173 for sample code that uses XMLHTTP (MSXML) instead - this code is much more flexible, and
See Q172210 if you are using a VB component that accesses the NT Event Log.
How do I make sure an entered string contains only valid characters?
Often you are relying on users to enter valid text for element names, file names, folders, etc. There is no
"isValidFolderName()" function attached to the FileSystemObject, and even if there were, it would be useless
because this status changes with each new Windows version (for example, I can currently name a folder with a
The following technique will show you how you can easily adjust one line of code to prevent any of a list of
<%
' here is the element name you want to check
' this can come from request.form or querystring
elementName = "foobar"
invalcount = 0
if instr(elementName,chr(34))>0 then
invalcount = 1
else
' loop through, making sure no characters
' are in the 'reserved characters' list
for i = 1 to len(invalidList)
if instr(elementName,mid(invalidList,i,1))>0 then
invalcount = 1
exit for
end if
next
end if
Here are a couple of snippets that will help you convert a hexadecimal number to decimal, and vice-versa.
VBScript has a built-in dec->hex function called hex. To do the opposite, you use a concatenation of "&h" and the
hex value.
<%
Response.Write "<hr>Dec -> Hex, VBScript<p>"
DecVal = 16777215
Response.Write Hex(DecVal)
And here it is in JScript, which uses toString (with a radix of 16) to get hex from decimal, and parseInt which has
</script>
This task is a little more daunting in T-SQL, and I hope to have a tested and working example available shortly.
How do I print the first n characters of a large block of text?
You can print, say, the left 200 characters or so of a block of text as follows:
<%
bigtext = "Hello, this is a big paragraph of text, used" & _
" solely for demonstration purposes. The code" & _
" will return the first 200 characters or so" & _
" to simulate some kind of ""excerpt"" where" & _
" you would later click for more info..."
response.write excerpt
%>
If you want to add a space before the "...", either take out the -1, or add a leading space, e.g. " ..."
Having said that, if this information is coming from a database, you might want to adjust your query to be more
efficient; for example, only returning the first 200 or so characters across the wire (this is especially true for TEXT
columns, which often contain far more data than you would ever need to show in an excerpt... so why return it?).
SQL Server doesn't support an InstrRev-like function, so the query is a little more complicated than it might
otherwise be:
SELECT
excerpt = SUBSTRING
(
column,
1,
200-CHARINDEX
(
' ',
REVERSE
(
SUBSTRING
(
column,
1,
200
)
)
)
)+'...'
FROM
table
In Access, the query is a LOT more like the VBScript version, since Access supports VBA:
SELECT
excerpt = LEFT
(
column,
INSTRREV
(
LEFT
(
column,
200
),
" "
)-1
) & "..."
FROM
table
However, you might find that this query doesn't work from ADO (see Article #2394). If this is the case, then just
take the left 250 characters or so, and then apply the same logic in your ASP code as above. Please let us know if
This error is usually due to permissions problems or, in rare instances, corruption of the metabase. Here are some
possible solutions:
■ try adding IWAM_MachineName to the web site operators, and giving this account full permissions (using
regedt32.exe) to
HKEY_CURRENT_USER\Software\Microsoft\Cryptography\UserKeys
...and delete the subkey 'MS IIS DCOM Client', then open the MMC console for IIS (which will re-create the
subkey)
■ if all else fails, try reinstalling IIS - this will rebuild your metabase, but will require you to configure your
This is usually due to using a With statement on a server with an older version of the script engines. Make sure you
have the most recent script engines on your server; specifically, With requires version 5.0. See Article #2151 for
To fix, either double-up the quotes, or use single quotes (see Article #2065 for more information):
or
#2067.
This is caused by using a variable name that starts with an @ symbol. This is not a problem in VBScript, as it does
not try to interpret the @ symbol as a conditional statement. Here is an example that reproduces the problem:
However we have been unable to find any documentation on enabling conditional compilation. :-(
<%
Response.Write("foo"$)
%>
Obviously, that dollar sign doesn't belong there. See Article #2376 for a more detailed description of this error
<%
set you = nothing
set me = nothing
%>
This is often due to leaving out a do while not rs.eof, or finishing a while struct with a loop. It can also be caused by
<%
for i = 1 to 10
' some logic
exit do
next
or
do while i < 10
' some logic
exit for
loop
%>
or
do while i < 10
' some logic
exit do
loop
%>
If you are using a while...wend struct, you might notice that this doesn't work:
<%
while i < 10
' some logic
exit while
wend
%>
Consider using a do while...loop struct instead (which is preferred anyway), or taking a different approach:
<%
while i < 10
' some logic
i = 10
wend
%>
This can happen if you use the same variable name in a nested loop, e.g.
<%
for i = 1 to 5
for i = 1 to 10
' do something
next
next
%>
This means you defined (using the dim keyword) a variable or array twice. This could be from including the same file
twice, or simply having two different dim statements for the same variable name. You will have to search the file for
the previous occurence, as the error message will point at the later line.
<%
If SomeCondition Then
DoSomething
End if
%>
Which is easier to read than the correct version of shorthand, putting it all on one line:
<%
If SomeCondition Then DoSomething
%>
Notice that when you put the entire conditional on one line, the End if no longer belongs.
<%
CONST strVarName = "foo"
CONST intVarName = 4
%>
If you are re-assigning a variable's value to another value, use a regular variable instead of a CONST declaration.
How do I parse the domain name out of a URL?
This has come up several times. People have complex URLs with querystring parameters and different protocols
(e.g. http:// and https://) stored in a database, flat file, or user-entered, and they want to return just the domain
<%
Function ParseDomainFromURL(url)
urlParts = split(url,"/")
ParseDomainFromURL = urlParts(2)
End Function
complexURL = "http://foo.com/whatever.asp?foo=1&bar=2"
domain = parseDomainFromURL(complexURL)
response.write "Domain = <b>" & domain & "</b>"
%>
This function assumes that you are going to pass a valid URL, with a // preceding the domain name. You can check
<%
Function ParseDomainFromURL(url)
if instr(url, "//") > 0 then
urlParts = split(url,"/")
ParseDomainFromURL = urlParts(2)
else
ParseDomainFromURL = "Invalid URL"
end if
End Function
%>
Note that we don't use a split on the double // character sequence, because splitting on single / allows us to isolate
just the domain name in one step, and also allows us to avoid having to special-case the scenario where the URL
This usually occurs with upload components, and can happen when you upload files larger than they can support.
If you are writing your own upload script / component, consider using BinaryRead in 'chunks' -- this way, you can
write out to your own buffer file as you receive each chunk (so you don't overwhelm the server with a huge in-
memory file), and before you read each segment, check if the client is still there by testing
Response.isClientConnected.
Some components seem to have no upper bound on file size, except the server's inability to hold the file in the
buffer, or the browser's inability to run a script for as long as it would take to transfer). If you believe you are
exceeding IIS' ability to buffer files, you can try to increase this limit (see Q260694. If that doesn't work, then try a
different component. ASPUpload, for example, has no problems with files much larger than 2 MB (though according
to PS01041741, you'll want to make sure you're using version 3.0 or higher).
If you are using Sybase, see Q198943 for information on resolving 80070057 issues.
If you are using Informix, make sure you have the latest drivers. You can review IBM's documentation on how to get
these drivers installed (let us know if you can find a download, we couldn't!), or you can purchase from DataDirect.
Response object error 'ASP 0101 : 80020008'
Unexpected error
/<file>, line <line>
The function returned |.
If you are using Oracle, the error above probably means the Response object is trying to deal with a datatype that it
doesn't know how to handle. Verify that your query works outside of an ASP environment, and check the datatype of
each column that is coming back in the resultset. A way to quickly narrow down the column(s) causing the problem
is to alter your SELECT query to only select one column at a time... keep changing this column until you reproduce
the error (and we already know you're not using SELECT *, right?).
Why do I get 80070057 errors?
There are several variations of the 80070057 error. Here are the ones we've read about or experienced:
This can happen when you use a named constant for FileSystemObject values, e.g.
<%
Set objFSO = Server.CreateObject("Scripting.FileSystemObject")
Set objFILE = objFSO.OpenTextFile("c:\boot.ini", ForReading)
%>
Since VBScript (unlike VB) doesn't know what ForReading is, the FSO object doesn't know what to do, so it returns
an illogical error message. Here are the constants you will want to use in the OpenTextFile method:
ForReading 1
ForWriting 2
ForAppending 8
This can happen if you are creating ADSI code and you forget to include a proper value. For example, leaving out
This can happen if you use ad* constants, like adOpenSchema, without having ADOVBS.INC included. See Article
<%
set typ = server.createobject("Scriptlet.TypeLib")
Response.Write typ.guid & "foo"
set typ = nothing
%>
Notice that 'foo' is nowhere to be found on the page. For some reason, the GUID string comes with two extra
characters, and they get 'swallowed up' by the response.write call. I believe this is a bug in either the Win32
CoCreateGUID API, or how the scriptlet type library implements that call. I have sent in a report to Microsoft about
this issue; in the meantime, there are at least two workarounds. One is to manually strip off the offending
characters, and the other is to Server.HTMLEncode the string (which essentially does the same thing).
<%
set typ = server.createobject("Scriptlet.TypeLib")
guidStr = cstr(typ.GUID)
Note that using CSTR and/or assigning the string to a local variable does nothing, on its own, to alleviate the
problem. Also, this isn't solely an ASP issue... the behavior occurs in ASP.NET as well.
How do I parse the file name out of a path or URL?
<%
path = "C:\inetpub\wwwroot\default.asp"
parts = split(path,"\")
response.write parts(ubound(parts))
%>
<%
path = "http://localhost/default.asp"
parts = split(path,"/")
response.write parts(ubound(parts))
%>
Keep in mind that if the path or URL doesn't have a file attached to it, you will get incorrect results. However, if the
path is ONLY a file name, it will work fine (in other words, your file name doesn't HAVE to be part of a path or URL).
How do I make sure my servers have the same time?
Often it is a concern that the system clocks on your web server(s) and/or database server(s) might go out of sync
after varying amounts of time. If your servers are not in sync, it might be difficult to analyze log traffic, errors, and
Windows has been known to lose clock ticks based on how heavily the server is being bombarded by CPU-intensive
tasks. The best way to ensure that your servers keep consistent time is to use an authoritative time server, such as
tock.usno.navy.mil. For information on setting up your servers to synchronize with a military clock, see Q216734
This error is encountered quite often by VB programmers venturing into VBScript. In VB, you can say this:
Dim i As Integer
For i = 1 to 10
Debug.Print i
Next i
In VBScript in an ASP page, on the other hand, both the Dim line and the Next line will cause the following error:
To correct:
Make sure you only Dim variables in ASP, without using AS. VBScript only supports variants, so there is no need to
Make sure you just use 'Next' instead of 'Next i'... VBScript does not need to keep track of the control variable
(though my preference would be for it to just ignore such a statement, so that VB and VBScript would be that much
myString = ""stuff"
This will cause the error as well, since there are too many quotes.
Make sure you don't mix JavaScript syntax into a VBScript page, e.g.:
<script language=vbscript runat=server>
var x = "1"
</script>
(In newer versions of ASP, this actually produces a '0x800A000D Type Mismatch' error.)
Basically, look at the line of code the ASP error points to, and try to figure out if there's any syntax on that line that
you copied from another environment (e.g. VB), from another machine with possibly a newer version of ASP, or
from memory. Chances are, you copied or wrote code that won't work in your environment, or has a syntax error of
some kind.
How do I convert old IDC / HTX pages to ASP?
Microsoft produces a tool for this, which can be found at the following URL:
http://www.microsoft.com/ntserver/nts/downloads/archive/IISIDCHTXtoASP/default.asp
Make sure you read the read me page before diving in!
Why do I get 80029c84 errors?
This is not a common error, but can be caused by creating #include files that #include each other (either directly or
in a more round-about way). Try to isolate / eliminate your #include files one by one from the calling page, or copy
all of the code from your include files into a single document to try and narrow down where this kind of situation
might be coming from. If the logic in each #include file is self-enclosed, you can try to hit each one in the browser
individually, and see if you can quickly find out which one will cause this error.
Why do I get 80070034 / 80070035 errors?
If you are connecting to a network server, make sure your NetBios name is 'clean' - e.g. no periods or underscores.
Make sure IUSR_machineName has access to the share / ADSI object you are trying to access. As a test, change the
web server's anonymous account to a domain user with the ability to log on interactively to the machine hosting the
share or object.
If you are using Site Server, and trying to connect to Novell shares, make sure you have Gateway Services for
If you are trying to connect to a server's printers folder using ADSI, and are connecting to the server by name, try
connecting by IP address instead, or by only using the "true" host name of the computer (e.g. don't use an alias in
your HOSTS file). See Q252416 for more information. You can also employ the following workaround:
3. add an additional OR clause for each alias for that machine, e.g.
If you are attempting to use secure authentication while connecting via ADSI's OpenDSObject, try temporarily
This can happen when you try to response write an object, e.g.
<%
' ...
set rs = conn.execute("SELECT column FROM table")
response.write(rs)
%>
Obviously, you'll want to loop through the resultset and response.write each element within it; you can't
This can happen if you create an object, and then assign it to a session variable without using the SET keyword,
e.g.
<%
set objDict = Server.CreateObject("Scripting.Dictionary")
session("objDict") = objDict
%>
To resolve, use the SET keyword in the assignment of the object (but you shouldn't be storing objects in the session
This can happen in a custom COM object when using the onStartPage method, which has been deprecated in favor
of ObjectControl_Activate.
Why do I get 'Invalid Default Script Language' errors?
or
or
First, make sure that the page you're trying to load doesn't have a typo, e.g. <% @language=VBSkirt %>.
You can start by the helpful advice in Q296626. Make sure your default scripting language is set to a valid option;
most commonly, this is VBScript, but could be JScript or other languages. If you are going to change the default
scripting language, make sure *everyone* involved with the server knows this change is going to take place!
If that doesn't help, you'll want to download the latest scripting engine from Microsoft, at
msdn.microsoft.com/scripting/.
Why do I get 800A01F4 errors?
This usually means you created a variable somewhere in your script, but did not define it with a dim statement. Most
commonly, this encompasses the ADO constants, such as adOpenForwardOnly and adLockOptimistic. This involves
forgetting to include ADOVBS.inc or an alternative, which you can read about in Article #2112. If you don't define
those constants and you remove Option Explicit, you will get the following error (as described in Article #2102):
If you are using ThisPage from Visual InterDev's PageObject, see Q190938 for information on resolving the issue
This error can happen when setting a barebones custom COM object to session or application scope. You shouldn't
be storing any objects in session or application scope, unless they are explicitly marked as safe for doing so. See <a
This can also happen when you try and work with an object when you should be working with one of its properties,
<%
response.write response
%>
There weren't as many error codes in this range as I had initially thought. Please let us know if you find others.
This usually happens in global.asa, and indicates that the scripting engines have become corrupt. See Article #2151
or
or
See Q276011. If you are trying to access a file on a network drive, see Article #2168.
or
This usually means you tried opening or creating a file with a named constant, like ForAppending, when you should
have used a constant. The following chart shows the possible values:
ForReading 1
ForWriting 2
ForAppending 8
Microsoft VBScript runtime (0x800A003a)
File already exists
or
Sounds like you used Scripting.FileSystemObject to move or copy a file to a location, however there is already a file
If you cut and paste code from other sources (e.g. web sites, other editors, etc) you often bring along characters
that don't show up in Notepad but are, nonetheless, present -- or do appear as non-prinatable characters, that look
like little squares. If you're looking at the line in question and it isn't simply an unclosed string or a premature
carriage return, try deleting the line(s) altogether and re-typing them by hand. This should eliminate the possibility
To work around either error, use a virtual include, with relative references from the root - rather than the current
directory, e.g.:
<!--#include virtual=/folder/file.asp-->
As far as the disallowed parent path goes, there is a security measure in IIS, designed mainly for multiple websites
(e.g. at an ISP). The concept is to prevent ASP pages in site A from including ASP pages from site B. This measure is
Another workaround is to enable parent paths. You can find this setting in the IIS MMC, right-click the web site in
question, select properties, on the Home Directory tab, click configuration, and move to the options tab:
However this is the least preferred method. See Q226474 and Q184717 for details.
Why do I get 800A03EC errors?
This usually means you left an invalid character at the end of a line, e.g.:
There are a few other errors, typically involving Microsoft Office or custom / 3rd party objects.
This typically means a permissions problem ... make sure IUSR_MachineName has write permissions on the target
folder.
Here are some KB articles that might help with this error:
And if you really want to automate Office from ASP, you should read Q257757.
Why do I get 800A03F6 errors?
If you are used to programming in JScript or T-SQL, you've probably seen this error once you've picked up
VBScript:
<%
if foo then
response.write "foo"
else if bar then
response.write "bar"
end if
%>
If you are using an else if construct, keep in mind that it is one word. So the above should be:
Another example is short-cutting a single if block, much like you can do in other languages:
<%
if foo then
response.write "foo"
%>
If you are creating an if structure that only has one possible outcome, you should handle it in either of the following
formats:
<%
if foo then
response.write "foo"
end if
' or
Note that if you only have one branch and you need to commit multiple statements, and wish to do it on one line,
you can do so using a line separator (colon) -- though for readability it isn't usually recommended:
<%
if foo then response.write "foo": call somesub(): bar = false
%>
Why do I get 800A01C2 errors?
This can happen when trying to call a method like a property, or vice-versa; or by calling a method of a built-in
However, the most common cause I've seen for this is using ADODB.Recordset to create a new record, and then
<%
' ...
RS.Fields.Item("ColumnName") = "someString"
' ...
%>
To resolve, you have one of two options. The first is to use an INSERT statement directly against a connection
object; this is both more efficient and less error-prone. The second is to use either of the following syntax styles:
<%
' ...
RS.Fields("ColumnName") = "someString"
' or
RS.Fields.Item("ColumnName").Value = "someString"
' ...
%>
Why do I get 800A138F errors?
For starters, of course, make sure you are referring to an object that exists. If you are trying to access the
Application object, for example, and you spell it wrong, you're going to be out of luck. Make sure any custom
functions you are calling are in the script, or that the include file they're in is actually being included.
When using both languages in an ASP page, you can get this error if you try, from server-side JScript, to call a client-
side VBScript sub or function. See Article #2045 for details on mixing server-side scripting languages, because in
certain scenarios, your function call in one language will occur before the function even exists in the other language.
Do not use CreateObject by itself in JScript. Use one of the following types of syntax:
or
If you are attempting to send mail with CDO / CDONTS, you might find that .eml / .rtr files are sitting around in your
queue folder (usually indicating that your SMTP server is not configured correctly). See Article #2268 for more
information.
If you are attempting to automate a Microsoft Office product (Word, Excel, etc.), see Q257757... there may be some
advice there that will (perhaps indirectly) help resolve this issue. For example, you shouldn't attempt to set a Word
object to visible from within ASP, since the server-side isn't going to open an instance of Word for the system
administrators to see. Also, you should attempt to open an Office document from the server, by sending a
querystring over 255 characters. Use POST to send data to a page that is generating a Word or Excel document that
or
This is usually caused by an empty parameter to the Response.Redirect call. Instead of the following code:
<%
Response.Redirect(variable_that_holds_URL)
%>
<%
Response.Write(variable_that_holds_URL)
Response.End
%>
Why do I get 80070056 errors?
When using LDAP / ADSI from ASP, you may have come across this error:
First off, make sure you are using the correct password to connect to directory services. Next, make sure you are
using proper credentials (e.g. you have disabled anonymous access to the folder housing the ASP page(s)).
Why do I get 800A01F9 errors?
This is usually due to using shorthand syntax (usually found inside a With...End With construct), e.g.:
<%
set fso = server.createobject("scripting.filesystemobject")
.deleteFile("c:\foo.txt")
set fso = nothing
%>
Should be
<%
set fso = server.createobject("scripting.filesystemobject")
fso.deleteFile("c:\foo.txt")
set fso = nothing
%>
or
<%
set fso = server.createobject("scripting.filesystemobject")
With fso
.deleteFile("c:\foo.txt")
End With
set fso = nothing
%>
Check if you have any method or properties that start with a dot (search the code for " ." without the quotes).
Why do I get 8004E00F errors?
You can get these errors when running SyncIWAM.vbs (usually as a result of Event ID 36, see Article #2226). You
The reason for the error is that the distributed transaction coordinator (MSDTC) service is not started. To resolve
If it is not set to start automatically, you should do so in Control Panel or Administrative Tools / Services.
Why do I get 800A01CA errors?
If you are returning a numeric value from a database, make sure you convert it to a long or double before
If you have a custom COM object, make sure you are passing valid OLE automation datatypes back and forth (e.g.
those that can be supported as VARIANTs under VBScript). If you are using a BSTR as an [out] parameter, for
example, you might come across this error. One workaround would be to use an [out,retval] parameter instead.
You might have a simple typo, e.g. the following code would produce this error:
<%
response.write "foo = " &
rs("foo")
%>
Obviously, it seems like you are trying to call a method rs("foo") when it is in fact returning a string. Of course,
those two strings should either be on the same line, or concatenated by a proper line delimiter, e.g.:
<%
response.write "foo = " & rs("foo")
' or
Basically, all you have to do is convert both strings to lower case, and compare the length of the original string with
the length of that string where the string to find has been removed. In VBScript:
<%
x = "one"
y = "two one two two one two two one two"
interimString = replace(lcase(y), lcase(x), "")
response.write((len(y) - len(interimString)) / len(x))
x = "one"
y = "two one two two one two two one two"
response.write ubound(split(lcase(y), lcase(x)))
%>
In T-SQL:
DECLARE
@x VARCHAR(10),
@y VARCHAR(64)
SELECT
@x = 'one',
@y = 'one two two two one two two one two'
SELECT
(DATALENGTH(@y) - DATALENGTH
(
REPLACE(LOWER(@y), LOWER(@x), '')
))
/ DATALENGTH(@x)