All about conditional comments

One of the most common operations performed in a Web page is to detect the  browser type and version. Browser detection is performed to ensure that the  content presented to the browser is compatible and renders correctly. The  browser type can be detected using many different techniques. Most methods of  browser detection make use of script on the server or client.

This article introduces conditional comments, which offer certain advantages  over scripted browser detection techniques. Conditional comments make it easy  for developers to take advantage of the enhanced features offered by Microsoft  Internet Explorer 5 and later versions, while writing pages that downgrade  gracefully in less-capable browsers or display correctly in browsers other than  Windows Internet Explorer. Conditional comments are the preferred means of  differentiating Cascading Style Sheets (CSS) rules intended for specific  versions of Internet Explorer.


The following terms are used in this article.

Term Description
expression A combination of operators, features, and/or values used to form a       conditional statement.
downlevel browser Any browser except Internet Explorer 5 and later versions. For the       purposes of this article, downlevel refers specifically to any       browser or browser version that does not support conditional comments.
uplevel browser Internet Explorer 5 and later versions, which support conditional       comments.
downlevel-hidden A conditional comment block that is ignored by downlevel       browsers. Internet Explorer 5 and later versions render the HTML content       if the expression evaluates to true.
downlevel-revealed A conditional comment block that is parsed by downlevel       browsers. Internet Explorer 5 and later versions also render the HTML       content if the expression evaluates to true.

Benefits of Using Conditional Comments

Conditional comments have certain advantages over scripting methods of  browser detection.

  • Low client-side impact.When a downlevel browser encounters a downlevel-hidden conditional comment,   the browser skips over the HTML inside the comment, and the content elements   are not parsed, downloaded, or rendered. This saves client machine   resources.
  • No script required.Conditional comments do not require scripting and DHTML, and when no   scripting is used in a Web page, no scripting engine needs to be loaded.   Conditional comments are processed during the downloading and parsing phase,   so only the content that is targeted for the browser is actually downloaded.   Conditional comments can be combined freely with other browser detection   techniques.
  • Separate code from detection logic.Using conditional comments, script logic can be separated into smaller and   simpler segments of code, which are easier to maintain and understand. Plus,   code segments are loaded only by the browser version for which they were   intended.
  • Cross-browser.Conditional comments have been around since Internet Explorer 5, but their   use is not restricted to Internet Explorer alone. Conditional comments can be   used to customize content delivered to browsers that support conditional   comments and those that do not.

Syntax of Conditional Comments

The basic syntax of each type of comment is shown in the following table. The  first comment shown is the basic HTML Comment, which is included for the purpose of comparison and to illustrate  the different syntax used by each type of conditional comment.

Comment type Syntax or possible value
standard HTML comment <!– Comment content  –>
downlevel-hidden <!–[if expression]> HTML <![endif]–>
downlevel-revealed <![if expression]> HTML   <![endif]>

The HTML shown inside the syntax block in each of the conditional  comments denotes any block of HTML content, including script. Both types of  conditional comment use a conditional expression to indicate whether the  content inside the comment block should be parsed or ignored.

The conditional expression is formed from a combination of feature, operator,  and/or value, as shown in the following table.

Item Example Comment
IE [if IE] The only currently supported feature is the string “IE”,       corresponding to Internet Explorer.
value [if IE 7] An integer or floating point numeral corresponding to the       version of the browser. Returns a Boolean value of true if the       version number matches the browser version. For more information, see Version       Vectors.
! [if !IE] The NOT operator. This is placed immediately in front of the       feature, operator, or subexpression to reverse the       Boolean meaning of the expression.
lt [if lt IE 5.5] The less-than operator. Returns true if the first argument is less       than the second argument.
lte [if lte IE 6] The less-than or equal operator. Returns true if the first argument is       less than or equal to the second argument.
gt [if gt IE 5] The greater-than operator. Returns true if the first argument is       greater than the second argument.
gte [if gte IE 7] The greater-than or equal operator. Returns true if the first argument       is greater than or equal to the second argument.
( ) [if !(IE 7)] Subexpression operators. Used in conjunction with boolean operators to       create more complex expressions.
& [if (gt IE 5)&(lt IE     7)] The AND operator. Returns true if all subexpressions evaluate to     true
| [if (IE 6)|(IE 7)] The OR operator. Returns true if any of the subexpressions evaluates       to true.
true [if true] Always evaluates to true.
false [if false] Always evaluates to false.

