Subscribe | Alerts via Email
View All Quotes
“Three years from now, no one will remember if you shipped an awesome software release a few months late. What customers will still remember three years from now is if you shipped a software release that wasn't ready a few months too soon.”
-Scott Guthrie
<July 2010>
SunMonTueWedThuFriSat
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

©2010 Cal Zant
Sign In
Total Posts: 106
This Year: 5
This Month: 1
This Week: 0
Comments: 2

One of the biggest drawbacks of using ASP.NET is the way it can really bloat a page size with ViewState.  That is because http and the web in general are stateless by nature, but when creating applications developers often need to track what changed on a form ... hence ViewState was introduced to essentially let the development code as if the web was stateful.  Although some left-wing purist might argue, my more pragmatic nature causes me to think this was a good thing overall because of the dramatic increase in productivity it offers.  However, the downside was bloated pages to the client ... but now ASP.NET 4.0 offers a better way to control that.

Pre 4.0, ASP.NET offered the EnableViewState property which you could put on a page or control.  If you set that value to false, it disabled all viewstate for that control and any child controls.  The problem was you didn't have the ability to override that behavior in any of the child controls.  So this was more of a system where it had to be enabled at a high level, but then disabled in an opt-out fashion where you didn't really need it.  The result was that people just didn't opt-out.  That isn't because they were lazy (well, not completely) ... its just that pattern doesn't encourage good practices.

ASP.NET 4.0 includes a new ViewStateMode property that is completely different than EnableViewState.  It allows you to set the overall viewstate for a site off by default, but that can be overridden at any level below that.  This creates an opt-in pattern.  When I first tried to confige this setup, I ran into a few problems because I was trying to mix EnableViewState with ViewStateMode. EnableViewState always trumps the ViewStateMode setting.  I think it is much simpler to not mix the two and just avoid the old school EnableViewState property all together, and solely rely on ViewStateMode to control your ViewState.

The ViewState Opt-In Pattern Using ViewStateMode

  1. Set the site's masterpage(s) to have a ViewStateMode setting of "Disabled".  Unfortunately you can't set that property at the page level in the web.config, but disabling at the master page level should have a similar site-wide effect.

    <%@ Master Language="C#" ViewStateMode="Disabled" ...

    You could optionally set the ViewStateMode="Disabled" on one or more of the content placeholders in your master page:

    <asp:ContentPlaceHolder ID="ContentPlaceHolder1" ViewStateMode="Disabled" ...
  2. Then, when you need viewstate on a certain page or control ... just set ViewStateMode="Enabled" at that location.  It is usually very obvious when you need this when you are developing the site from scratch, but this type of pattern can be more difficult to retro-fit into an existing app.  Essentially the things that typically need ViewState are controls that you will need to access the values of on a PostBack to the server (e.g. textboxes, drop downs, checkboxes, etc).

    One tricky thing I have ran into is that you can't set the ViewStateMode at the page level in the Page directive like this (at least when using master pages):

    <%@ Page Title="Dynamic Schedule" ViewStateMode="Enabled" ... // WON'T WORK

    Instead you should preferrably set it for the individual controls that need it that way only the ViewState data that is required would be sent to/from the client, and the page wouldn't be bloated with unnecessary ViewState data.

    <asp:CheckBoxList ID="cblProducts" ViewStateMode="Enabled" ...

    If you have many controls that you need to enable ViewState on, and you didn't want to have to set the property on all of them ... you could optionally just enable viewstate for an entire content placeholder section like this:

    <asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" ViewStateMode="Enabled" ...

Here are a few more helpful resources on the new ViewStateMode functionality:

http://msdn.microsoft.com/en-us/library/system.web.ui.viewstatemode.aspx
http://weblogs.asp.net/sreejukg/archive/2010/04/06/viewstatemode-in-asp-net-4-0.aspx
http://www.mostlylucid.net/archive/2009/01/28/1312.aspx

Friday, July 23, 2010 2:06:36 PM (Central Standard Time, UTC-06:00)  # 

This is a T-SQL code snippet I find myself using regularly to find all references to a particular field or table, or search for comments from a particular date.  It essentially allows you to search for a keyword in all of the definitions for stored procedures, functions (scalar or table-valued), and views.  It uses some of the built-in methods SQL Server provides for obtaining metadata, which are essentially simplified views of the data contained in system tables like sysobjects and syscolumns.  I know this T-SQL script works on SQL Server 2005 & SQL Server 2008, but don't know if the methods it is dependent on were available on previous versions.

DECLARE @Keyword varchar(64)
SET @Keyword = '%keyword%'

(
  SELECT  [ROUTINE_TYPE] AS 'Type', [SPECIFIC_NAME] AS 'Name'
  FROM  INFORMATION_SCHEMA.ROUTINES
  WHERE  [ROUTINE_DEFINITION] LIKE @Keyword
 UNION
  SELECT  'VIEW' AS 'Type', [TABLE_NAME] AS 'Name'
  FROM  INFORMATION_SCHEMA.VIEWS
  WHERE  [VIEW_DEFINITION] LIKE @Keyword
)
ORDER BY [Type], [Name]

Here is an example of the results of the script:

Search Results of All SQL Defintions for Stored Procedures, Functions, & Views
Tuesday, April 13, 2010 6:45:29 AM (Central Standard Time, UTC-06:00)  # 

A few weeks ago, I started noticing that every time I opened a LINQ to SQL .dbml file in Visual Studio 2008 I would get an alert message that said "The connection property in the web.config file is missing or incorrect.  The connection string from the .dbml file has been used in its place."

The connection property in the web.config file is missing or incorrect

The dbml file was configured to reference a connection string in the web.config file, but after I got this alert and clicked OK, Visual Studio would check out the project and add a new Settings.settings file under the Properties folder and generate a new connection string there, and then change the dbml file to reference that instead of the web.config.

Changes that were automatically applied to the project after I clicked OK

This would probably be the behavior you would expect if the web.config was really missing the connection string, but in my case the referenced connection string was there and well-formed.  I know this, because the related code was under source control and other developers could open the same dbml file (from the shared code base) and they wouldn't get the alert and everything worked flawlessly.

To me, it seemed like the LINQ to SQL designer on my machine was simply having trouble reading from the configuration file ... so I thought it might be an issue with a particular dll or registry entry.  Another thing that convinced me of this was the fact that when you were in the dbml editor ... under the Connections property on my computer, none of the connection strings from the web.config were included in the drop down to select from.  But, when another developer would look at this on their machine (using the same exact code base and files), their Visual Studio environment would find those connection strings in the web.config and include them in the list to select from.

Connection Options on my computer containing no references to web.config connection strings

Connection Options on other computers containing references to web.config connection strings

I figured out that I could just undo the changes to add the new file, and then view the differences of the dbml file's XML and undo the reference change to make it once again reference the web.config instead of the settings file it created ... and that worked fine for a couple weeks, but eventually jumping through those extra hoops that "should just work" got pretty old.

So I first tried to repair my Visual Studio install, but that didn't work.  Next, I completely uninstalled Visual Studio and then re-installed it and the related service packs ... but that didn't work either.  I was about to just format my machine and re-install Windows ... but I thought I would give Microsoft Support a chance, hoping they could help me.  I knew this was an obscure enough problem that they probably wouldn't be much help, but I really hate paving my machine ... so it was worth a little time to give them a chance.  After explaining what was going on to a technician, we tried to use ProcMon to figure out what was going on behind the scenes.  We noticed several registry errors, and that further convinced me that the problem was corrupted  or missing registry keys.  The support technician said he would have to look into it more, but I wasn't going to count on them as my only hope for a solution ... so I kept investigating myself.  Apparently when you uninstall Visual Studio, it doesn't remove all of the registry keys.  It also doesn't overwrite them all when you run a repair.  It doesn't even remove them all when you remove all the other software that might have hooks or add-ins related to Visual Studio.  So these corrupted or missing registry keys could have persisted through the repair and re-install.

Here are some of the issues we noticed in ProcMon (it is a snapshot of what was happening when I would open the dbml file and the pop-up alert would appear, and then I would click OK).  The one highlighted is the what I was guessing was the culprit, because that missed registry key occurred right between the read of the dbml file and the opening the related project (which I am guessing was to create the new settings file).  You can ignore the "Fast IO Disallowed" stuff, because that is unrelated.

Related errors from Process Monitor while opening the LINQ to SQL dbml file

So, although the support technician had actually explicitly advised me not to do this ... I decided to uninstall Visual Studio (and any other software add-ins that might be related to it), manually delete the related registry keys contained in any folder that appeared related to Visual Studio, and then do a fresh install.  I never think hacking in the registry is a good solution to a problem, but this was my last ditch effort ... and I was resolved to do a full re-install of Windows if it didn't work.  To my surprise ... it actually worked.

I can't guarantee this will work for you, or even recommend it ... but here are the registry folder locations I manually deleted:

  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio
  • HKEY_CURRENT_USER\SOFTWARE\Microsoft\VisualStudio
  • HKEY_CURRENT_USER\SOFTWARE\Microsoft\VSCommon
  • HKEY_CURRENT_USER\SOFTWARE\Microsoft\VSTAHost

Hopefully if you are still reading, this is the exact problem you are having and this will work for you as well.  The only thing I can say is ... it worked on my machine, and it saved me from having to completely re-install Windows to fix the problem.

Wednesday, January 20, 2010 9:22:50 AM (Central Standard Time, UTC-06:00)  # 

We recently ran into a problem up at work where people were opening a particular spreadsheet from a network fileshare, which had a lot of formulas that were tied to a few inputs.  You would typically change one input cell, and a lot of the other related cells would be recalculated and updated automatically.  However, a wierd issue started occuring where some users would change the input and nothing would happen.  But ... when they saved the worksheet, all of the related calculated cells would be updated at that time.

I think the issue was related to one person opening the spreadsheet on a Mac that was running Office 2008, and when they saved it we noticed it changed a lot of subtle things like the colors or formatting ... but I think it may have also changed the formulas to be manually updated and only automatically updated when the file is saved, which is an Excel Option.  Regardless of how this issue occurred ... here is the fix:

  1. Open Excel, and click on the Microsoft Office Button in Excel icon in the top left of the window, then go to Excel Options.
    Open Excel Options dialog
  2. Then go to the Formulas tab, and change the Calculation options to Automatic, and hit OK to apply the changes.
    Change formula calculation options to automatic
Thursday, January 14, 2010 10:32:59 AM (Central Standard Time, UTC-06:00)  # 

I was recently using SQL Server Report Builder, and needed to format a date and time in a specific way.  I went to edit the related expression, and looked for built-in functions to help me work with dates under Common Functions > Date & Time ... where I found FormatDateTime:

Using FormatDateTime in SSRS Expression

This is the example shown for how to use it:

=FormatDateTime(Fields!BirthDate.Value, DateFormat.ShortDate)

This is helpful, because the 2nd argument is obviously a DateFormat enumeration ... but what are the other members besides ShortDate?  I searched (like you might have) and couldn't come up with anything.  The only thing I can assume, is that the DateFormat enumeration used in SSRS expressions is the same as the DateFormat enumeration in Visual Basic, which is:

  • GeneralDate
  • LongDate
  • ShortDate
  • LongTime
  • ShortTime

None of these "canned" formats matched the way I wanted to format the date, so I kept looking and noticed some MSDN samples that used the Format method instead of the FormatDateTime method.  In the expression edittor, that function can be found under Common Functions > Text:

Format a date and time in SQL Report Builder Expression

The Format method is very flexible, and allows you to use any of the standard string-based format patterns you are probably already familar with.  Here are a few common examples:

=Format(Fields!myDateTime.Value, "M/d/yy") ... 6/15/09
=Format(Fields!myDateTime.Value, "M/d/yyyy h:mmtt") ... 6/15/2009 2:45PM
=Format(Fields!myDateTime.Value, "MM/dd/yy HH:mm") ... 06/15/09 14:45
=Format(Fields!myDateTime.Value, "MMM d, yyyy") ... Jun 15, 2009
=Format(Fields!myDateTime.Value, "Short Date") ... 6/15/2009
=Format(Fields!myDateTime.Value, "Long Date") ... Monday, June 15, 2009

Of course, the standard format strings like "Short Date" and "Long Date"  may vary depending on the culture set on your system.  The examples above reflect the "en-US" configuration.

Then, here is a slight variation of the 2nd sample.  It is my personal favorite and how I usually format all dates that have time values related to them, because I think the lowercase AM/PM designator makes the value easier for someone to scan, and requires less conscience effort to process.  If it is all uppercase, the AM/PM designator seems to blend in with the rest of the text and you have to pay a little closer attention to interpret the related value (even if it is just a tenth of a second).

=LCase(Format(Fields!myDateTime.Value, "M/d/yy h:mmtt")) ... 3/15/2009 2:45pm

Tuesday, December 29, 2009 3:06:08 PM (Central Standard Time, UTC-06:00)  # 

I was working on a report in Report Builder 2.0 for SQL Server 2008 Reporting Services, and added a textbox to an existing report.  I wanted to display some summary info related to the data shown in the report.  I added a textbox, then went to edit the “Expression” property of that report item, but wasn't able to select any fields because it said “Report item not linked to a dataset.”

 

Report Item not linked to a dataset

 

So I tried to find a property on the textbox related to a "Dataset" (like the "DataSetName" property on Tablix elements), but the only thing I found was a “DataElementName” property … which I tried, but didn’t work.

SSRS Report Item Textbox and Tablix Report Builder Properties

I then learned you can link to a dataset by defining it’s name directly in the expression, like this:

=Count(Fields!Address.Value,"DatasetName")

Monday, December 28, 2009 11:36:38 AM (Central Standard Time, UTC-06:00)  # 

After installing SQL Server Data Mining Add-ins for Office, I was attempting to use some of the table analysis functionality it provides and ran into an error that said "Error (Data mining): Session mining objects (including special data source views used to process data mining dimensions) cannot be created on this instance".  Wow, what a helpful error message!