Downlevel-hidden Conditional Comments

The following sample shows a downlevel-hidden conditional comment, which  contains a short paragraph of text

<!--[if IE 5]>
<p>Welcome to Internet Explorer 5.</p>

The downlevel-hidden conditional comment contains hyphens (“–“) in the  opening and closing tag, similar to the basic HTML Comment. The condition  appears in the opening portion of the tag, and [endif] is placed prior to the  closing portion of the tag. The content is placed inside the comment tags.

Because the first four characters and the last three characters of the  comment are identical to a basic HTML Comment element, downlevel browsers  ignore the HTML content inside the comment block. Since content is effectively  hidden from browsers that do not support conditional comments, this type of  conditional comment is called downlevel-hidden.

If the result of the conditional expression is true, the content inside the  comment block is parsed and rendered by Internet Explorer 5 and later versions.  This behavior makes the downlevel-hidden conditional comment particularly useful  for content that has been specifically designed for Internet Explorer.

The following sample illustrates how a client-side script block can be placed  inside a conditional comment; in this case, a message is displayed in Internet  Explorer 5 and later.

<!--[if gte IE 5]>

alert("Congratulations! You are running Internet Explorer 5 or greater.");

<P>Thank you for closing the message box.</P>

In the preceding example, only the major digit of the browser version is  compared because it is the only digit specified in the conditional expression.  To compare both major and minor version numbers, specify both digits. For  further explanation and examples on specifying the browser’s version number, see Version  Vectors.

Downlevel-revealed Conditional Comments

The downlevel-revealed conditional comment enables you to include content in  browsers that do not recognize conditional comments. Although the conditional  comment itself is ignored, the HTML content inside it is not. Internet Explorer  5 and later versions also parse and render the content if the conditional  expression evaluates to true. The downlevel-revealed conditional comment  complements the downlevel-hidden conditional comment.

The following snippet shows a typical downlevel-revealed conditional  comment.

<![if lt IE 5]>
<p>Please upgrade to Internet Explorer version 5.</p>

When comparing this type of comment to the basic HTML Comment, notice  that there are no hyphens (“–“) immediately after the opening “<!” or  immediately before the closing “>” of the comment block; therefore, the  comment delimiters are treated as unrecognized HTML. Because the browser does  not recognize the downlevel-revealed conditional comment, it does nothing with  it.

Version Vectors

Conditional expressions are often used to determine the version of the  browser. The format of the version vector number must be defined correctly to  obtain the desired result.

When testing the major browser version number, the version vector is an  integer. To check for a minor browser version, follow the version vector by a  decimal point and four digits. For example, the version vector for the release  build of Internet Explorer 5.5 is 5.5000.

In the following example, only the major version number is specified;  therefore, the sample evaluates as true for both Internet Explorer 5 and  Internet Explorer 5.5.

<!--[if IE 5]>
<p>Welcome to any incremental version of Internet Explorer 5!</p>

The following test correctly identifies Internet Explorer 5.

<!--[if IE 5.0000]>
<p>Welcome to Internet Explorer 5.0!</p>
Note  Internet Explorer 5, which shipped  with Microsoft Windows 2000, has a version vector equal to 5.0002. Therefore,  the conditional expression [if lte IE 5.0000] returns false when evaluated in  the release build of Internet Explorer 5.


Here are some more examples of  conditional comments.

<!--[if IE]><p>You are using Internet Explorer.</p><![endif]-->
<![if !IE]><p>You are not using Internet Explorer.</p><![endif]>

<!--[if IE 7]><p>Welcome to Internet Explorer 7!</p><![endif]-->
<!--[if !(IE 7)]><p>You are not using version 7.</p><![endif]-->