Session mining objects cannot be created on this instance

It turns out, in order to use the analysis tools like forecast, highlight exceptions, detect categories, analyze key influencers, scenario analysis, fill from example, etc. ... you have to configure the SQL server to allow creation of temporary mining models (aka "session mining objects").  Ahh ... the cryptic error message makes a little more sense now.

Here is how you fix it:

  1. Go to Start Menu > All Programs > Microsoft SQL 2008 Data Mining Add-ins > Server Configuration Utility
    SQL 2008 Data Mining Add-in Server Configuration Utility
  2. The utility will walk you through a few simple steps, but this step is the one that really addresses this issue.  It explains a little more about these temporary/session models.
    Allow creation of temporary mining models

That's it.  After you finish the wizard, you should be able to run the model.

NOTE: If you created a new database for add-in users in step 3 of the configuration tool, you will obviously have to change the Analysis Services connection in Excel to point to that new database before this will work.

Change Analysis Services connection in Excel
Monday, December 07, 2009 3:02:51 PM (Central Standard Time, UTC-06:00)  # 

This article gives three easy steps to start showing Google ads on your site.  Although most sites won’t make a fortune from ad revenue, if you even has a small amount of traffic you could easily pay for your hosting and domain fees.  If you have a site with higher traffic and content related to high paying keywords (simply meaning topics more advertisers are willing to pay to be placed beside) … you might could make A LOT.  It’s really simple to add Google ads to your site:

  1. Sign up for a Google AdSense account (yes, even if you already have a Google account for other services like gMail).  It is a pretty short form, only takes a moment, and if you decide it isn’t for you … your under no obligation to use the service.  Apply Here
  2. Wait for site approval.  When you signed up for an account, they asked for the URL of the website you are thinking about placing the ad words on.  People at Google will check it out and ensure it is in line with their program policies.  They will let you know if you are accepted by sending a follow-up email within a week (in my experience it was less than 24 hours, but it can vary based on the volume of applications they are processing at the time).
  3. Paste the HTML snippet into your site.  After you are approved, you can sign into your account and get the HTML code snippet that will display ads on your webpages.  Just paste it into your site, and that’s it … your site will start displaying targeted ads based on its content.  

Here is an example of what the HTML snippet will look like:

<script type="text/javascript"><!--
   google_ad_client = "pub-xxxxxxxxxx";
   google_alternate_color = "ffffff";
   google_ad_width = 160;
   google_ad_height = 600;
   google_ad_format = "160x600_as";
   google_ad_channel ="xxxxxxxxxx";
   google_ad_type = "text_image";
   google_color_border = "FFFFFF";
   google_color_bg = "FFFFFF";
   google_color_link = "0000FF";
   google_color_url = "666666";
   google_color_text = "333333";
//--></script>
<script type="text/javascript" src="
http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>

You can customize the various settings in the snippet (like “google_color_bg” which controls the background color of the ad area) so the appearance of your ads complements the look and feel of your site.

Another Way To Earn Revenue With The “AdSense for Search” Search Box
In addition to showing ads on your site, you can also earn revenue by adding an AdSense search box to your site.  This allows users to perform searches and the results are displayed in a frame within your site.  You can limit the search results to just your own site, or a collection or subset of sites.  Relevant ads will appear with the results like you typically see on Google searches, but since they are being displayed on your site … you will earn revenue from them.  You can customize the look and feel of the search results to complement your site.

Tuesday, December 01, 2009 7:59:48 PM (Central Standard Time, UTC-06:00)  # 

I have some WCF services that I work in quite a bit, and ever since I enabled the project to automatically generate an XML documentation file (so I could use Sandcastle to build MSDN-style help files with it) ... I started getting really verbose warnings every time I built the project.  It would warn me about any publicly visible types or members that didn't have explicit XML comments associated with it.  Because those comments wouldn't be exposed to the WCF client that consumes the service anyway, my team doesn't add XML comments to every class and property (at least not the ones where the name makes the behavior completely obvious, so that a summary isn't needed).

Every time I did a build I would see a ton of warning that said something like "Missing XML comment for publicly visible type or member..."  So, I wanted to supress all of those warning, so I didn't have to wade through them to see other warnings that really did need to pay attention to.  In order to hide a particular warning, you have to figure out what the warning number is for the type of message you want to disable.  One easy way to quickly find that in VS 2008 is to simply right click on the warning and click on "Show Error Help":

Find Number To Disable Build Warnings

The help file that pops up, will contain a heading like shown below ... which contains the error number or warning number that you need to know to supress that type of message (highlighted):

Compiler Warning Help

After you have the number, all you have to do is set the project's build to surpress that warning number.  To do that, just right click on the project in Solution Explorer, and select "Properties."  Then, under the "Build" tab, find the section for "Errors and warning" and add the warning number to the "Supress Warning" textbox like shown below:

Configuring Build To Supress Certain Warnings

Note: You can also decrease the overall warning level for the project, which will make VS less verbose about non-critical warnings ... but I personally think it is a better idea to leave it at 4 (the default) and explicitly filter warnings that are a problem.

Thursday, October 08, 2009 10:30:35 AM (Central Standard Time, UTC-06:00)  # 

I got this error message when trying to open a few open source or sample solutions in Visual Studio 2008.

error x.csproj cannot be opened The project type is not supported by this installation

Of course I had the generic C# projects installed, so I googled around and after sifting through a few solutions that didn't work ... I realized what the issue was.

The real issue is that Visual Studio 2008 didn't recognize some of the "ProjectTypeGuids" defined in the .csproj file, because one or more those GUIDs referenced a type of project that wasn't installed.  However, instead of figuring out which project templates you are missing, and finding out where to download those from and get them installed ... there is a shortcut you can take if you just want to open the project.  You can simply replace that tag in the .csproj file with standard GUIDs (installed by default in any instance of VS), then just reload the project and it should open as expected.

Here is an example of the before and after for one sample app I had problems opening.  The app is called Nerd Dinner, which is an open source application that Scott Hanselman created (http://nerddinner.codeplex.com/).  It turns out it has a dependency on MVC, and I didn't have the project templates related to MVC installed on my machine (nor did I want to install them).  The templates for MVC aren't bundled with VS 2008, but instead are installed through a separate "extension" download (which is the case for a lot of project templates).  All I wanted to do is look at some of the code, so by removing the bad GUID references, I could then open the project and poke around.

Before:

<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    ...
    <ProjectTypeGuids>{603c0e0b-db56-11dc-be95-000d561079b0};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
    ...

After:

<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    ...
    <ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
    ...

That's it ... just replace the entire <ProjectTypeGuids> tag with the values shown above, and reload the project.  Note: You might need to open the csproj file in notepad to be able to edit the underlying XML this way.

Tuesday, October 06, 2009 7:40:08 AM (Central Standard Time, UTC-06:00)  # 

I was recently attempting to build documentation for a few projects using Sandcastle, a free engine provided by Microsoft that compiles documentation with a MSDN look and feel from managed class libraries and projects.  Sandcastle doesn't have a GUI or command-line interface built-in, so I used the Sandcastle Help File Builder, which is an open source project.  If I used a dll as the documentation source, it was able to build the documentation ... but you are also supposed to be able to reference a project (e.g. a file with a .csproj extension), but when I tried to do that instead of the dll I got this error:

SHFB: Error BE0065: BUILD FAILED: The imported project "C:\Program Files\MSBuild\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" was not found. Confirm that the path in the <Import> declaration is correct, and that the file exists on disk.  C:\...\XXX.csproj

I googled this error (like you probably did), and found this post about multi-targetting that seemed unrelated at first ... but turned out to kind of be the answer.  It suggested replacing the typical import statement in the .csproj file with one that included conditions, which allows the project to automatically determine which Microsoft.WebApplication.targets file to reference based on the environment.

To fix it, I just opened the .csproj file with Notepad, and removed the existing import statement related to Microsoft.WebApplication.targets (towards the bottom of the file), and replaced it with two import statements that included the conditions.

Replace This Line:

<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" />

With These Lines:

<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v8.0\WebApplications\Microsoft.WebApplication.targets" Condition="'$(Solutions.VSVersion)' == '8.0'" />
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" Condition="'$(Solutions.VSVersion)' == '9.0'" />

After that it built fine with just the reference to the project file instead of the actual dlls.  This is a good thing, and here is a tip from the Sandcastle Help File Builder documentation that explains why this is better than referencing the dlls directly:

Given that solutions and projects are supported as documentation sources, you may find it easier to add them as documentation sources instead of the assemblies, comments, and references that they contain. When a solution or project is used, these items are imported from them automatically at build time.

Monday, September 14, 2009 7:36:25 AM (Central Standard Time, UTC-06:00)  # 

I read an interesting blurb in the August 2009 edition of PC Magazine, which I thought was helpful. Here is an excerpt:

"Windows XP and Vista store the DNS information of websites you've visited, to reach those sites faster each time you access them. The cache sometimes gets corrupted or stores a lot of unusable data, and that leads to slower Internet response times. To clear the cache, go to the command prompt and type ipconfig/flushdns." - PC Magazine August 2009

Friday, August 07, 2009 7:07:18 AM (Central Standard Time, UTC-06:00)  # 

I recently read a helpful article in the July/August edition of Code magazine that pointed out several things you could do to tune up developer workstation.  To get more performance you could obviously boost your RAM, CPU, or hard drive ... but this article mainly focused on what Visual Studio and .NET do to your computer, and how you can do some simple file system clean-up to boost the performance of your machine.  It also included a few general tips for tweaking visual effects and OS-level stuff to free up more processing power and memory.  Here is a cliff-notes version of what I took away from the article of cleanup tasks to perform in Windows Vista:

Hard Drive Elements

  • Delete all files and subfolders from the directories below (if you use IIS for ASP.NET development you should run the "iisreset" command first so it will release the files so you can delete them):
    • <sysdrive>:\Windows\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files
    • <sysdrive>:\Windows\Microsoft.NET\Framework64\v2.0.50727\Temporary ASP.NET Files
    • <sysdrive>:\Users\<UserName>\AppData\Local\Microsoft\WebsiteCache
    • <sysdrive>:\Users\<UserName>\Documents\Visual Studio 2008\Backup Files
  • Run "Disk Cleanup" utility at least once a month (this takes care of a long list of miscellaneous directories and file types that can be safely deleted)
  • If you have ever created any additional users on your computer and they no longer use your computer, you should delete their subfolder under <sysdrive>:\Users
  • After you have completed the tasks above, defragment your hard drive.

Visual Effects

  • Make sure you have configured settings for "best performance" ... who needs all that animated fading, sliding, translucent fluff anyway?
    • Change your color scheme to Windows Vista Basic (Control Panel\Appearance and Personalization\Personalization > Window Color and Appearance)
    • If you have a background picture on your desktop or have the gadget sidebar enabled, you should consider removing them. Both take up memory and make screen refreshes much slower.

Operating System

  • There are probably a ton of services running in the background that are not needed for most of your day-to-day tasks, but were either turned on when the OS was installed or when you installed a certain program.  Try turning off as many services as you can, but keep a list of the items you turned off ... so when something doesn't work like it used to, you can go back and turn a particular service back on.  One tool you can use to easily set which services should be running is "System Configuration", which you can get to by running "msconfig".
    Optimize Dev Machine Background Services

    Here is a link to the full article by Paul Sheriff: http://www.code-magazine.com/Article.aspx?quickid=0907031

    Monday, July 27, 2009 7:33:59 AM (Central Standard Time, UTC-06:00)  # 

    A recent project forced me to try to use the COM-based Office Interop stuff ... painful.  I found some example code and tried to get it to run, but kept getting a System.UnauthorizedAccessException "Retrieving the COM class factory for component with CLSID ..." exception on this line of code:

    ApplicationClass excelApplication = new ApplicationClass();

    Retrieving the COM class factory for component with CLSID exception

    Although the exception above didn't give me much direction, I was able to uncover more details when I went to view the same error in the computer's event log:

    "The machine-default permission settings do not grant Local Activation permission for the COM Server application with CLSID {00024500-0000-0000-C000-000000000046} to the user DOMAIN\USER SID (S-1-5-21-789336058-854245398-1417001333-2107) from address LocalHost (Using LRPC). This security permission can be modified using the Component Services administrative tool."

    The machine-default permission settings do not grant Local Activation permission for the COM Server application with CLSID

    Turns out you have to configure some COM security stuff on the computer before you can actually make a call like this.  Here is what you have to do to adjust the settings.

    1. Figure out what application the CLSID GUID represents.  To do this you have to look in the registry.  So run "regedit" and go to Computer\HKEY_CLASSES_ROOT\CLSID and find the related CLSID folder.  In my case, the error was related to "Microsoft Office Excel Application."
      Figure out what application the CLSID GUID represents
    2. Open the "Compenent Services" configuration window by running "dcomcnfg".

    3. Find the related app like shown below, and then right-click on it and go to "Properties"
      Find the related application in component services
    4. In the "Properties" window go to the "Security" tab.  Then under "Launch and Activation Permissions" click the "Customize" radio button, then click edit.
      customize the launch and activation permissions
    5. Add the account the code is executing as and give them the appropriate permissions:
      add account code is executing as
    6. Then go back to the "Component Services" configuration window and right-click on "My Computer" and go to "Properties":
      Go to computer properties in Component Services configuration window
    7. Under the "COM Security" tab find the area for "Launch and Activation Permissions" and click the "Edit Default" button.
      Edit default for Launch and Activation Permissions
    8. Add the account and set the permissions like we did in step 5. 

    That should be it.  Try the code again and it should run (or at least get you past this particular exception).  So far, my experience with COM is that it is painful and should be avoided at almost any cost.  In my scenario though, it seems to be the only viable option out there, because no 3rd party component has the particular feature I needed ... so sometimes this is unavoidable, and I hope these instructions make it a little less painful for you.

    Monday, July 20, 2009 9:16:23 AM (Central Standard Time, UTC-06:00)  # 

    In my recent post on How To: Find Missing Indexes in SQL Server, I showed how to find the information related to indexes that SQL Server thinks would be helpful (based on query stats it has been keeping since it was last rebooted).  The actual "CREATE INDEX" script you have to write based on that info can be a little tedious to get write, so I created a user-defined function that you could simply pass some of the info you get from the query in that post and it will return the necessary T-SQL you would need to run in order to create the index for the given info.

    ALTER FUNCTION [dbo].[bcz_fn_GetCreateIndexScript](@TableName varchar(100), @EqualityColumns varchar(500), @InequalityColumns varchar(500), @IncludedColumns varchar(500))
    RETURNS varchar(MAX)
    BEGIN

     DECLARE @CreateScript varchar(MAX), @Name varchar(128), @EqualityAndInequalityColumnList varchar(MAX), @ColumnListForName varchar(MAX), @MaxColumnListLength int, @IndexPrefix varchar(63)

     -- Construct what the prefix of the index will be (this will be followed by the list of related columns)
     SET @IndexPrefix = 'IX_' + @TableName + '_'

     -- Construct a list of all columns that will be involved with the index (equality, inequality, and included)
     SET @EqualityAndInequalityColumnList = ''
     IF @EqualityColumns IS NOT NULL
      SET @EqualityAndInequalityColumnList = @EqualityColumns
     
     IF @InequalityColumns IS NOT NULL
     BEGIN
      IF (LEN(@EqualityAndInequalityColumnList) > 0) SET @EqualityAndInequalityColumnList = @EqualityAndInequalityColumnList + ', ' + @InequalityColumns
      ELSE SET @EqualityAndInequalityColumnList = @InequalityColumns
     END
     
     SET @ColumnListForName = @EqualityAndInequalityColumnList
     
     IF @IncludedColumns IS NOT NULL
     BEGIN
      IF (LEN(@ColumnListForName) > 0) SET @ColumnListForName = @ColumnListForName + ', ' + @IncludedColumns
      ELSE SET @ColumnListForName = @IncludedColumns
     END

     -- Remove the brackets around the column names, and make it "_" delimited instead of ", " delimited
     SET @ColumnListForName = REPLACE(REPLACE(REPLACE(@ColumnListForName, '[', ''), ']', ''), ', ', '_')
     
     -- The max length of the index name must be <= 128 ... so calculate how big the column list can be with
     -- respect to that and the given naming convention we will use.
     SET @MaxColumnListLength = 128 - LEN(@IndexPrefix)

     -- Create the name with the list of columns appended to the end of it
     SET @Name = @IndexPrefix + SUBSTRING(@ColumnListForName, 1, @MaxColumnListLength)

     SET @CreateScript =
      'CREATE NONCLUSTERED INDEX ' + @Name + ' ON ' + @TableName + ' (' + @EqualityAndInequalityColumnList + ')'
      
     IF @IncludedColumns IS NOT NULL
      SET @CreateScript = @CreateScript + ' INCLUDE (' + @IncludedColumns + ')'

     RETURN @CreateScript
    END

    If you would like to dig deeper into this, here is a good resource: http://msdn.microsoft.com/en-us/library/ms345405.aspx.

    Wednesday, July 08, 2009 1:29:35 PM (Central Standard Time, UTC-06:00)  # 

    I listened to a .NET Rocks podcast recently on database design, and although I found most of the content near worthless … they did mention in passing some dynamic management views that are built into SQL Server that I was unaware of.  They turned out to be immensely helpful, so I thought I would share them.

    Some of the dynamic management views are designed to provide usage info about the indexes you currently have in place in a particular database.  Often times we create indexes that seem like they would be helpful for some functionality, but if the query that is actually ran against the database varies slightly from what we had in mind … SQL might not even be able to use the index.  There are also times were the data a particular index is built on is updated much more frequently than it is used, and is therefore causing a huge amount of maintenance overhead for an index that is rarely used … and is therefore robbing performance.

    Here is a simple T-SQL script I created that helps me find these types of indexes that should be candidates to be removed.  It finds all of the “non-system” indexes that are just simple indexes (not primary keys or unique constraints), which have to be updated at least twice as often they are used.

    SELECT OBJECT_NAME(S.object_id, S.database_id) AS 'Table',
      I.Name AS 'IndexName',
      S.user_seeks,
      S.user_scans,
      S.user_lookups,
      S.user_updates,
      S.last_user_seek,
      S.last_user_scan,
      S.last_user_lookup,
      S.last_user_update
    FROM sys.dm_db_index_usage_stats S
      INNER JOIN sys.indexes I ON I.object_id = S.object_id AND I.index_id = S.index_id
    WHERE S.database_id = DB_ID()
      AND NOT OBJECT_NAME(S.object_id, S.database_id) LIKE 'sys%'
      AND NOT OBJECT_NAME(S.object_id, S.database_id) LIKE 'fulltext%'
      AND NOT I.Name IS NULL
      AND NOT I.is_primary_key = 1
      AND NOT I.is_unique_constraint = 1
      AND S.user_updates > ((S.user_seeks + S.user_scans) * .5)
    ORDER BY S.user_updates DESC

    You can see in the WHERE clause the “* .5” text.  You can change that to be lower to be more stringent on how bad an index has to be before you consider removing it.

    For more info on index usage stats, see these MSDN articles:

    Monday, June 01, 2009 3:03:27 PM (Central Standard Time, UTC-06:00)  # 

    I listened to a .NET Rocks podcast recently on database design, and although I found most of the content near worthless … they did mention in passing some dynamic management views that are built into SQL Server that I was unaware of.  They turned out to be immensely helpful, so I thought I would share them.

    Some of the dynamic management views are designed to help you find “missing indexes.” While SQL Server is processing queries, it keeps track of some usage stats and this view allows you to see a list of indexes that SQL Server thinks would help performance if they were added.  (I think the stats are cumulative since the last time the server was rebooted.)

    Here is little T-SQL script I created that finds the top 10 missing indexes with the highest anticipated improvement for user queries.  It is based on an example from SQL Books Online, which is where the scoring algorithm comes from.  The score is simply meant to provide a relative comparison of the “anticipated cumulative improvement” implementing a particular index would have on query performance.

    SELECT TOP 10
      GS.avg_total_user_cost * GS.avg_user_impact * (GS.user_seeks + GS.user_scans) AS 'Score',
      OBJECT_NAME(I.object_id) AS 'Table_Name',
      I.equality_columns,
      I.inequality_columns,
      I.included_columns,
      GS.avg_user_impact,
      GS.avg_total_user_cost,
      GS.user_seeks,
      GS.user_scans
    FROM sys.dm_db_missing_index_details I
      INNER JOIN sys.dm_db_missing_index_groups G ON G.index_handle = I.index_handle
      INNER JOIN sys.dm_db_missing_index_group_stats GS ON GS.group_handle = G.index_group_handle
    WHERE I.database_id = DB_ID()
    ORDER BY GS.avg_total_user_cost * GS.avg_user_impact * (GS.user_seeks + GS.user_scans) DESC

    The scores are relative, and there doesn’t seem to be a hard rule for what the score should be in order to need to create the index … but I have set my personal threshold at 200.  If the “anticipated cumulative improvement” score is greater than 200, I create the related index … otherwise I don’t. 

    Creating too many indexes can hurt the performance of your server just as bad as not having enough indexes.  The podcast actually mentioned a company that wrote a script that would look at this view every night, and simply create all the indexes it suggested.  That turned out to be a horrible idea.  You need to be the ultimate judge of what your “score threshold” is, and whether it would be wise to create a particular index. 

    What is neat is that often times when I could look at the suggested indexes, and know exactly what functionality would be boosted by adding a particular index.  Most the time it was just something I had accidentally overlooked, and this view is designed specifically to help you find those things.  It can also help you find ways to optimize indexes for common ad-hoc queries that users might run on the server.  All-in-all this view is a great tool … when used wisely.

    For more info, see these MSDN articles:

    Monday, June 01, 2009 2:53:18 PM (Central Standard Time, UTC-06:00)  # 

    Some 64-bit printer drivers have issues that might cause you to get error messages saying one of these things:

    • Thunking Spooler APIs From 32 to 64 Process Has Stopped Working
    • Spooler Subsystem App Has Stopped Working

    I have ran into this issue a couple times already on Windows Vista x64, and both times it was pretty painful finding instructions that actually fixed it.  So, I wanted to post them here.  It seems like a lot of steps, but it is pretty quick ... and seriously this will work.  These are based on a post on the TechNet forum.

    1. If you have Acrobat, uninstall it.
    2. Reboot into SAFE mode.
    3. Delete all of the files in these folders:
      C:\Windows\System32\spool\drivers\x64\
      C:\Windows\System32\spool\drivers\W32X86\
      C:\Windows\System32\spool\PRINTERS
    4. Open Regedit, and make a backup of the registry
    5. Delete all subkeys (folders) in these locations:
      Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Printers
      Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows x6\Drivers
      Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows x64\Print Processors
    6. Close registry & reboot
    7. Go to Control Panel > Turn Windows features on or off, uncheck Windows Fax and Scan & reboot
    8. (Optional) If you actually use the "Windows Fax and Scan" stuff, you can go back into Control Panel > Turn Windows features on or off, re-enable Windows Fax and Scan, & reboot.

    At this point you should have a stable print spooler.

    To add your printers back in, get the absolute latest printer driver from your manufacturer's website (that supports the 64-bit version of Windows you are using).  Add each printer one at a time (rebooting and printing a test page in-between).

    Monday, May 18, 2009 10:04:22 AM (Central Standard Time, UTC-06:00)  # 

    On SQL Server 2008, when I went to create my first Maintenance Plan I got an error message that said:

    'Agent XPs' component is turned off as part of the security configuration for this server.  A system administrator can enable the use of 'Agent XPs' by using sp_configure.  For more information about enabling 'Agent XPs', see "Surface Area Configuration" in SQL Server Books Online.

    'Agent XPs component is turned off as part of the security configuration for this server

    To turn them on, all you have to do is run this script:

    USE master
    GO
    sp_configure 'show advanced options', 1
    GO
    RECONFIGURE
    GO
    sp_configure 'Agent XPs', 1
    GO
    RECONFIGURE
    GO

    After running the script you should see messages like the ones shown below:

    T-SQL to enable Agent XPs using sp_configure

    Friday, April 17, 2009 3:26:49 PM (Central Standard Time, UTC-06:00)  # 

    For those of us who might execute queries on the live/production SQL Server from time to time, it is important that we are constantly aware of whether we are about to execute a query in the context of the test or live environment.  Most the time I like to perfect a script in the test environment, and then change the connection to the live environment ... but even then a visual cue that would help me differentiate which one I am currently connected to would be a welcomed feature and might make me double-check that DELETE statement I am about to run to make sure it has a WHERE clause on it.

    Luckily, this feature is built into SQL Management Studio 2008.  Turns out it is right in front of your face, but I didn't ever notice it until I actually heard someone mention it and I went digging for it.  When you connect to a server, there are several options under the "Connection Properties" tab.  The bottom one is a checkbox for Use Custom Color.  Here is the MSDN's documentation for that property: "Select to specify the background color for the status bar in a Database Engine Query Editor window.  To specify the color, click Select. In the Color dialog box, select a predefined color from the Basic Colors grid or click Define Custom Colors to define and use a custom color."

    Test/Development Environment - Lower contrast green to indicate everything is cool

    Configure status bar color in SQL Management Studio based on connection

    Dev environment status bar color example

    Live/Production Environment - Dark red that would be hard to miss

    Configure status bar color in SQL Management Studio based on connection

    Production environment status bar color example
    Thursday, April 09, 2009 2:20:02 PM (Central Standard Time, UTC-06:00)  # 

    I was trying to set up dasBlog on my development laptop, which is a 64-bit machine, and got an error message that said "Could not load file or assembly 'BasicFrame.WebControls.BasicDatePicker' or one of its dependencies. An attempt was made to load a program with an incorrect format."

    Could not load file or assembly BasicDatePicker

    I searched online and found one blog post that had a suggested solution to "Download the x64 edition from http://www.basicdatepicker.com/ (manual install) and copy (override) the "BasicFrame.WebControls.BasicDatePicker.dll" from that zip file to your "bin" folder in the dasBlog web."  I went to that site and was going to download it, but it turns out they don't allow you to download that dll for free anymore.  Then I remembered IIS could run in a 32-bit compatibility mode, and modified the application pool settings to reflect those shown below.  Jackpot ... everything loaded correctly after that.

    Configure IIS Application Pool to run in 32-bit compatibility mode
    Sunday, April 05, 2009 4:49:45 PM (Central Standard Time, UTC-06:00)  # 

    I recently ran into an interesting issue where I had a .xls file linked in one of my web pages, and changed the content to a .xlsx file ... updated the link and it quit working.  Every time I clicked on it, I was sent to a 404 error ("Page Cannot Be Found").  I double checked the path in the link, that the file actually existed, that it had the correct permissions, etc. and everything seemed correct.

    Turns out you have to manually add the MIME Types for the new Office 2007 file extensions (e.g. docx, xlsx) in order for IIS 6 to render them.  I checked IIS 7 and it apparently doesn't have this same problem.  Here is how you add the new types:

    1. Go into IIS Manager, right-click on the Web Sites folder, and select Properties
      IIS Manager Web Sites Properties
    2. Click on the HTTP Headers tab and then click on the MIME Types... button
      Click on HTTML Headers then MIME types
    3. Add each of the MIME Types contained in the table below
      Add each MIME Type

    New Office 2007 MIME Types

    Extension MIME Type
    docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
    dotx application/vnd.openxmlformats-officedocument.wordprocessingml.template
    xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
    xltx application/vnd.openxmlformats-officedocument.spreadsheetml.template
    pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
    potx application/vnd.openxmlformats-officedocument.presentationml.template
    ppsx application/vnd.openxmlformats-officedocument.presentationml.slideshow
    Friday, March 20, 2009 3:37:08 PM (Central Standard Time, UTC-06:00)  # 

    This is based on excerpts from Wikipedia’s “Join (SQL)” article, which can be found at http://en.wikipedia.org/wiki/Join_(SQL). That article contains way more detail than most people really need to know, and also touches on about 20 different types of joins ... where in reality the majority of people just use two: inner and left outer. So I tried to filter the info down to just the basics that you need to know.


    A SQL JOIN clause combines records from two tables in a database. It creates a set that can be saved as a table or used as is. A JOIN is a means for combining fields from two tables by using values common to each.

    All subsequent explanations on join types in this article make use of the following two tables. The rows in these tables serve to illustrate the effect of different types of joins and join-predicates. In the following tables, Department.DepartmentID is the primary key, while Employee.DepartmentID is a foreign key.

    Inner Joins
    An inner join requires each record in the two joined tables to have a matching record. An inner join essentially combines the records from two tables (A and B) based on a given join-predicate. The result of the join can be defined as the outcome of first taking the Cartesian product (or cross-join) of all records in the tables (combining every record in table A with every record in table B) - then return all records which satisfy the join predicate.   This type of join occurs most commonly in applications, and represents the default join-type.

    Programmers should take special care when joining tables on columns that can contain NULL values, since NULL will never match any other value (or even NULL itself), unless the join condition explicitly uses the IS NULL or IS NOT NULL predicates.

    As an example, the following query takes all the records from the Employee table and finds the matching record(s) in the Department table, based on the join predicate. The join predicate compares the values in the DepartmentID column in both tables. If it finds no match (i.e., the department-id of an employee does not match the current department-id from the Department table), then the joined record remains outside the joined table, i.e., outside the (intermediate) result of the join.

    SELECT *
    FROM   Employee
           INNER JOIN Department ON Employee.DepartmentID = Department.DepartmentID

    Notice that the employee "Jasper" and the department "Marketing" does not appear. Neither of these has any matching records in the respective other table: "Jasper" has no associated department and no employee has the department ID 35. Thus, no information on Jasper or on Marketing appears in the joined table. Depending on the desired results, this behavior may be a subtle bug. Outer joins may be used to avoid it.

    Outer Joins
    An outer join does not require each record in the two joined tables to have a matching record. The joined table retains each record—even if no other matching record exists. Outer joins subdivide further into left outer joins, right outer joins, and full outer joins, depending on which table(s) one retains the rows from (left, right, or both).

    Left Outer Joins
    The result of a left outer join (or simply left join) for tables A and B always contains all records of the "left" table (A), even if the join-condition does not find any matching record in the "right" table (B). This means that if the ON clause matches 0 (zero) records in B, the join will still return a row in the result—but with NULL in each column from B. This means that a left outer join returns all the values from the left table, plus matched values from the right table (or NULL in case of no matching join predicate).

    For example, this allows us to find an employee's department, but still to show the employee even when their department does not exist (contrary to the inner-join example above, where employees in non-existent departments are excluded from the result).

    Example of a left outer join, with the additional result row italicized:

    SELECT *
    FROM   Employee
           LEFT OUTER JOIN Department ON Employee.DepartmentID = Department.DepartmentID

    Wednesday, February 18, 2009 8:35:47 AM (Central Standard Time, UTC-06:00)  # 

    A couple weeks ago at PDC, the guys at DevExpress announced a new product, CodeRush Xpress, which is a free version of their refactoring tool for C#.  It actually does more than pure refactoring, but is essentially a Visual Studio productivity add-in targetted for C# developers.  I had sat through a couple of Mark Miller's demos (because those guys seem to be at every convention I have ever been to), but for some reason I got a little more interested in it this year at PDC and watched several demos and asked lots of questions (even before the announcement of the free version).

    I installed CodeRush Xpress before I even left Los Angelos, but Visual Studio began crashing on me for unknown reasons.  At various times, a popup would appear saying a problem occurred, and Visual Studio had to restart.  This got old really quick, especially after it caused me to loose some work a couple times.  I actually went to uninstall it, but hit cancel because I thought I would try to Google a fix or workaround one more time ... because I really thought the product would save me a lot of time if I could get it stable.

    I stumbled upon a blog post that had a questionable proposed fix, but I tried it anyway ... and although it hasn't completely eliminated the problem, I do think it has decreased the number of times VS crashes.

    1. Go to the Visual Studio ".exe" file, right-click on it, and go to "Properties"
    2. Go to the "Compatibility" tab, and check "Disable Visual Themes" ... then click "OK"
      Disable Visual Themes for program compatability

    This will disable some of the fancy gradients and shadows in the IDE, but really it isn't anything major.  I actually think it makes Visual Studio a little more responsive ... and keeps CodeRush from crashing.  I think this is due to some of the conflicts CodeRush has with Vista's themes, because the CodeRush UI is such an elaborate and graphical add-in.  I didn't see anyone mention they had this problem on non-Vista machines.

    UPDATE 1/6/2009: This change did seem to help some, but Visual Studio still continued to crash periodically.  It was often enough that it  caused me to loose some code every now and then, or restart Visual Studio continuously.  So, I uninstalled CodeRush Express.  But I missed some of the refactoring tools so much that I thought I would give just "Refactor! Pro" a chance, and sure enough it has yet to crash my machine in a full month evaluation.  I guess since it doesn't do "code generation" in such a way that it modifies the behavior of your program (but just does refactoring) ... it might be a little more straight-forward and stable than some of the features included in CodeRush.

    Wednesday, December 10, 2008 1:20:00 PM (Central Standard Time, UTC-06:00)  # 

    I recently upgraded my AT&T Tilt (aka HTC 8925) to Windows Mobile 6.1, and after a couple days I noticed the phone started getting really hot and the battery life really took a nose-dive.  I figured out that ActiveSync was having some issues when it tried to sync my calendar events, which caused it to just keep running forever. 

    For example, I might have 5 new calendar events that needed to actually be copied from Exchange to the device, and it would tell me it was looking for changes, then show "Downloading" ... "Calendar: 0/5" ... "Calendar: 3/5" ... "Downloading" ... "Calendar: 5/10" ... "Downloading" ... "Calendar: 10/15" ... and keep going for hours.  It would get into the thousands (e.g. "Calendar: 3850/3855").

    Failed Fix #1: At first I thought there might be something wrong with the device, and I happened to have another working Tilt I could test with.  So I configured the other phone, and synced it with my account and sure enough it worked properly ... for a couple days, then had the same problem.

    Failed Fix #2: Then I thought I might have a "quirky" or "corrupted" recurring calendar event that didn't have an end date set, and it was downloading them into the year 2500 or something.  So I found all my recurring calendar events, which you can do in Outlook by searching your calendar for "isrecurring:true") and changed them all to either expire after a certain number of occurrences or on a certain date.  None of them were left as "No end date".  I tried syncing ... and still got the same issue with the infinite sync.

    Although I didn't really know what to Google for an answer, I finally found a couple forum posts where people were having similar issues and had uncovered some more details about the issue:

    New Calendar Event on Tilt + Exchange ActiveSync = Endless ActiveSync loop, battery drain
    New Calendar Event in Outlook + Exchange ActiveSync = Event Synced to Tilt with no issue

    Most people seem to think the issue is related to a bug with daylight savings time in Windows Mobile 6.1.  There are several different proposed solutions you might find, but the most common one that seems to have the most people saying it worked for them is this:

    1. On your device, go to your "Clock" and change your the time zone to a region without DST (e.g "GMT-7 Arizona")
    2. Sync your device
    3. Change back to your normal time zone
    4. Sync your device, and it should complete without the endless loop

    I personally tried this one, and it seemed to work for me ... although some people commented that it resulted in only a temporary fix.  If you add another event from your device, and then try to sync the issue might reappear.  In that case you just perform the steps above again, and should be good to go until the next time you add an event or Microsoft fixes the bug.  I did find one hotfix Microsoft published to address issues with DST on Windows Mobile Devices.  I installed it, but haven't done enough testing yet to know if it is a permanent fix to this problem.

    UPDATE: The "fix" I described in this post worked for me several times, but then sometime in February it stopped working and ActiveSync would get in an endless loop everytime it tried to sync calendar events.  So I just went without syncing calendar events for a couple weeks, then decided it was ridiculous ... and bought an iPhone.  I can't tell you how much I love it.  It isn't hype ... I am one of the biggest PCs I know, but the iPhone really is one of the greatest breakthroughs in electronics in a long time.  The user experience is incredible.

    Tuesday, December 09, 2008 3:06:23 PM (Central Standard Time, UTC-06:00)  # 

    I have often times wanted to write something like CheckBoxList1.Items.Where(i => i.Selected).  Turns out you can't do that, because the Items property is a "specialized" collection.  But to get it to work all you have to do is cast it to a generic list of ListItems like this:

    var selectedItems = CheckBoxList1.Items.Cast<ListItem>().Where(i => i.Selected)

    The example above uses method syntax, but you could also use query syntac like this:

    var selectedItems = from i in CheckBoxList1.Items.Cast<ListItem>()
                        where i.Selected
                        select i;

    Monday, November 17, 2008 1:52:49 PM (Central Standard Time, UTC-06:00)  # 

    Since performing a hard reset is completely different on each device (and somewhat hard to find instructions on how to do), here are the steps you need to restore the factory defaults on each device.  Here is a fairly clear disclaimer from the AT&T Tilt's user manual that explains what a "hard reset" means:

    Hard Reset (a.k.a. full reset): A hard reset should be performed only if a normal reset does not solve a system problem. After a hard reset, the device is restored to its default settings - the way it was when you first purchased it and turned it on. Any programs you installed, data you entered, and settings you customized on your device will be lost. Only Windows Mobile® software and other pre-installed programs will remain.


    AT&T Tilt (a.k.a. HTC 8925)

    1. Press and hold the left SOFT KEY and the right SOFT KEY, and at the same time, use the stylus to press the RESET button at the bottom of your device.
    2. Release the stylus, but continue pressing the two SOFT KEYs until you see a message on the screen saying "This operation will delete
      all your personal data, and reset all settings to the manufacturer default settings. Press SEND to restore manufacturer defaults, or press any other button to cancel.
    3. Release the two SOFT KEYs, and then press the button on your device.

    Alternatively you can go to Start > Settings > System > Clear Storage, and it will do the same thing.


    iPhone 3G

    1. Start with the device on
    2. Go to SETTINGS > GENERAL > RESET > ERASE ALL CONTENTS AND SETTINGS

    NOTE: This one takes abnormally long to reset everything back to the factory defaults (around an hour).


    Palm Treo Pro

    1. Start with the device on
    2. Take off the back cover
    3. While holding down the END button use to stylus to tap the RESET button (located on on back in the bottom left)
    4. Keep holding down the END button until you see the "Erase all data?" prompt.
    5. Press Up.


    HP iPAQ 910

    1. Start with the device off
    2. While holding down the OK button (located on the right side) and the VOICE COMMANDER button (located on the left side) use the stylus to press the RESET button (located on top)
    3. Release the OK and VOICE COMMANDER buttons when "Clean Boot" is displayed on the screen.


    Motorola Q 9H Global

    1. Start with the device off
    2. Hold down the * and E keys for 5 seconds while turning on the device
    Thursday, October 09, 2008 1:25:57 PM (Central Standard Time, UTC-06:00)  # 

    I find myself doing the same series of steps in VS 2008 quite a bit, and decided to give Visual Studio's macros a shot at helping with some of those mundane, mindless tasks.  The "Record Macro" functionality (Ctrl+Shift+R) works pretty well ... except when I tried to work in a project build.  While messing with it I got a few different types of exceptions (e.g. invalid arguments, the COM object called couldn't return), but I eventually found the little documentation there is on the DTE object model, and combined some ideas from examples on blogs and whitepapers to come up with something that works.

    You can leverage the DTE.Solution.SolutionBuild a couple different ways.  You can build a single project (like in the example below) or the whole solution using DTE.Solution.SolutionBuild.Build(True).  You can also do other things like clean the solution with DTE.Solution.SolutionBuild.Clean(True).  Here is a link to the full reference of the SolutionBuild members.

    In the example below I explicitly set the build configuration to "Debug", but you could also leverage DTE.Solution.SolutionBuild.ActiveConfiguration.Name to build it in whatever configuration is currently selected in the IDE.

    ...
    DTE.Solution.SolutionBuild.BuildProject("Debug", "C:\inetpub\wwwroot\ProjectFileName.csproj", True)
    
    ' Check to see if there build errors, and if there were alert the user ... otherwise continue
    ' NOTE: LastBuildInfo returns an integer representing the number of projects that failed during
    ' the last build.
    If DTE.Solution.SolutionBuild.LastBuildInfo > 0 Then ' There were build errors ... alert the user by popping up the "Output" window DTE.Windows.Item(Constants.vsWindowKindOutput).Activate() Else ' Do more stuff here is needed End If ...
    Tuesday, October 07, 2008 9:29:29 AM (Central Standard Time, UTC-06:00)  # 

    This is probably the first of many topics I will post about jQuery.  Since I found myself writing more and more JavaScript these days (especially for DOM manipulation resulting from AJAX calls), I am going to try to start leveraging the jQuery library. 

    What is jQuery?  As the creator of jQuery, John Resig, says in the book jQuery in Action "it's all about simplicity.  Why should web developers be forced to write complete, book-length pieces of code when they want to create simple pieces of interaction? ... When I first set out to create jQuery I decided that I wanted an emphasis on small, simple code that served all the practical applications that web developers deal with day to day."  As Scott Hanselman said in one of his recent podcasts about jQuery, it's syntax is "very terse."  That is a simple way of saying it is neat, compact, elegant, and devoid of needless, verbose constructs.  It is also another tool that helps abstract away the browser differences that typically leads developers to writing "if(IE)" type code.

    jQuery offers a lot of benefits, and I will probably cover some more in future posts ... but one of the fundamental features is a new type of event you can attach to.  It is the $(document).ready event, and can be used in places where developers might have previously used something like window.onload.  Through painful experiences I have learned that the window.onload event doesn't always fire when you expect that it would.  The onload event doesn't fire until all binary content has been downloaded, which includes images and any other type of content.  On the other hand, the ready event provided by jQuery fires as soon as the DOM is ready to be traversed and manipulated ... which in extreme instances might be seconds before the onload event.

    Here is a little blurb from the jQuery documentation about the ready event:

    This is probably the most important function included in the event module, as it can greatly improve the response times of your web applications.

    In a nutshell, this is a solid replacement for using window.onload, and attaching a function to that. By using this method, your bound function will be called the instant the DOM is ready to be read and manipulated, which is when what 99.99% of all JavaScript code needs to run.

    ... Please ensure you have no code in your <body> onload event handler, otherwise $(document).ready() may not fire. ... You can have as many $(document).ready events on your page as you like. The functions are then executed in the order they were added.

    Here are a couple simple example of how you can use it:

    $(document).ready(function()
    {
        // Your code here
    });

    /*************************/

    function DoSomething()
    {
        // Your code here
    }
    $(document).ready(DoSomething);

    /*************************/

    // This example shows how something that was previously tied to the
    //  window.onload event would look if tied to the document.ready event.

    window.onload = function() { alert("window.onload event fired!"); };
    $(document).ready(function() { alert("document.ready event fired!"); })

    Thursday, October 02, 2008 3:09:55 PM (Central Standard Time, UTC-06:00)  # 

    If you have ever used Visual SourceSafe, you know it allows seemless integration with Visual Studio ... but has its own laundry list of issues and complexity associated with it.  It seems that the fundamental, daily tasks are sometimes intuitive, but move past those concepts and nothing is straight-forward.  One of those topics is branching a solution or project in SourceSafe, which I will cover here.  Most of the info in this post is based on stuff I read in Visual SourceSafe 2005: Software Configuration Management in Practice.

    Two Methods To Branch Code
    There are actually two common methods to branch code in SourceSafe: "Share, Pin, & Branch" and "Share & Branch."  Here is a quote from the aforementioned book that describes the differences:

    Using the Share, Pin & Branch method can save database space because new files aren't created until you branch them.  Also, it can be helpful in checking which files have been modified and which files are unchanged since the configuration was created.

    This method has the disadvantage of having to manually branch the files in Visual SourceSafe Explorer as needed.  Another disadvantage is that if you accidentally unpin some files instead of branching them, they will become shared with the files in the mainline where development is being conducted.  This can affect both the mainline and the development line.

    The alternative is to use the Share and Branch method and branch all files from the start, when creating a new maintenance codeline. (p274)

    To me, it seems like the only time you would consider using Share, Pin & Branch would be scenarios when the you had a project with an enormous amount of files, and you had limited disk space.  Share & Branch would probably be the recommended method for 90% of software projects out there, so that is what I will discuss here.

    Branching the Maintenance Line on Creation

    1. Before proceeding you should make a backup of the SourceSafe database.  To learn how to do that see this blog post.
    2. In Visual SourceSafe Explorer, right-click on the folder containing the main line's files and choose "Show History".


    3. In the history window, click "Share"


    4. The new branches must be created outside the solution's hierarchy top folder.  In this example, my solution file is in the $/StuffThatJustWorks.root/StuffThatJustWorks folder.  By default Visual SourceSafe will try to structure your project hierarchically like this (with a ".root" folder), but you can override that behavior.  If you do have it set up this way, just select the ".root" folder.  Otherwise you will have to create new folder in SourceSafe to hold the brached code.  Also, be sure to check "Branch after share."


    5. In the "Share" dialog window, enter the name for the new project, select "Recursive", and enter some optional comments.

    After you click "OK" on that last window, the new codeline will be created.  The screenshot below shows how the newly created branch looks in this example.  The "Release 1" project would be the maintenance codeline, and the original project would be my main codeline.  I can now start implementing new features in the main codeline, but if there are bugs found in the "Release 1" code I can easily fix those in that codeline and release an updated version (e.g. Release 1.1) that doesn't contain the new, possibly unfinished features of the main codeline.

    Sunday, July 20, 2008 12:51:36 PM (Central Standard Time, UTC-06:00)  # 

    Turning on the compression feature in IIS (Internet Information Services) will compress the server's responses, and therefore improve perceived performance while reducing bandwidth-related charges.  Sounds like a good thing, right?  I believe this feature wasn't enabled by default in IIS 6, but it looks like it is at least enabled by default in IIS 7 for static files.  I will walk through how you would set this up in both versions of IIS.


    IIS 6
    In IIS 6, you can only enable compression for the web server as a whole ... not individual sites.  To enable compression, open IIS 6, and then right-click on the "Web Sites" folder and choose "Properties."

    IIS 6 Web Sites Properties

    Then find the "Services" tab, and from there you can easily set a few simple options to enable native compression in IIS.

    Configure Http compression settings in IIS 6

    There is also gzip compression build into IIS that enables you to compress ASPX pages, but the team didn't make it very easy to find.  In fact, I am pretty sure there is no way to enable it in the GUI.  But I followed the instruction provided at http://support.microsoft.com/kb/322603, and even though the instructions on that post say "To enable IIS 5.0 to compress .aspx pages, follow these steps", I have personally tried it on two web servers running IIS 6, and monitored the responses they were sending before and after using the amazing YSlow plugin for Firebug, and sure enough it showed that before I ran the commands the ASPX/HTML content wasn't gzipped, but after it was.  This brought the page I was monitoring from a 23K download on the client to 8K.  Here are the instructions I followed:

    1. Open a command prompt.
    2. Type net stop iisadmin, and then press ENTER.
    3. Type cd C:\InetPub\adminscripts, and then press ENTER.
    4. Type the following, and then press ENTER:
      CSCRIPT.EXE ADSUTIL.VBS SET W3Svc/Filters/Compression/GZIP/HcScriptFileExtensions "asp" "dll" "exe" "aspx"
    5. Type the following, and then press ENTER:
      CSCRIPT.EXE ADSUTIL.VBS SET W3Svc/Filters/Compression/DEFLATE/HcScriptFileExtensions "asp" "dll" "exe" "aspx"
    6. Type net start w3svc, and then press ENTER.


    IIS 7
    In IIS 7 you can specify compression settings for all web sites, or individual sites.  I will show how to do it for all web sites, although it is straight-forward to extend these same steps to be site-specific.  First open IIS 7, and right in the default view you can see an icon for the "Compression" feature.

    IIS 7 Compression Feature

    Click on that feature, and you will see the screen like the one shown below.  There are a few different options than those available in IIS 6.  These were the default settings on my machine.

    Configure IIS 7 compression settings

    Notice that there is an alert that says "The dynamic content compression module is not installed," and the checkbox for that feature is disabled.  That module wasn't installed by default on my machine, but you can easily add it by going to Control Panel > Programs and Features > Turn Windows features on or off, and then selecting the item shown below.  The next time you open up IIS that checkbox will be enabled for you to select, and you won't see that alert message any longer.

    Enable dynamic content compression in IIS 7
    Saturday, July 19, 2008 5:16:58 PM (Central Standard Time, UTC-06:00)  # 

    I have had some trouble before trying to configure IIS 7.0 so that I could run multiple sites at one time on my development machine.  So I just wanted to share some quick notes on how I got it working (mainly so I could refer back to them later).

    1. Open IIS, and add each site.  The binding for each site should including a unique host name that will be used to access the site (e.g. localSTJW).  The rest of the default settings are fine.
      Edit Site Bindings

      Configured sites in IIS 7

    2. Edit the "hosts" file, and add the appropriate host name of each site and associate it with 127.0.0.1.  The file should be located at "C:\Windows\System32\drivers\etc".
      Edit hosts file to add site name

    You should then be able to navigate to any of those sites by just entering the host name in the browser (e.g. http://localSTJW).

    Wednesday, July 16, 2008 9:42:00 AM (Central Standard Time, UTC-06:00)  # 

    I find myself needing to do this from time to time, and I can never remember how you are supposed to do it.  It isn't hard ... but I think I always get confused because there was a different naming convention back before .NET 2.0.  However, this example shows how it should would in 2.0, 3.0, or 3.5.  The main reason I have to use this is if I have a user control inside a repeater or some other type of databound list-type control, and I need to access it in a method in the code behind (e.g. ItemDataBound).  Here is a very simple example showing a scenario I might need to do this, and how you can figure it out:

    Front-End

    ...
    <%@ Register Src="~/Controls/Calendar/AgendaViewDay.ascx" TagName="AgendaViewDay" TagPrefix="uc" %>
    ...
    <asp:Repeater ID="rptEvents" runat="server" OnItemDataBound="rptEvents_ItemDataBound">
        <ItemTemplate>
            <uc:AgendaViewDay ID="AgendaViewDay1" runat="server" />
        </ItemTemplate>
    </asp:Repeater>

    Back-End

    protected void rptEvents_ItemDataBound(object sender, RepeaterItemEventArgs e)
    {
        if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
            // Bind the events to the day control
            Controls_Calendar_AgendaViewDay thisDayControl = 
                
    (Controls_Calendar_AgendaViewDay)e.Item.FindControl("AgendaViewDay1"); thisDayControl.BindEvents(eventsToDisplay); } }

    As you can see, in this scenario you have to explicity call out the data type of the user control so the compiler knows it has a "BindEvents" method.  The type is "Controls_Calendar_AgendaViewDay", which is a pretty obvious naming convention.  Here is the path I used when registering the user control on the page: "~/Controls/Calendar/AgendaViewDay.ascx".  By default, Visual Studio will name a page or user control's class based on it's original location in the folder structure with this type of naming convention (pretty much just replacing "/" with "_").  But beware, when you rename a file or a containing folder Visual Studio won't go back and update all of the class names for you, because that could possibly break some of your references.  So, one sure-fire way to determine the class name is to navigate to the user control's ascx code-behind file and look at the class definition, which will be something like this:

    ...
    public partial class Controls_Calendar_AgendaViewDay : System.Web.UI.UserControl
    {
    ...
    Thursday, June 12, 2008 6:41:17 PM (Central Standard Time, UTC-06:00)  # 

    Ever noticed that setting the MaxLength for a MultiLine TextBox does absolutely nothing? According to documentation on MSDN "this property is only applicable when the TextMode property is set to TextBoxMode.SingleLine or TextBoxMode.Password." You can just put a RegularExpressionValidator on the textbox that makes sure the length is under a certain threshold, but that isn't always the most user-friendly way.  On single line textboxes, the browser simply doesn't allow the user to insert any more letters once they have reached the max length ... which is pretty intuitive for users to understand "oh, I need to shorten this down a little."  Here is some JavaScript that will create that same type of effect on MultiLine textboxes that users are familiar with on single line textboxes.

    Here is the JavaScript function:

    function MaxLength(field, maxLength)
    {
        if (field.value.length >= maxLength)
            field.value = field.value.substring(0, maxLength - 1);
    }

    Then you can add an "onKeyDown" event to the TextBox in the aspx file like this:

    <asp:TextBox ID="txtMultiLine" TextMode="MultiLine" onKeyDown="MaxLength(this,200);" runat="server" />

    ... or add it programmatically in the CodeBehind like this:

    txtMultiLine.Attributes.Add("onKeyDown", "MaxLength(this,200);");

    Note: If it is critical that a user not be able to submit a form with more than the maxLength number of characters in the multiline textbox, then you also need to add a RegularExpressionValidator to double-check the length ... just in case the user has disabled JavaScript on their browser.  This will obviously only work if they have JavaScript enabled, and is more designed to make this a more pleseant and familiar expreience for the end-user.  Here is an example of the RegularExpressionValidator you might drop on the page as well:

    <asp:RegularExpressionValidator ID="revMultiLine" ControlToValidate="txtMultiLine" ValidationExpression="(.|\n){1,200}" ErrorMessage="Please enter 200 characters or less" runat="server" />

    Friday, May 23, 2008 2:38:51 PM (Central Standard Time, UTC-06:00)  # 

    I was trying to figure out how to make the rows in a grid alternate background colors in a SQL Server Reporting Services (SSRS) report using Report Builder/Designer in Visual Studio (like the example shown below).  I searched the internet for a little while and pretty much just found people who said Report Designer was a very simple tool and didn't allow you to do "advanced customizations" like this ... bull crap.  It is a simple tool (with a few quirks) ... but it is pretty extendible too.  There are a ton of places where it allows you to use "Expressions", which are extremely powerful.  Thats what I used to make this alternating background thing work.

    Alternate backgrounds in SSRS report

    First select the table row you would like to apply this type of alternative style to in the designer.  Then in the properties window for that TableRow object you should see a "BackgroundColor" property ... click the drop down and choose "<Expression...>":

    Set TableRow BackgroundColor property to alternate color in report

    Then, all you have to do is write an expression like this and replace the colors with the ones that you want.  The expression simply evaluates the current row number mod 2, which will be one for row numbers that are odd (e.g. 1,3,5) and zero for row numbers that are even (e.g. 0,2,4).  It compares the result for the current row and returns the appropriate background color ... simple stuff:

    Write custom expression to set row background color

    Wednesday, May 07, 2008 1:41:41 PM (Central Standard Time, UTC-06:00)  # 

    I have our production web servers set up to email me notifications when unhandled exceptions occur, and if a site is publicly accessible crawlers, spiders, and other types of search bots can make this a pain.  Most crawlers try to go through pages they have in their index, and pull new content.  If you have promo-type pages that are only up for a limited amount of time, they will try to access that URL later on after it is gone, and bam ... another email in my box.

    There are CAPTCHA components out there, but they aren't really appropriate for this scenario ... I don't want site users to have to read some squiggly letters and type those out before the site sends me an error email.  So I have written some pretty simple code that helps me filter out if the request came from a crawler.  My 10 second Goggle on the subject didn't turn up much, so maybe this will help someone else out there.  If you figure out a way to improve it, please post some comments or contact me.

    The solution is two part.  I have a regular expression pattern stored in the web.config, and then a single "IsBot" method that returns true if the current request is from a crawler and false otherwise.  I store the pattern in the web.config because it is still evolving, and probably far from "all encompassing" ... but when an error email slips through that is from a bot, I will look at the agent it presents itself as and then add a new keyword to the pattern to detect that crawler in the future.  If I was to guess, I would say the pattern posted here probably detects over 90% of hits from crawlers ... as of today.

    Current pattern in web.config's AppSetting setting section:

    <add key="botRegex" value="bot|crawler|spider|slurp|ask|teoma" />

    C# IsBot method:

    /// <summary>
    /// Returns true if the current request is from a bot crawling the site, and false otherwise.
    /// </summary>
    public static bool IsBot
    {
        get
        {
            // If this method can't access the current context that means the executing thread doesn't have access
            // to the current request's properties ... since we can't pull any agent information we have to assume
            // this is not a bot.
            if(HttpContext.Current == null)
                return false;
            
            string HTTP_USER_AGENT = "";
            if (HttpContext.Current.Request.ServerVariables["HTTP_USER_AGENT"] != null)
                HTTP_USER_AGENT = HttpContext.Current.Request.ServerVariables["HTTP_USER_AGENT"].ToLower();
    
            // Check to see if the user agent field contains any of the terms in the botRegex set in the web.config
            string expression = ConfigurationManager.AppSettings["botRegex"];
            Regex botRegex = new Regex(expression);
            return botRegex.IsMatch(HTTP_USER_AGENT);
        }
    }
    Tuesday, April 29, 2008 2:45:22 PM (Central Standard Time, UTC-06:00)  # 
    ...
    using System.Globalization;
    using System.Threading;
    ...
    
    string thisPhrase = "HELLO WORLD!";
    lblOutput.Text = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(thisPhrase.ToLower());

    That's it ... this example would convert the string to "Hello World!"  Pretty easy, huh?

    Thursday, April 24, 2008 12:04:48 PM (Central Standard Time, UTC-06:00)  # 

    A lot of times I have user controls or even entire pages that are customized for the authenticated user ... but the output rendered by that user control or page doesn't change that often.  It makes sense to cache this information, but I still want it to be personalized for each user.  For example, if you have a menu on every page of a site that only contains links relevant for the user that is signed in ... you can actually use the OutputCache directive to allow ASP.NET to cache the output of that control or page for a particular duration for that user, instead of having to recreate the menu on every page load.  This can save a significant amount of processor cycles and memory, and make your site more scalable.  Here is how you do it:

    1. Add this directive to top of your user control (.ascx) or page (.aspx) file:

      <%@ OutputCache Duration="60" VaryByParam="none" VaryByCustom="UserID" Shared="true" %>

      Duration: The amount of time in seconds to cache the content
      VaryByParam: Indicates whether the content should vary based on the QueryString
      VaryByCustom: The custom value used to determine if the content is already in cache
      Shared: Whether multiple pages should be able to access the cached content (default is false)

    2. Then create a new method in the Global.asax file that defines how to find the value for the custom parameter:

      public
      override string GetVaryByCustomString(HttpContext context, string arg)
      {
          if (arg == "UserID")
              return context.User.Identity.Name;
          
          return string.Empty;
      }

    The GetVaryByCustomString method is called very early in the life-cycle of a request ... even before the SessionState has been populated, so you can't use a custom value from the SessionState to vary the content.  At least that is 99% true.  If you are using the OutputCache inside a user control, the SessionState is actually available when the method is called.  So if you ran into this problem on a particular page, one (not very elegant) solution would be to move all the content into a user control and then OutputCache that control so you can access the SessionState value in the GetVaryByCustomString method.

    Another use for customized OutputCache is on things like product details pages where the ProductID is in the QueryString.  Here is what the URL for a page like this might look like: Details.aspx?ProductID=34.  In this scenario you can OutputCache like this:

    <%@ OutputCache Duration="7200" VaryByParam="ProductID" %>

    OutputCache is the most effecient way to utilize caching in ASP.NET.  I also use the HttpRuntime.Cache API frequently to cache data, but when most consultants come on-board to help make a site more scalable ... they start with OutputCache.  For more info, check out the OutputCache documentation, or this article on Caching Portions of an ASP.NET Page.

    Tuesday, April 22, 2008 2:06:58 PM (Central Standard Time, UTC-06:00)  # 

    We did some testing with replication a couple months ago to see if we could make use of it on one of our projects.  Some other complications caused us to choose a different path, so we tried to remove replication from the database through SQL Management Studio's UI.  It seemed to clear out some stuff ... but still left hundreds of system views and procedures related replication.  I wasn't sure how to remove them without risking some instability in our sytem, and today I learned just how easy this is ...

    EXEC sp_removedbreplication 'databaseName'

    Note: This is intended for SQL Server 2005.  Here is a link to the SQL Books Online reference to this procedure.  Microsoft does have a disclaimer that says "This procedure should be used only if other methods of removing replication objects have failed. For more information about these methods, see Removing Replication."

    Monday, April 07, 2008 1:59:16 PM (Central Standard Time, UTC-06:00)  # 

    By creating a bootable USB flash drive you can install a fresh OS on a machine very quickly (I personally installed fresh copy of Vista Ultimate in about 8 minutes ... no lie).  If I had done the same install from the DVD install disc it would have taken light-years (that might be an exageration).  If you are installing Windows on more than one machine, this is a no-brain decision on how you should do it.  Although these instructions were written for Windows Vista, the process should be the same for Windows 7 or any other version of Windows you are installing (except maybe Windows 3.1).

    First, you will want to obtain a USB 2.0 flash drive that has these features:

    • Large storage capacity: The ISO for Windows Vista x64 is around 3.6GB and the x86 version is around 2.5GB.  So you will probably need one of the 4GB flash drives.
    • Fast read speed: If you really want to get the most out of this install method, you will need a flash drive with good read speed.  I happened to already have the 8GB Corsair Flash Voyager GT, which has a blazing average read speed of 33.8MB/s (I think that is the fastest today).  Most low-end or older flash drives have a read speed closer to 10MB/s or less.  I took a quick look at NewEgg.com, and it looked like they had an A-Data 4GB USB flash drive for under $50 that had 30MB/s read speed.  The faster the better ... but I bet even installing from a "slower" USB flash drive might still be quicker than DVD.

    NOTE: You will be completely wiping the contents of the USB flash drive, so if you have stuff on it that you want to keep copy it somewhere else.

    How To Set Up The USB Flash Drive

    1. Connect the USB flash drive with your computer
    2. Determine which drive number and letter your computer automatically assigned the device.  On a Vista machine you can do this by typing in "Computer Management" in the search textbox in the start menu and hitting ENTER.  Click on "Disk Management" in the navigation window on the left and find your drive.  In the screen shot shown below my drive was assigned to Disk 3 and is the I: drive.
      Determine device drive number using Disk Management

    3. All that is left now is running the appropriate commands to format the USB flash drive and copy the install files over from the Vista install DVD.  Here is a screenshot of what commands you would need to run in this situation:
      diskpart commands to create bootable thumbdrive

    Note: The last command in the example above copies the files from my DVD drive, which is "E:" ... you might need to replace that with the appropriate letter of your DVD drive.

    I originally found some of this content on this good blog post by Kurt Shintaku:
    http://kurtsh.spaces.live.com/blog/cns!DA410C7F7E038D!1665.entry

    Thursday, March 06, 2008 4:10:04 PM (Central Standard Time, UTC-06:00)  # 

    I don't really like how Microsoft changed where the "Start Menu" folder resides for a particular user in Vista.  It was pretty easy to find in XP, but seems "hidden" in Vista.  After I have a fresh install of an OS, I like to customize what all is included in my start menu ... especially with all the crap Vista puts in there by default like Windows Calendar, Windows Contacts, Windows Mail, etc.  You can obviously delete those one by one directly in the start menu, but I like to make changes inside the folder ... because it is easier to delete or move things in mass.  To do this in Vista you will have to find the "magic" folder, and here's where it is:

    C:\Users\Username\AppData\Roaming\Microsoft\Windows\Start Menu

    Note: AppData is a hidden folder, so you can either change your folder options to show hidden folders ... or you can just navigate to user's folder and then append "\AppData" to the path like this:

    Customize and Organize Windows Start Menu

    Not all of the menu items are contained in that user's folder ... some are common to all users and those are stored in another hidden place:

    C:\ProgramData\Microsoft\Windows\StartMenu

    Friday, February 29, 2008 7:56:36 AM (Central Standard Time, UTC-06:00)  # 

    IE printer header and footer

    I have found a ton of scenarios when it is helpful to either edit or completely remove the header and footers that are put on a page when you print from Internet Explorer.  It’s sometimes convenient to have the URL and number of pages printed on every page … but not normally.  The URL is too long to be displayed half the time anyway.  And if whatever you are printing is supposed to be a “printer-friendly version” of the page, or used as client-facing business document then that stuff just looks unprofessional and unpolished. 

    Remove Headers & Footers:

    1. Open Internet Explorer
    2. Click on the little arrow to the right of the printer icon and go to “Page Setup…”
      Configure page setup in IE
    3. This will display the screen shown below.  Just delete all the text from the boxes in the “Headers and Footers” section, and click OK.
      Default page setup configuration in IE

    Customize Headers & Footers:
    So what does all that stuff shown in the textboxes above mean anyway?  I think Microsoft could have definitely done a better job on this, because it is pretty cryptic.  But I guess this is the way it has pretty much looked since IE 3 or so, and since it isn’t a section a lot of people go … they haven’t done much improvement on it over the past 10 years.  So here is what all of that stuff means:

    Code Description Example
    &w Window Title StuffThatJustWorks.com - How To: Customize or …
    &u URL http://www.stuffthatjustworks.com/2008/01/03…
    &d Date (short format) 2/4/2008
    &D Date (long format) Monday, February 04, 2008
    &t Time (12 hour format) 4:48:51 PM
    &T Time (24 hour format) 16:48:51 PM
    &p Current page # 1
    &P Total # of pages 2
    &b Separate in new section

    So by default, the header is set to “&w&b Page &p of &P”.  This means it will print the Window Title (i.e. whatever is shown in the top title bar of the internet explorer window), and then in a different section (which will be right aligned) it will print the current page number, followed by the text “ of “, followed by the total number of pages.

    Say we wanted it to print the current date on the left side of the header instead of the Window Title.  All we need to do is replace “&w” with “&D”.  We can also choose to divide the header into three sections instead of just two.  All you need to do is add the new field you want to display, and a “&b” code along with it.  For instance, if we want the date on the left, time in the center, and then current page on the right, we might use “&D&b&t&b&p”.  This would display something like this:

    Default Page Print Header

    You can also just put some static text in that you want to show up on every single page.  For instance, maybe instead of the current page number on the right hand side, we would prefer to have “Visit StuffThatJustWorks.com” printed there.  To do that we might use ““&D&b&t&bVisit StuffThatJustWorks.com”, which would look like this:

    Custom Page Print Header

    If you ever want to set the header and footer back to what they were by default, just copy and paste these values back in:

    • Default header:  &w&b Page &p of &P
    • Default footer:  &u&b&d

    For more information on this topic, here is a good reference that goes into more detail: http://www.febooti.com/products/iezoom/print-web/printing-codes-footer-header.html.

    Saturday, February 16, 2008 8:30:46 PM (Central Standard Time, UTC-06:00)  # 

    I had a particular console application that I used to edit in Visual Studio 2005, which targeted the .NET Framework 2.0.  I recently “converted” and updated that application so that I could edit it using Visual Studio 2008, and also changed it to target the .NET Framework 3.5 so that I could use some of the new features and tools in the framework.  The conversion wizard worked as expected, said it was successful, and then I changed the build to target .NET Framework 3.5.  After that  I thought this was all too easy … and I was right, because I went to try to build the solution and got this compile-time error: Required file 'alink.dll with IAlink3' could not be found.

    Required file alink.dll with IAlink3 could not be found

    I googled this error message and anything else I could think of for hours, and found about ten different suggestions for how to fix it … none of which worked.  Most of them had to do with finding some obscure Windows Update that would fix whatever was ailing you.  After wasting my time on those jokers, I finally found a post on Channel 9’s forum with a solution that worked.

    It turns out there is a new project setting in VS 2008 that allows you to embed a manifest into the application, which determines some specific application settings.  By default the C# compiler was trying to embed a Vista manifest, which is definitely not the desired behavior in my scenario.  All I had to do was right click on the project that contained the error, and open the “Property Pages.”  When you open that it should look like the screenshot shown below.  By default the “Manifest” drop down was set to “Embed manifest with default settings” (which was trying to embed the Vista  stuff) … but all I had to do was change it to “Create application without a manifest” as shown, and the build succeeds and application behaves as expected.  Hopefully you didn’t waste too much time on this before you came across this post.

    Create application without a manifest
    Monday, February 04, 2008 8:25:29 PM (Central Standard Time, UTC-06:00)  # 

    I recently had the great experience of learning why you read the fine print on software agreements.  I was a beta tester for Microsoft Home Server, and didn’t realize that if you let the trial expire there was really no simple way to recover the data off the server.  When you tried to boot the server up, it would get all the way to Windows and then a popup would appear telling me the trial had expired and I should purchase the real version.  Home Server is such an awesome product that I did actually go out and drop the $170 on the OEM version … but what about all the data I had on the home server. 

    However, over the past several months I had grown to trust the OS, albeit it was in beta.  But since I had set up my critical data to be replicated to multiple drives … I thought it was a pretty safe place to store files.  Looking back this logic seemed skewed, but the OS really just gradually earned my trust and I didn’t realize that I had started storing data on it that wasn’t saved anywhere else.  This included family photos, and isn’t that the thing people say they miss the most after a house fire destroys all their possessions?  So, I had to recover the data somehow … and although it wasn’t too hard, I wanted to post some details on how I did it in case anyone ever finds themselves in a similar situation.

    If you had some data stored on Home Server, and for some reason you can’t boot into the OS … here is how you can recover your files (or at least how I did it).  Since one of the coolest features of Home Server is storage pooling and disk redundancy, most people probably have more than one drive in the server, which means Home Server probably spread your data across many drives.  So a few of files might be on this hard drive and few might be on that one, and a few on another.  So you will need to take each disk and connect it somehow to another computer so that you can inspect the contents.  You can do that by placing the disk in an external enclosure that plugs in via USB or eSATA … or you can plug the drive straight into the motherboard of the other computer via IDE or SATA cables.  If you are already lost, you might be better off paying someone to do this.

    After you have the drive hooked up you should be able to browse the contents using Windows Explorer.  You need to make sure that you have your computer configured to show hidden files and folders, and if you do you should see a structure like this in the root of your drive:

    Browse hidden files and folders on home server drive

    It looks a little cryptic, and at first you might think your data is stored in the “folders” or “shares” directory … but you would be wrong.  It is actually all under the hidden “DE” directory.  Click on that directory and you will see this:

    Recover shared folder data from home server drive

    Then click on the shares directory, and you will see some of the shared folders you had on the Home Server … might look like this:

     

    Access shared folder data on home server drive

    Notice I said you will see “some of the shared folders you had on the Home Server” … all the folders might not be listed.  Remember the data can be spread across multiple drives, so the only folders listed are the ones with files on that drive.  If all part of your photos are stored on disk 1 and part on disk 2, then there would be a Photos folder listed on both of them.  However, if all of the software was stored on disk 1 and there wasn’t any on disk 2 … that share wouldn’t be listed on disk 2.

    You should copy all of these folders to a set location on another drive.  Then pop in the other drives you had on the server and copy their contents to that same place.  After you have gone through all of the drives this way, you should have recovered all of the files stored on the Home Server shares.

    Saturday, January 26, 2008 8:22:53 PM (Central Standard Time, UTC-06:00)  # 
    Visual Studio Recent Projects

    I use the links listed in "Recent Projects" in Visual Studio constantly.  In fact, I use it so much that I lengthened how many items were displayed, which can be done in Tools > Options > Environment > General.  I think the default is 10.  But sometimes there are items in there that I don't really need shortcuts to, or worse they are not the shortcuts that should be used to open a project (for one reason or another).  It's pretty simple to remove some or all of the items listed in this area of the Start Page, all you have to do is:

    1. Run regedit
    2. Navigate to HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\8.0\ProjectMRUList
    3. Delete any of the values related to projects that you don't want to show up in the "Recent Projects" List

    NOTE: These are the instructions for Visual Studio 2005, but the steps are almost identical for Visual Studio 2008 ... you do the same thing except instead of navigating to "8.0" in the registry path, go to "9.0".

    Clear Recent Projects registry values
    Thursday, January 03, 2008 8:37:18 AM (Central Standard Time, UTC-06:00)  # 

    Creating an archive of a SourceSafe database or individual project is a fairly straight-forward task, but I was tired of having to refer back to one of the books I read to "re-learn" how to do it every time it needed to be done.  So here are the steps for how to make an archive for backup purposes.

    1. Log onto the SourceSafe server and open Visual SourceSafe Administrator.  Click on the Archive menu item, and choose Archive Projects ... as shown below:

    2. A wizard appears that allows you to select what you want to include in the archive. Choose $/ if you would like to archive the entire SourceSafe database you are currently logged into, or you can select an individual project (i.e. subfolder). If you want to archive multiple projects you can select a specific one and then add others to the archive by clicking the << Add button.

    3. The next step allows you to select where the archive should go, and a few other options. If you are creating the archive for backup purposes you will probably want to leave the radio button list set to the default selection of Save data to file, so that it doesn't remove the project(s) from SourceSafe after the archive is created.

    4. Next you can choose whether you want to archive all of the data contained in the database, or only a subset by specifying one of the following items in the Version textbox:
      • Project version number (Example: 5)
      • Project date in the format M/D/YY (Example: 11/1/07)
      • Project label string (Example: Beta1)

    That's it! Click Finish, and in a few minutes you will have an archive file (.ssa extension) in the location you specified.
    Monday, December 10, 2007 9:36:50 AM (Central Standard Time, UTC-06:00)  # 

    Wow, this was way more difficult to setup than it should have been.  However, one of the guys I work with finally conquered this task and I wanted to share how we did it so that you don't have to go through the same ordeal.  First off, you need to have your carrier's tethering service on your phone.  I have my service through AT&T, and this service isn't included in their "unlimited data plan" ... it is extra.  I think it is around $60 for unlimited data and tethering, but you should definitely check with your local store for an accurate price.  If you don't have this service, I wouldn't try this.  One of two things will probably happen: a) it just won't work, b) your carrier will be able to notice you are connecting from a device other than your phone and charge you an outrageous, and possibly offensive fee.

    So, I figure you can probably do this somehow through AT&T's Communication Manager software.  However, we tried this and the software seemed pretty invasive ... plus we couldn't ever get it to work.  We also searched the internet and tried several other people's instructions on how to set this up (most had to do with setting up dial-up accounts), but couldn't get any of these to work either.  Finally we stumbled upon another alternative that did work for us. 

    These are the steps we took to tether to our Windows Mobile Device via Bluetooth and use it as a modem.  When surfing the internet the latency is pretty long, but having an internet connection anywhere you have cell phone service is pretty nice.  In areas with 3G (UMTS) service the connection is up to 384kbps, and the older GPRS is up to 174kbps.  If only the basic cell service is available (like is rural areas), you should still be able to connect ... but be prepared for some really slow speeds.  But its nice to be able to get online if you really had to.

    The instructions probably won't vary much by phone model, version of windows mobile, or service carrier ... but just so you know the instructions were used to setup our AT&T Tilt phone (HTC 8595) running Windows Mobile 6 for Pocket PC.

    On The Phone

    1. Open File Explorer and navigate to My Device > Windows
    2. Select the “IntShrUI” and hold the stylus down until the right-click menu appears and choose “Copy” 
      (Note: This stands for Internet Sharing User Interface, and it seems AT&T doesn't really give you a straight-forward way to get to this because they would rather you use their own proprietary connection manager.  However, if you pay for their tethering service this is a totally valid, unobtrusive way to share tether your phone.)
    3. Navigate to My Device > Windows > Start Menu > Programs, and click Menu > Edit > Paste Shortcut
    4. Go into “Programs”, and click the item named “Shortcut to IntShrUI”
    5. Configure the settings to match the following:
      PC Connection: Bluetooth PAN
      Network Connection: AT&T ISP GPRS
    6. Click “Connect”.  The Status message at the top should change to say “Device setup finished.  On the PC, connect Bluetooth PAN.”

    On The PC

    1. Right-click on the Bluetooth icon in the system tray, and select “Join a Personal Area Network”
      Join a Personal Area Network to use mobile phone as a modem
    2. Your mobile device should be selected under an area named “Access Points”.  Click the “Connect” button.  (If your device is not listed, you must first "Add a Bluetooth Device" to make a connection that is Passkey enabled so that the mobile device and computer can trust and communicate with each other.  I noticed that if I chose to set up the bluetooth connection without a passkey, the device didn't show up as a PAN that I could join.)
      Connect using bluetooth to use mobile phone as a modem
    Tuesday, November 13, 2007 9:35:41 AM (Central Standard Time, UTC-06:00)  # 

    At my company, we use SourceSafe 2005 for source control.  It allows administrators to configure it to have one of the following check-out models:

    • Exclusive Check-Out Model: Lock-Modify-Unlock mode that only allows one user to modify a resource at a time.
    • Multiple Check-Out Model: Copy-Modify-Merge mode that allows multiple users to modify a resource at a time, after which a merge operation is performed at which point any conflicts between the changes can be addressed and resolved.  In my experience, the merge can typically be done automatically without any issues.

    Although we usually only have two people working in the project at a time, there are still times when it would be convenient to allow both users to modify different sections of the file at the same time.  However, the few times we have done that one of the developers gets an error message the next time they try to build the solution that says "Access to the path _ is denied."  We didn't notice that this was the file that required the merge for a while, but finally pinpointed that to be the problem.

    I found a blog post that explained the issue.  It turns out the "Include inheritable permissions from this object's parent" property of the merge files had been cleared.  If you simply re-check this property, the solution should build properly.  This is just seems to be a bug with the merge functionality in SourceSafe, and will hopefully be something they fix in the future.

    Friday, October 19, 2007 1:23:45 PM (Central Standard Time, UTC-06:00)  # 

    I use .netTiers as my data access and business logic layers in some of my web applications, and recently started using it on one that is hosted on a server I don't have access to.  .netTiers is based on the Enterprise Library, but allows you to choose which version you want to target.  I am targeting the latest version, Enterprise Library 3.1 (May 2007 release).  My application built and seemed to be working fine on my local machine, but when I tried to move the code out to the hosted server I got an error saying:

    Security Exception
    That assembly does not allow partially trusted callers.

    After a little research, I figured out that the Enterprise Library runs at the "full" trust level out of the box, which is more than the company hosting my application would allow.  Like most major hosting services, they only allow applications to run under "medium" trust ... which is actually a good thing because it isolates your application from others that may be running on the same machine.  Medium trust applications also have no registry access, no event log access, no ability to use reflection, and file system access is limited to the application's virtual directory.

    Fortunately, I did find a few "hacks" out there that allowed me to modify the library to run in "medium" trust environments.  I eventually built some new dll's for the Enterprise Library that I used to replace the ones in my application.  Even though the process was a little painful, I finally got it to work.  To save you from going through the same hassle, the link below will allow you to download the customized, medium trust dll files I built.  I bet if you are having a similar problem that the one I described, you can probably just copy the dll's you need over the ones currently in your application.

    App Blocks bin Folder.zip (1.12 MB)

    To generate those dll's I followed the steps listed below, which are a condensed version I found in this article.

    1. If you don't already have the Enterprise library installed, download and install the Enterprise Library 3.1 (May 2007 release).
    2. Make a complete copy of the EntLibSrc Enterprise Library 3.1 source code folder, and rename it to something like EntLibSrc-PartialTrust.
    3. Go to http://www.codeplex.com/ObjectBuilder/Release/ProjectReleases.aspx?ReleaseId=1613, download ObjectBuilder-1.0.51206.0-source.zip, and unzip it.
    4. Open the solution ObjectBuilder.sln from the root of your ObjectBuilder folder in Visual Studio 2005.
    5. Open the file AssemblyInfo.cs from the Properties folder of the ObjectBuilder project and add the line "[assembly: AllowPartiallyTrustedCallers()]" to the end of the code. (You'll also have to add a reference to the System.Security namespace).
    6. Right-click on the ObjectBuilder project in Solution Explorer and click Rebuild.
    7. Copy Microsoft.Practices.ObjectBuilder.dll from the EntLibSrc-PartialTrust\ObjectBuilder\ObjectBuilder\bin\Debug folder to the Enterprise Library folder EntLibSrc-PartialTrust\App Blocks\Lib, replacing the original signed version of the assembly.
    8. Open the solution EnterpriseLibrary.sln from the folder EntLibSrc-PartialTrust\App Blocks into Visual Studio 2005, and hit CTRL+SHIFT+H to open the "Find and Replace" dialog.  Set the following values:
      • Find what: <Reference Include="Microsoft.Practices.ObjectBuilder, Version=1.0.51206.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      • Replace with: <Reference Include="Microsoft.Practices.ObjectBuilder, Version=1.0.51206.0, Culture=neutral, PublicKeyToken=null, processorArchitecture=MSIL">
      • Look in: [path]\EntLibSrc-PartialTrust (the full path to your copy of the Enterprise Library source files)
      • Include sub-folders: Yes (checked)
      • Look at these file types: *.csproj
    9. Click Replace All, and the matching files are modified.  VS will give you a few prompts, but just click "Reload" (and maybe "Discard" once) until there are no more alerts.  Click "Save All" from the File menu.
    10. Right-click on the Enterprise Solution entry at the top of the Solution Explorer window and click Rebuild.
    11. Navigate to the EntLibSrc-PartialTrust\App Blocks folder in Windows Explorer and double-click the batch file named BuildLibraryAndCopyAssemblies.bat.
    12. Copy the assemblies you need from the EntLibSrc-PartialTrust\App Blocks\Bin folder into your application's Bin folder.
    Thursday, October 18, 2007 6:26:43 PM (Central Standard Time, UTC-06:00)  # 

    I have been a long time user of iTunes (started around version 3), and openly admit that in the past it has been a much better product than Windows Media Player and other media player applications ... at least for me.  But, a few weeks ago I heard Scott Hanselman mention in one of his podcasts, that iTunes has been on a downhill spiral since the release of version 7.  There are constant, sometimes daily, updates that prompt me download a new 50MB install file (not just a patch), and really 99% of the changes in those updates are for the new iPhone functionality that Apple thought would be a good idea to tightly bind to iTunes.  But those aren't the only issues. 

    I can't remember the native burning software in iTunes ever working on my Dell Precision M90 laptop.  I have read some forum posts that said Apple simply doesn't have 64 bit drivers yet.  Whatever the reason, after I installed iTunes on a clean machine I got an error saying:

    iTunes was not property installed. If you wish to import or burn CDs, you need to reinstall iTunes.

    There is a 64 bit driver available from Gear Software that I was able to download and install, and it fixed this issue.  However, from that point on every time I opened iTunes I got the error message shown below:

    iTunes cannot locate the CD Configuration folder, so you cannot import or burn CDs

    After searching and reading a few more forums, I figured out this folder was "missing" from C:\Program Files (x86)\iTunes.  I don't usually make it a habit to go in and delete random folders from the Program Files directory ... so I am thinking this folder was never really there.  Luckily, I had a friend who was able to make a copy of his "CD Configuration" directory from one of his machines.  I copied it into the iTunes directory, and viola ... no more error messages, and CD ripping and burning actually work in iTunes.  You can download a copy of that folder here.  Thanks for the painful process Apple.

    Tuesday, October 09, 2007 1:51:16 PM (Central Standard Time, UTC-06:00)  # 

    I struggled with this error message for quite some time on one of my home computers.  When I tried to install some software like Adobe Reader or iTunes, the installation would fail and show me a popup alert saying "Invalid Drive: P:\".  This was really frustrating, because it was keeping me from installing some programs I really needed.  

    I originally mapped the P: drive to the shared "Pictures" folder on my Windows Home Server (which is very cool by the way), and also changed the "My Pictures" folder for all users to point to that drive instead of the default location.  I did this so everyone would access and store pictures in the same place, and that would be on the Home Server which I had set up to have raid-like fault tolerance (can't loose those pictures).  Since that time I figured out a different way to do this, but although I had disconnected or "unmapped" the P: there was still a key deep in the registry pointing to this location for "My Pictures" ... and that was causing the conflict.

    To resolve the issue, all I had to do was go into the Registry Editor (run "regedit") and navigate to folder shown below (the path is shown at the bottom of the window).

    Remove registry key referencing mapped drive

    I just changed the value of the "My Pictures" key to point to the default location of "C:\Users\Cal\Pictures" ... and the next time I tried to install those programs everything went smoothly.

    Sunday, October 07, 2007 3:57:14 PM (Central Standard Time, UTC-06:00)  # 

    I've found it useful a few times to map a folder residing on a local machine to a drive letter.  Here is a simple command line script that allows you to do just that:

    subst G: "C:\Program Files (x86)"

    This would create a new drive named "G:", which would appear like an entirely seperate hard drive or partition ... but really would just be a shortcut to "C:\Program Files (x86)".  If you want to "delete" the mapped drive later, just run this:

    subst G: /d

    Thursday, October 04, 2007 10:28:36 AM (Central Standard Time, UTC-06:00)  # 

    In SQL you can use the LIKE operator to perform simple keyword searches.  For example:

    SELECT CustomerID, CompanyName, ContactName
    FROM   dbo.
    Customers
    WHERE  CompanyName LIKE '%St%' OR ContactName LIKE '%St%'

    Using the Northwind database, the script above would return 21 results:

    CustomerID  CompanyName                  ContactName
    ----------------------------------------------------------------
    ALFKI       Alfreds Futterkiste          Maria Anders
    BERGS       Berglunds snabbköp           Christina Berglund
    EASTC       Eastern Connection           Ann Devon
    ERNSH       Ernst Handel                 Roland Mendel
    FRANR       France restauration          Carine Schmitt
    GALED       Galería del gastrónomo       Eduardo Saavedra
    GROSR       GROSELLA-Restaurante         Manuel Pereira
    HILAA       HILARION-Abastos             Carlos Hernández
    HUNGC       Hungry Coyote Import Store   Yoshi Latimer
    ...

    However, this can be slow because it usually involves a full table scan.  SQL Server has a much more efficient way to perform full-text searches, which utilizes the Microsoft Search Engine.  This involves creating a full-text catalog, which is external to the database (actually stored outside normal database structure) so it requires a little configuration but has the potential to significantly improve search performance.

    Defining A Full-Text Index
    To define a full-text index, just navigate to the table you want the index on using SQL Management Studio, right click on the table name, and choose "Define Full-Text Index..." as shown below:

    SQL Server Define Full-Text Index

    A wizard will appear like the one shown below, and you will need to configure what you want to index and how you want SQL to keep that index up-to-date (remember ... it is stored outside the normal database structure, so SQL has to keep it in-sync).  Most the time you don't need to change the wizard's default selections, but the next few screen shots show how I configured a full-text index for Northwind's Customer table.

    SQL Server Full-Text Indexing Wizard

    Full-Text Indexing Wizard Select Table Columns

    Full-Text Indexing Wizard Select a Catalog

    After you have the index created, you can use the FREETEXT, FREETEXTTABLE, CONTAINS, and CONTAINSTABLE keywords (for more info on the functionality that each provides go here).  You could rewrite the query from the earlier example to be more like this:

    SELECT   C.CustomerID, C.CompanyName, C.ContactName, R.[Rank]
    FROM     dbo.Customers C INNER JOIN
             CONTAINSTABLE(dbo.Customers, (CompanyName, ContactName), '"St*"') R ON C.CustomerID = R.[KEY]
    ORDER BY R.[Rank] DESC

    Which would yield the following results:

    CustomerID  CompanyName                  ContactName      Rank
    ----------------------------------------------------------------
    LAZYK       Lazy K Kountry Store         John Steel       112
    VICTE       Victuailles en stock         Mary Saveley     112
    HUNGC       Hungry Coyote Import Store   Yoshi Latimer    96
    LETSS       Let's Stop N Shop            Jaime Yorres     96
    QUICK       QUICK-Stop                   Horst Kloss      96

    The script using LIKE returned 21 results, but the one using the full-text index only returned 5 results.  However, that is actually a good thing, because the full-text index only returned relevant results (the ones someone would most likely be looking for if they searched for "St").  The LIKE clause returned any row that had "st" somewhere in it ... whether it was the first of a word or buried in the middle of it.  The full-text index is smart enough to only return words that start with "St," but notice that doesn't necessarily mean it is at the start of the string or preceded by whitespace ... because it returned "Quick-Stop" as well.  A hidden benefit of using full-text indexes is that the Microsoft Search Engine will intelligently parse the text, and yield more relevant results.

    When you search a full-text index using the CONTAINSTABLE method, you are also able to utilize a new column named rank.  This column indicates how relevant the match is compared to the rest of the results.  In this example, the rank column isn't too useful.  But if you were searching a long product description, this column could become very useful.  I chose this really simple example to just show the bare bones functionality of searching SQL, but when you are searching columns with a lot of text (like a product description) ... that is when this approach really pays off in terms of efficiency, only returning relevant results, and providing a rank of how relevant a result was in comparison to the rest of the set.

    Wednesday, October 03, 2007 3:05:10 PM (Central Standard Time, UTC-06:00)  # 

    This is based on a post by Vik Thairani that I have referred back to many times, and I just updated it for Outlook 2007 (he actually found the VB script here).  A lot of times I am frustrated when I enter a phone number in my phone (AT&T 8525 running Windows Mobile Pocket PC 5.0), because it always defaults to display contact names by "Last, First."  It seems a lot more intuitive to me if everyone is listed "First Last."  Outlook gives you the option to configure what you want the default format to be, but that functionality isn't available on my phone.  So when the two sync up, I have some listed one way and others listed differently.  The steps below will install a VB Script in Outlook that you can run anytime and it will spin through your every contact in your default contact folder and change them to all display in the "First Last" format ... instead of editing each one.

    1. Set your default preference in Outlook
    Go to Tools > Options > Contact Options
    Set the Default "File As" Order

    2. Setup Security to Allow Unsigned Macros
    Go to Tools > Macro > Security
    Change to "Warnings for all macros"
    Restart Outlook

    3. Creating the Macro
    Go to Tools > Macro > Visual Basic Editor
    In the Left hand window double click on "ThisOutlookSession" (you may have to expand the project tree)
    Copy and paste the following script into the code window:

    Public Sub FormatNamesAndNumbers()
        Dim objOL As Outlook.Application
        Dim objNS As Outlook.NameSpace
        Dim objContact As Outlook.ContactItem
        Dim objItems As Outlook.Items
        Dim objContactsFolder As Outlook.MAPIFolder
        Dim obj As Object
        On Error Resume Next
        Set objOL = CreateObject("Outlook.Application")
        Set objNS = objOL.GetNamespace("MAPI")
        Set objContactsFolder = objNS.GetDefaultFolder(olFolderContacts)
        Set objItems = objContactsFolder.Items
        For Each obj In objItems
            If obj.Class = olContact Then
                Set objContact = obj
                With objContact
                    ' Try to file the contact by their first name followed by their last name.
                    ' If one of those names are missing, just use the one that is there, but if
                    ' both are missing file it by the company name set for the contact.
                    If .FirstName <> "" Or .LastName <> "" Then
                        .FileAs = Trim(.FirstName & " " & .LastName)
                    Else
                        .FileAs = .CompanyName
                    End If
                    
                    ' Format all of the common types of phone numbers to be in the standard
                    ' (XXX)XXX-XXXX format
                    If .MobileTelephoneNumber <> "" Then _
                        .MobileTelephoneNumber = FormatPhoneNumber(.MobileTelephoneNumber)
                    If .BusinessTelephoneNumber <> "" Then _
                        .BusinessTelephoneNumber = FormatPhoneNumber(.BusinessTelephoneNumber)
                    If .HomeTelephoneNumber <> "" Then _
                        .HomeTelephoneNumber = FormatPhoneNumber(.HomeTelephoneNumber)
                     
                    .Save
                End With
            End If
            Err.Clear
        Next
        Set objOL = Nothing
        Set objNS = Nothing
        Set obj = Nothing
        Set objContact = Nothing
        Set objItems = Nothing
        Set objContactsFolder = Nothing
    End Sub
    
    Private Function FormatPhoneNumber(ByVal number)
    
        Dim defaultAreaCode As String
        Dim returnValue As String
        defaultAreaCode = "806"
    
        number = CStr(number)
        number = Replace(number, "-", "")
        number = Replace(number, "(", "")
        number = Replace(number, ")", "")
        number = Replace(number, "+1", "")
        number = Replace(number, " ", "")
        Select Case Len(number)
            Case 7
                ' The number doesn't include an area code ... append the default area code
                returnValue = "(" & defaultAreaCode & ") " & _
                                Mid(number, 1, 3) & "-" & Mid(number, 4, 4)
            Case 10
                returnValue = "(" & Mid(number, 1, 3) & ") " & _
                                Mid(number, 4, 3) & "-" & Mid(number, 7, 4)
            Case 11
                ' The number is prefixed with an unnecessary "1" for long distance
                returnValue = "(" & Mid(number, 2, 3) & ") " & _
                                Mid(number, 5, 3) & "-" & Mid(number, 8, 4)
            Case Else
                returnValue = number
        End Select
        FormatPhoneNumber = returnValue
    End Function

    4. Saving the Code and Running the Macro
    Click File > Save
    Close the editor window
    Go to Tools > Macro > Macros
    Select "ThisOutlookSession.FormatNamesAndNumbers" and click Run

    That's it ... after the script completes all of your contacts will be in the "First Last" format, and the changes will be reflected on your phone next time you sync.  It is a good idea to change your macro security settings back to "Warnings for signed macros, all other macros are disabled", which you can do by repeating step 2 and choosing the appropriate option.

    I also updated it to format mobile, home, and business phone numbers to be in the common (XXX)XXX-XXXX format.  Notice that the FormatPhoneNumber function has a default area code set in the first few lines.  It will append that to numbers that only have 7 digits (i.e. no area code).

    Monday, October 01, 2007 6:47:33 AM (Central Standard Time, UTC-06:00)  # 

    This article provides step-by-step instructions on how to add a network printer to a computer running Windows Vista.  This example uses "LPR" protocol (stands for Line Printer Daemon) to communicate with the printer.  It's fairly straight-forward, but I just wanted to write down the steps so I didn't have to "re-learn" it every time.

    1. Click on the Windows Start Button and then "Printers" 

    Add Network Printer By IP Address

    2. Click on the "Add a Printer" menu item

    Add IP Printer

    3. Click the "Add a network, wireless or Bluetooth printer" option

    Choose a local or network printer - Add a network, wireless or Bluetooth printer

    4. Your computer might be able to discover the printer automatically, but I never had any luck with that ... so I just clicked on "The printer that I want isn't listed" option.

    Searching for available printers - The printer that I want isnt listed

    5. Choose the "Add a printer using a TCP/IP address or hostname" option

    Find a printer by name or TCP/IP address

    6. Find the IP Address, TCP Port, and LPR Queue Name for the printer you want to install.  I am using D-Link's DPR-1260 RangeBooster G Multifunction Print Server to host the printers on my network.  However, these instructions aren't necessarily specific to this print server.  You should just find the screen on your print server that provides info like the stuff highlighted in the screenshot below.  These values will be used in the next few steps. 

    NOTE: Although my extremely frustrating experience with D-Link's WBR-1310 Wireless G Router has completely shattered any confidence I had in D-Link, I have to admit I haven't had many problems at all with this wireless print server.  It hosts up to four USB printers, and even allows me to still use the scanner on my HP PSC 2100 All-In-One ... although since that particular printer isn't in the compatible multifunction printers list I could only get it to work using "Scan" tab in the D-Link interface shown below.

    D-Link Print Server Network Device Info

    7. Type the IP Address and Port name in the wizard.

    Type a printer hostname or IP address

    8. Choose the "Custom" option, and then click the "Settings" button.

    Additional Port Information Required - Custom Settings

    9.  Set the protocol to be "LPR", and then enter the LPR Queue Name we found in Step 6.

    Configure Standard TCP/IP Port Monitor - LPR Settings Queue Name

    10. Choose the appropriate printer driver.

    Install the printer driver

    11. Give the printer a friendly name.

    Type a printer name

    12. Windows starts installing the printer on the machine.  Wait for it to finish and click Next.

    Installing printer...

    13.  Choose whether you would like to share the printer with other computers or not.

    Printer Sharing - Share name

    14.  SUCCESS!!!

    Youve successfully added the network IP printer
    Sunday, September 23, 2007 12:56:34 PM (Central Standard Time, UTC-06:00)  #