<!--[if gte IE 7]><p>You are using IE 7 or greater.</p><![endif]-->
<!--[if (IE 5)]><p>You are using IE 5 (any version).</p><![endif]-->
<!--[if (gte IE 5.5)&(lt IE 7)]><p>You are using IE 5.5 or IE 6.</p><![endif]-->
<!--[if lt IE 5.5]><p>Please upgrade your version of Internet Explorer.</p><![endif]-->

<!--[if true]>You are using an <em>uplevel</em> browser.<![endif]-->
<![if false]>You are using a <em>downlevel</em> browser.<![endif]>

<!--[if true]><![if IE 7]><p>This nested comment is displayed in IE 7.</p><![endif]><![endif]-->

Automating Server Side Tracing in SQL Server

Information & code
samples from this article are tested on SQL Server 2005 RTM (Yukon)
and found to be working. Will update the article in case of any
compatibility issues.

Experts from within and outside Microsoft have  always said that running Profiler on a production environment is not a recommended practice, as it could degrade the performance of the server. Instead they suggest doing server side tracing via SQL Trace system stored procedures. But DBAs often questioned this by asking, what if I profile the production server, from a different PC? Well, I haven’t heard a
convincing answer to this whole thing about Profiler vs. server side tracing yet, but I attended SQL PASS 2003 in Seattle (between 11th and 14th of November, 2003). More than once during the event, some of the big wigs from Microsoft PSS recommended server side tracing over Profiler. Well, they definitely know their stuff, so I decided to start using server side tracing, instead of Profiler. Not to mention, I personally heard about incidents where DBAs brought down SQL Servers by running Profiler (especially in version 7.0).

Prior to attending PASS, I’ve never used SQL Trace system stored procedures to set up a server side trace. So, I started off by reading SQL Server Books Online. There are a bunch of SQL Trace system stored procedures, that you can use to set up a server side trace. There are also a few system functions to query and get information
about currently running traces.
Here is a list of SQL Trace system stored procedures and functions:

SQL Trace system stored

sp_trace_create Creates a trace
sp_trace_generateevent Creates a user-defined
sp_trace_setevent Adds or removes an event
or event column to a trace
sp_trace_setfilter Applies a filter to a
sp_trace_setstatus Used to start, stop and
close traces

SQL Trace system

fn_trace_geteventinfo Returns information about
the events traced by a specified trace
fn_trace_getfilterinfo Returns information about
the filters applied to a specified trace
fn_trace_getinfo Returns information about
a specified trace or existing traces
fn_trace_gettable Returns trace file
information in a table format, for querying

If you look at the
documentation for above stored procedures in SQL Server Books Online, you
will realize that they accept about four input parameters each, on an
average. If you ever used Profiler, you’ll know there are numerous events
and data columns. It is a bit cumbersome To specify all those events and
data columns using these stored procedures. Because, every event has a
number associated with it, and so do the data columns. You cannot remember
all those numbers and repeatedly call the above procedures by specifying
various EventIDs and ColumnIDs. No wonder many DBAs prefer to use the
point-and-click Profiler. But hey, Microsoft is not really recommending
Profiler on a production server, and I must trace production SQL Servers
for various troubleshooting and tuning purposes while working on

So, I decided to write my own wrapper stored procedures, that
wrap the trace system stored procedures and make it easier to setup
traces. In this article, I’ll provide you with downloadable stored
procedures, that you could use to setup your own server side traces,
without much effort. These stored procedures make the job almost as easier
as using Profiler.

For example, using my wrapper stored procedures,
you could setup and start trace, to record all stored procedures that are
called in a specified database (with database ID = 37), along with the
following information: Actual stored procedure call, start time, end time,
duration, application name and host name. As you will see below, we called
four different friendly stored procedures. To setup the same trace
directly, using trace system stored procedures, you’ll have to call at
least 10 stored procedures, one for each event traced, and one for each
data column captured. A massive saving on the number of stored procedures
called 🙂 and I am definitely finding these wrappers much easier to setup
and quicker too. The more complex the trace definition gets, the more
savings on the number of stored procedure calls.

DECLARE @TraceID int

EXEC CreateTrace
‘C:My SQL TracesAll Procedure Calls’,
@OutputTraceID = @TraceID OUT

EXEC AddEvent
‘TextData, StartTime, EndTime, ApplicationName, ClientHostName, DatabaseID’

EXEC AddFilter

EXEC StartTrace

The massive saving in the
number of stored procedure calls to setup the trace, comes from the fact
that the main wrapper stored procedure accepts a list of all events and
data columns in a comma delimited string format. So, how many ever events
and data columns you want to capture in your trace, you’ll only make one
call to the wrapper stored procedure.

Better yet, all the above
wrapper procedures handle errors gracefully, and return meaningful and
helpful error messages, instead of throwing some cryptic return code

An important note: Only members of the sysadmin fixed
server role can execute the SQL Trace system stored procedures. So, even
if you manage to call my wrapper stored procedures, they will fail to
invoke SQL Trace system procedures, if you are not a

Deployment Instructions:

Click here to
the complete set of scripts in a zip file. Run all
the scripts inside a database of your choice. But make sure the tables and
stored procedures are created in the same database. I tend to create them
in a utilities/tools database created for use by the DBA team.

let me introduce you to all the wrapper stored procedures I

Stored procedure name Parameter usage

Creates an empty trace
definition, ready for use.

Click here to


Specifies the trace file name and complete
path. Do not provide a .trc extension to the file name, as SQL Trace
automatically adds the .trc extension to the output

@OverwriteFile – bit

Specifies whether to
overwrite the trace file, if it already exists. Default is 0, in
which case if the file already exists, an error will be raised and
trace will not be created. Specify 1 to overwrite an existing trace

@MaxSize – bigint

Specifies the maximum
size in megabytes (MB) a trace file can grow upto. Default is 5 MB.
This stored procedure code restricts the maximum trace file size to
512 MB (half Giga Byte (GB)) as a safety measure, but can be
overridden by setting the variable @MaxAllowedSize to a bigger
value. You will find @MaxAllowedSize in the body of the stored

@Rollover – bit

Specifies that when
the trace file reaches the maximum specified size, a new trace file
will be created. Default is 1, meaning new file will be created when
the current trace file reaches the maximum size. If you specify 0,
tracing will stop when the file reaches its size limit. The new file
will get the same name, but will be postfixed with a number, to
indicate the sequence. For example, when the file MyTrace.trc
reaches its maximum size, MyTrace_1.trc will be

@Shutdown – bit

Defaults to 0. If you
specify 1, SQL Server will shut down, if the trace cannot be written
to the file for whatever reason. Use this option with caution, and
only when absolutely needed.


Defaults to 0. If you specify 1, a blackbox trace will be
created. A black box trace stores a record of the last 5 MB of trace
information produced by the server. When 1 is specified, all other
parameters will be ignored. To learn more about how black box trace
works, consult SQL Server 2000 Books Online.


Defaults to NULL. When NULL, the trace will run
until it is manually stopped or until the server shuts down. If you
specify a valid date and time, the trace will stop automatically at
that specified date and time.

@OutputTraceID – int –
OUTPUT parameter

This is an OUTPUT parameter and returns the
ID for the trace that is created. This ID is needed for adding
events and filters to the trace, as well as for querying the trace

Return values: -1 indicates a failure and
0 indicates success

Note: The ID of the created trace
will also be returned as a resultset for convenience.


events and columns to the trace definition.

Click here to download

@TraceID – int

The ID of
the trace, created by CreateTrace, and is used to identify
the trace to which to add the events and columns.

@EventList – varchar(1000)

Used to specify a
comma separated list of events to capture. You can see a list of all
valid events in the SQL Server Books Online page titled
“sp_trace_setevent”. Alternatively, you will find a list of all the
events and their descriptions, in this


Used to specify a comma separated list of data
columns to capture. You can see a list of all valid column names in
the SQL Server Books Online page titled “sp_trace_setevent”.
Alternatively, you will find a list of all the data columns and
their descriptions, in this

Return values: -1 indicates a
failure and 0 indicates success


Adds filters to existing trace

Click here to download

@TraceID – int

The ID of
the trace, created by CreateTrace, and is used to identify
the trace to which to add the filter.


Name of the column on which to apply the filter.
You can only filter on a column, after adding that column to the
trace definition, using AddEvent


Specifies the value on which to filter.

@ComparisonOperator – varchar(8)

Specifies the
type of comparison to be made. Defaults to ‘=’, meaning ‘Equals’
comparison. Other valid comparison operators are: ‘<>’ (Not
Equal) , ‘>’ (Greater Than) , ‘<‘ (Less Than) , ‘>=’
(Greater Than Or Equal), ‘<=’ (Less Than Or Equal), ‘LIKE’ and


Defaults to ‘OR’. You could also specify ‘AND’.
Useful for filtering a column for multiple values.

-1 indicates a failure and 0 indicates

Note: Call this procedure once for each
filter. If you want to filter a column for a range of values
(similar to BETWEEN operator), call this procedure once with ‘>=’
comparison operator and again with ‘<=’ comparison operator.


Starts a specified

Click here to

@TraceID – int

The ID of
the trace (created by CreateTrace), to be started.

Return values: -1 indicates a failure and 0 indicates


Stops a specified

Click here to

@TraceID – int

The ID of
the trace (created by CreateTrace), to be stopped.

Return values: -1 indicates a failure and 0 indicates


Clears the definition of the
trace from memory.

Click here to

@TraceID – int

The ID of
the trace (created by CreateTrace), to be cleared from

Return values: -1 indicates a failure and 0
indicates success

Now that
we know what the stored procedures are called and how to use their
parameters, let me show you, how to use these stored procedures to setup
traces for specific requirements. Before we go any further, here is a
quick tip. Once you setup a specific trace using my wrapper stored
procedures, you could save all those stored procedure calls in a file. Now
this file will serve you as a template, just like the Profiler

Scenario 1: Identifying long running stored

In this scenario, we will trace for all stored
procedures, that took more than 15 Seconds to complete. The output trace
file ‘LongRunningProcs.trc’ will be saved to ‘C:My SQL Traces’ (Note
that this is the location on the SQL Server machine, not the client

DECLARE @TraceID int

EXEC CreateTrace
‘C:My SQL TracesLongRunningProcs’,
@OutputTraceID = @TraceID OUT

EXEC AddEvent
‘TextData, Duration’

EXEC AddFilter

EXEC StartTrace @TraceID

Once you are done, you could  stop the trace by calling the following stored procedures. Important Note:  You can only view the trace file, after successfully stopping the trace and clearing it from memory: (Lets assume that the ID of the trace created above was 1)
 EXEC StopTrace 1
EXEC ClearTrace 1

Scenario 2: Get a list of  all the stored procedures called within a specific database:

In this scenario, I will show you, how to get a list of all the stored procedures called, from within a specific database. In this example, we will look for all stored procedure calls from msdb database. We will also capture the start time, end time, application name, client host name, NT user name and the domain name.
DECLARE @TraceID int, @DB_ID int

EXEC CreateTrace
‘C:My SQL TracesProceduresCalledInMSDB’,
@OutputTraceID = @TraceID OUT

EXEC AddEvent
‘TextData, StartTime, EndTime, ApplicationName, ClientHostName, NTUserName, NTDomainName, DatabaseID’

SET @DB_ID = DB_ID(‘msdb’)

EXEC AddFilter

EXEC StartTrace @TraceID

Scenario 3: Tracing for specific errors:

Let us imagine a scenario, where you deployed  a brand new application and database. Now the old database is not needed  anymore. So, you took the old database offline, but you want to make sure  no user or application is trying to access the old database. As you  probably know, when somebody or some application tries to open an offline  database, you get the following error: 942: Database ‘OldDatabase’ cannot be opened because it is offline. In the following example, we will setup a
trace that looks for error 942 and captures the time of the request, application name, NT user name and the client machine name from which the request originated. We will also specify that if the trace file already exists, it’ll be overwritten.

DECLARE @TraceID int

EXEC CreateTrace
@OverwriteFile = 1,
@OutputTraceID = @TraceID OUTPUT

EXEC AddEvent
‘Error, StartTime, ApplicationName, NTUserName, ClientHostName’

EXEC AddFilter

EXEC StartTrace @TraceID

Scenario 4: Troubleshooting

In this scenario, I will show you how to setup a
trace to identify the connections (SPIDs) involved in a deadlock, using
the Deadlock and Deadlock Chain events.

DECLARE @TraceID int

EXEC dbo.CreateTrace
‘C:My SQL TracesDead Locks’,
@OutputTraceID = @TraceID OUT

EXEC dbo.AddEvent
‘Lock:Deadlock, Lock:Deadlock Chain, RPC:Starting, SQL:BatchStarting’,

EXEC dbo.StartTrace

Scenario 5: Identifying
stored procedure recompilations:

Stored procedure recompiles
have a potential to hinder the performance of your application. So it is
important to identify those procedures that are recompiling repeatedly,
and fix them, if the recompilation is not beneficial. The following
template creates a trace that logs the stored procedures that are
recompiling along with the database ID in which they are running. It also
captures EventSubClass. From SQL Server 2000 SP3 and above, EventSubClass
tells you the exact reason for the stored procedure recompilation. For
more information search Microsoft Knowledge Base (KB) for article

DECLARE @TraceID int

EXEC dbo.CreateTrace
‘C:My SQL TracesRecompilations’,
@OutputTraceID = @TraceID OUT

EXEC dbo.AddEvent
‘ObjectID, ObjectName, EventSubClass, DatabaseID’

EXEC dbo.StartTrace

Scenario 6: Starting a
Blackbox trace:

A black box trace stores a record of the last 5 MB of trace information produced by the server. This is very useful for  troubleshooting nasty problems, bugs and access violation errors, that cause the SQL Server to shutdown. Consult SQL Server 2000 Books Online and  Microsoft Knowledge Base for more information on Blackbox traces.

DECLARE @TraceID int

EXEC CreateTrace
@Blackbox = 1,
@OutputTraceID = @TraceID OUT

EXEC StartTrace


above scenarios, will just get you started, but you can really use these
stored procedures to setup complicated traces with various columns, events
and different types of filters. I hope you find my work useful. For the
sake of completeness, I’ll mention the fact that, you could even schedule
the above stored procedures, as SQL Agent jobs, in order to start the
traces at a desired date and time.

In the process of learning the
SQL Trace system stored procedures, I did stumble upon few bugs. For
example, when you set a filter on ObjectID, and then query the trace
definition using fn_trace_getfilterinfo function, the ObjectID reported
will be incorrect when the ObjectID is greater than 255 (SELECT value FROM

One thing I observed with
Profiler is that, though the trace system stored procedures support the
comparison operators > and <, Profiler only shows >= and

Before we end, here are the links to some of the best SQL
Server performance tuning related books. I personally, read some of these
books and found them extremely useful and enlightening. Hope you find them
useful too:


The SQL Server 2000 Performance Optimization and Tuning HandbookThe SQL Server
2000 Performance Optimization and Tuning Handbook
Microsoft SQL Server 2000 Performance Tuning Technical ReferenceMicrosoft SQL
Server 2000 Performance Tuning Technical Reference
Yes. Click here to
SQL Performance TuningSQL Performance
The Guru's Guide to SQL Server Architecture and InternalsThe Guru’s Guide
to SQL Server Architecture and Internals
Inside Microsoft SQL Server 2000Inside Microsoft
SQL Server 2000
Yes. Click here to
Microsoft SQL Server 2000 Resource KitMicrosoft SQL
Server 2000 Resource Kit
Yes. Click here to


Hello world!

Welcome to After you read this, you should delete and write your own post, with a new title above. Or hit Add New on the left (of the admin dashboard) to start a fresh post.

Here are some suggestions for your first post.

  1. You can find new ideas for what to blog about by reading the Daily Post.
  2. Add PressThis to your browser. It creates a new blog post for you about any interesting  page you read on the web.
  3. Make some changes to this page, and then hit preview on the right. You can always preview any post or edit it before you share it to the world.