Thursday, September 25, 2008
There are a few events taking place in October that I would like to promote:
Day of .NET in Ann Arbor
Saturday, October 18, 2008
The fourth Day of .NET in Ann Arbor event will take place on the campus of Washenaw Community College in Ann Arbor (or is it actually Ypsilanti? I can never tell). The original conference was a collaboration between GANG, AADND, and NWNUG. This year, we have officially added GLUG.net and West Michigan to the list of groups who are assisting in the organization and promotion. So, for those of you following along at home, this event is the product of FIVE regional user groups, and like its predecessors, should be a great day of learning and networking.
The session list has been posted, and there should be something on the schedule for everyone. Registration is free, and all that we ask is that if you do sign up, then please show up. There's nothing worse than wasting sponsorship dollars by ordering too much food or too many T-Shirts (though, this year, we're going to try to get Pizza back on the menu, so food waste should hopefully be minimized).
http://www.dayofdotnet.org/AnnArbor/Fall2008/Sessions.aspx
Wally McClure appearing at Northwest Ohio .NET User Group
Monday, October 20, 2008
"Hello. I'm Wally McClure. You might remember me from my many books published by Wrox Press with my picture on them. Or, the ASP.NET Podcast, where on the website, you'll find pictures of my books with my pictures on them. Or, the very popular T-Shirt with my picture on the back...."
I met Wally this summer at TechEd Developer in Orlando, where one of the first experiences was being crammed into the back seat of a car between him and Keith Elder. He seemed to take a liking to me, and let's just say that my phone now receives more random text messages than ever before. :-)
In all seriousness, Wally was supposed to have appeared at NWNUG in June, just after TechEd, but had to back out due to work commitments (Steve Smith ended up filling in for him). We're thrilled that he was still interested in coming to Toledo, and we were able to arrange an October 20th date (note that this is a Monday, not the regular Tuesday meeting night).
Watch the NWNUG website for further details about the meeting topic.
Tuesday, September 16, 2008
I use Windows 2008 Server as a workstation. I find that certain things actually work better (like Sleep Mode, for instance, which is weird because servers shouldn't need to sleep, but Vista would often not go to sleep and drain my battery the entire couple of hours that it continued to run in my computer bag, and Windows Server 2008 has never done that.... but I digress).
However, when the new Zune 3.0 software was released today, I found, like many others, that the software explicitly listed three operating systems that were supported, and Server 2008 was unfortunately not one of them. The install refused to continue.

While experimenting, I found a way to install the Zune 3.0 software manually. It appears to be fully functional on my machine, but I assume no responsibility and will provide no support if you decide to follow these instructions on your own machine.
First, I noticed that the setup bootstrap (zunesetuppkg-x86.exe) says where it's extracting its files to:

(This was weird to me, since the E: drive on my machine is an external USB hard drive...)
Next, I opened that directory in Explorer. There, I found a "packages" directory:

And inside of there, I found a MSI file called Zune-x86.msi:

Ok, learn from my trial-and-error here: uninstall any previous version of the Zune software, because this MSI will not do it for you, and will not install if a previous version exists. Other than that, I just double-clicked on the MSI, and a minute later, I had the 3.0 software on my machine!
Note that I am currently behind a ISA Server proxy/firewall that requires authentication with Active Directory. Therefore, in order to download the firmware update, I had to change my UseLmCompat to "0" per this blog post:
http://jasonfollas.com/blog/archive/2007/11/13/how-to-get-the-zune-2.2-firmware-when-your-corporate.aspx
Monday, September 15, 2008
Dear Microsoft,
We've been pals for a long time, right? You put out all of these great technologies, and I help others to use them within their own organization. We have a good thing going, you and me.
But, I have a problem. You see, whenever I press this one special key on my keyboard, mostly by accident due to its proximity to ESC and ~, you give me an excuse to stop working and go get coffee. This isn't very productive. I don't always want an excuse to get coffee.

Can you please add a Cancel button or something? Your modal dialog is not very friendly at times.
BFF,
-Jason
Saturday, August 23, 2008
Last week, I travelled to Philadelphia to work out of my company's office in Exton for a week. Before leaving, I took my youngest daughter for a hike in the park, which was a two-fold treat for her: she got to spend time with Daddy, and as an extra bonus, she got to ride in Daddy's truck. Being a two-seater, it is not often used unless I'm spending some 1-on-1 time with one of the kids.
After returning from out hike, I parked it on the street in front of the house. It looked something like this picture that I took in 2003 right before buying it:
Well, fast forward to very early Friday Morning. I was sleeping in my hotel room, when my cellphone began to ring. I think the alarm clock took quite a few swipes of my fist before I realized that it was not the loud noise that was bothering me. I stumbled out of bed and picked up the phone, only to hear my wife tell a tale of firetrucks and flames and the entire neighborhood observing some bonfire that was taking place on the street in front of my house.
I had actually had a bad dream just a bit earlier, and was relieved to find out that it was only a dream. I think part of me expected the same to happen in this case, but no such luck. My truck - the one that I had just paid off a few months ago - was ablaze.
Now, the truck looks a little more like this:



Notice if you will that there is no hood on the truck. It was a steel hood, and is nowhere to be found. So, it was either removed by the firefighters and they took it with them, or it simply melted away.
The worst part now is that we have to wait until Monday (2 more days as I write this) for the insurance company to tow the shell away to their evaluation center... once there, the lady told me, they would then make the determination as to whether it could be repaired or not. I just giggled to myself.
But, until then, there's a tarp-wrapped burned-out truck serving as a landmark for those trying to locate my house. It's the one with the nose sitting on the asphalt.
UPDATE: The truck was hauled away shortly ago. While speaking with the neighbors who came out to watch, I learned that another neighbor filmed it AND UPLOADED TO YOUTUBE! Thanks, Andy!
Monday, August 18, 2008
Have you ever gotten an idea stuck in your head? One that you start your day thinking about in the shower, and then try as you might, you just can't get rid of it?
That's what happened to me a few months ago. Specifically, I started thinking about Twitter, and the problems that it was experiencing. At that time, the "Fail Whale" was making very frequent appearances, indicating that Twitter was having problems keeping up with the demands being made on it. My Tweeps (social networking friends on Twitter), all with short attention spans like myself, began chattering about moving to a more reliable platform.
But, in my estimation, Twitter was still the best platform to remain on. Among other things, it was actually the most "mature" in its class - if you can call something a mere two years old as being mature.
So, Jason, what is Tourniquet?
After quite a few weeks of thinking about the various problems that I was aware of, I came up with a pretty simple solution. I needed to bounce some ideas off of a sounding board, so I fired off an email to some of my friends: Alan Stevens, Keith Elder, and Micheal Eaton.
The contents of that email still serve as a pretty good overview for my vision, which has been realized as "Tourniquet":
Think of a multi-faceted approach to fixing Twitter's issues that ultimately concentrates on reducing the number of calls that you make into Twitter itself, as well as provides transparent access to Twitter data during Twitter downtimes:
1. A personal Twitter proxy.
This is simply an API passthrough service that you host yourself. The thought process here is that Witty and other clients could be configured as to what server is accessed to execute an API call (i.e., by default, it's http://www.twitter.com/, but someone hosting a Twitter proxy would be able to specify something like http://thisismydomainyo.com/tourniquet/ ). Aside from the server, there's nothing different about the request or response. That is, the client might hit http://thisismydomainyo.com/tourniquet/statuses/friends_timeline.xml instead of
http://www.twitter.com/statuses/friends_timeline.xml.
This is not a public/shared service - it would be intended for just the user.
2. Obfuscation/encryption
Network Nazis suck, and so do people who brag about having smart phones and data plans. :-) There may be other reasons why someone would want the URL obfuscated and the response from Twitter encrypted when transfered between the Twitter Proxy and the client. But, that's what I'm talking about. Clients would need to be modified to support encryption/obfuscation before the user can utilize it.
3. Caching
While the Tourniquet proxy is fetching the information from Twitter, it might as well cache it to some form of persistent storage. This can be used to save some calls into the Twitter API, especially for historical data (which is also useful for when they disable access to historical data during times of heavy demand)
4. Store and Forward
Is Twitter down? Damn! But, no worries with Tourniquet! Your status update is saved until Twitter comes back up. This is really no different than other store-and-forward services, except you're not giving your twitter credentials to some unknown third party website.
5. Automated fetching
Perhaps Tourniquet can periodically fetch your timeline for you and cache it, either by means of some external triggering or by a timer. Then, when you hit the proxy to check for updates, it's already there (and your current request would likely trigger another fetch just to make sure that it has the latest data)
6. Tribe-Net Sharing/Synchronization
Here's where the service gets interesting: @keithelder and @jfollas follow each other, and both run Tourniquet. So, both proxies can be configured to be able to sync statuses between each other, hopefully saving some Twitter API hits that count towards your hourly usage. (I'm thinking that Direct Messages can somehow be used to announce Tourniquet endpoints). If Twitter is down and there are some status updates that are available (but not yet on Twitter), those can be propogated across the cloud via proxy-to-proxy synchronization. Eventually, they'll show up as actual Twitter statuses.
I picture the communication resembling something like this:
@jfollas's proxy calls @keithelder's proxy, and announces the highest message ID for each person that @jfollas follows. @keithelder's proxy returns a list statuses for each of those people that it has cached where the message ID is higher (plus any new/unpublished statuses that are in store-and-forward). @keithelder's proxy will need to call @jfollas's proxy to reciprocate the process.
Another possibility is to also synchronize a single person, perhaps with the goal of maintaining a cache of the last 100 tweets per person on your follow list. In this case, @jfollas's proxy will call @keithelder's proxy and list all of the message id's that it has cached for the person. If @keithelder is also following that person (or otherwise happens to have some tweets cached for the person), then any new messages (or any in-between messages that @jfollas's proxy might have missed) will be supplied in the response.
That was the original concept. The name "Tourniquet" came from the same place where all good project names come from: the Thesaurus. I simply looked for synonyms for the word "bandage", stumbled upon this word, and discovered that it was not already well-known as a software product.
Alright, sounds good. Where is Tourniquet?
Tourniquet is not a product, per se. It's a project, and an open source project at that (MIT license). You can download the source and do just about anything with it from the project site on Codeplex:

http://codeplex.com/tourniquet
To run Tourniquet, you will need to grab the release from Codeplex, set up a database (i.e., run the create scripts), copy the files from the release to your webserver, and then set up a new web application on your webserver. If this sounds too complicated, then perhaps you should wait until it's a little more refined before checking it out. I'm just sayin'... :-P
As I wrote the code, I tried to keep in mind that not everyone has a really sweet hosting deal. Therefore, I targeted what I thought to be the lowest common denominators: ASP.NET 2.0 and SQL Server 2005.
Classifying SQL Server as a LCD, though, bothered me, for a lot of "common man" hosting plans do not include access to a database at all. But, being a SQL Server MVP, I found this to be the quickest way to build the prototype and release the project to Codeplex. The persistance layer actually uses the Provider model, so my goal is to make alternative providers that do not require SQL Server (i.e., maybe XML on the filesystem, or Amazon SimpleDB, etc).
The point that I'd like to drive home is that the current codebase is very much a proof-of-concept or prototype, albeit a fully functional one (I've been using it for a few weeks). People may point at my code and say "Why did you do this like that? Where are your tests? This code sucks!" and that's okay. In its current form, it does most of what I outlined in the email above, so I'm content (working software is the #1 measure of success).
I encourage anyone who wants to participate in taking this prototype to the next level to join the development team. Contact me through the site, Codeplex, or Twitter (@jfollas). I'd be very happy if some enthusiastic people could take on the development of parts of the system and run with it.
Wednesday, August 06, 2008
I found out about 15 minutes ago that SQL Server 2008 RTM version is available for download from MSDN! Congratulations to the SQL Server team!
As I watch the 3 GB download trickle slowly onto my hard drive, I'm left with a few questions at this point, like what version of NETFX will be installed when I run setup? RC0 so nicely installed the .NET 3.5 SP1 Beta Framework onto my machine, and since .NET 3.5 SP1 itself has not RTM'd, what are they going to do? Distribute the beta with the SQL Server RTM bits? Release .NET 3.5 SP1 to manufacturing? Remove the install dependency on .NET 3.5 SP1?
I'll know in [checking the download rate] about 3 days from now. ;-)
UPDATE: Well, come to find out, a few days later, Microsoft released the RTM version of NETFX 3.5 SP1. You need to have Visual Studio 2008 fully upgraded to SP1 (which will include the NETFX 3.5 SP1) before installing SQL 2008.
Tuesday, July 01, 2008

Thanks again, Microsoft!
Monday, June 23, 2008
In this, the eighth part in a series on the new Spatial Data types in SQL Server 2008, I'll step away from the database and do a little spatial coding using .NET.
Redistributable .NET Library
Up to this point in the series, I have demonstrated a lot of interesting (?) things that you can do with the new Spatial data types (Geometry and Geography) in SQL Server 2008. You might be thinking, "That's swell, and all, but I wish I could do some of that stuff without needing to be tethered to a database." Well, you know what? You can!
I mentioned in a previous post that the Spatial data types were implemented as SQLCLR User-Defined Types. I've since been corrected by Isaac Kunen, who stated that they are more accurately described as System-Defined Types, with the difference being that these are automatically installed and available for use as part of SQL Server 2008, regardless of whether the ENABLE CLR bit has been activated. Semantics aside, these types are merely classes within a .NET assembly, and Microsoft is making this freely available as part of a Feature Pack for SQL Server (which will be redistributable as part of your stand-alone application, according to Isaac):
SQL Server 2008 RC0 Feature Pack Download Page
(Look for "Microsoft SQL Server System CLR Types," which includes the two Spatial types plus the HierarchyID type. This link is for RC0, and may not be applicable to future versions as the product is finalized and released.)
Builder API
A new feature that was included with the first Release Candidate (RC0) is the Builder API. This is a collection of interfaces and classes that helps you to construct spatial types by specifying one point at a time until all points have been added.
The Builder API is not only useful for creating new instances of spatial data, but also for consuming existing instances one point at a time (maybe to convert an instance into another format). Documentation is light at the moment, so I'm still trying to grok exactly how to best utilize it.
For my first experiment with the API, I obtained some Zip Code Boundary data in ASCII format from the U.S. Census Bureau:
http://www.census.gov/geo/www/cob/z52000.html#ascii
My goal was to parse the data, and then create a new SqlGeography instance for each zip code. (Note: SqlGeography is the .NET class name that T-SQL refers to simply as Geography). The SqlGeographyBuilder class proved to be perfect for accomplishing this task.
At its core, the SqlGeographyBuilder implements the IGeographySink interface. If you wanted to consume an existing SqlGeography instance, you could implement IGeographySink in your own class, and then invoke the SqlGeography's Populate() instance method, passing in your object as the parameter. The Populate() method takes care of calling the appropriate IGeographySink methods within your class.
In this case, I'm not starting with an existing SqlGeography instance, so my code will need to call the methods of the SqlGeographyBuilder in the correct order:
After EndGeography() has been invoked, the new instance is available via the ConstructedGeography property of the SqlGeographyBuilder class.
Simple enough, right? Yeah, I'm still a little lost myself... But, here's some code to help demonstrate what's going on!
First, let's look at the ASCII data. A single zip code's boundary might be defined as:
1469 -0.824662148292608E+02 0.413848583827499E+02
-0.824602851767940E+02 0.413864290595145E+02
-0.824610630000000E+02 0.413860590000000E+02
-0.824685900000000E+02 0.413841470000000E+02
-0.824686034536111E+02 0.413843846804627E+02
-0.824605990000000E+02 0.413863160000000E+02
-0.824602851767940E+02 0.413864290595145E+02
END
The very first line happens to contain an identifier (maps to a second file that lists the actual USPS zip code). The coordinate listed in the first line is not actually part of the boundary, but rather appears to be the population center of that area. The actual boundary begins with the second line, and continues until you encounter the "END". Also, in case you couldn't tell, coordinates in this data are in Longitude-Latitude order.
Since a Zip Code is a polygon, and since we are working with SqlGeography, we must be aware of ring ordering. That is, the exterior ring of a polygon must be defined in a counter-clockwise order so that as you "walk the ring", the interior is always to your left. If you reverse the order, then SqlGeography assumes that you're trying to define a polygon containing the entire world except for the small area inside of the polygon.
Well, in this case, the order of the points of the Zip Code boundary is defined in clockwise order... so, we must be aware of this and call into the SqlGeographyBuilder in the opposite order (so the last point defined in the ASCII data is the first point used while building our new instance).
To accomplish this, I simply parse the Lat/Long coordinates as "double" types, and then push them onto a stack. Then, I pop the stack and call into the Builder API with each point. At the end, I obtain the new SqlGeography instance from the ConstructedGeography property.
(Note: This is demonstrative code - some things should probably be cleaned up/refactored/error handled... You have been warned)
public SqlGeography ParseAsGeography(string zipcode_points)
{
StringReader sr = new StringReader(zipcode_points);
string line = sr.ReadLine();
Stack<double[]> Points = new Stack<double[]>();
while (line != null && line != "END")
{
if (line != String.Empty)
{
Points.Push(ParseLatLngValues(line));
}
line = sr.ReadLine();
}
return CreateGeography(Points);
}
private double[] ParseLatLngValues(string line)
{
// -0.838170700000000E+02 0.409367390000000E+02
double[] ret = new double[2];
string lng = System.Text.RegularExpressions.Regex
.Matches(line, "\\S+")[0].Value;
string lat = System.Text.RegularExpressions.Regex
.Matches(line, "\\S+")[1].Value;
double.TryParse(lat, out ret[0]);
double.TryParse(lng, out ret[1]);
return ret;
}
private SqlGeography CreateGeography(Stack<double[]> points)
{
SqlGeographyBuilder builder = new SqlGeographyBuilder();
builder.SetSrid(4326);
builder.BeginGeography(OpenGisGeographyType.Polygon);
double[] point = points.Pop();
builder.BeginFigure(point[0], point[1]);
while (points.Count > 0)
{
point = points.Pop();
builder.AddLine(point[0], point[1]);
}
builder.EndFigure();
builder.EndGeography();
return builder.ConstructedGeography;
}
SQL Server 2008: Spatial Data, Part 1
SQL Server 2008: Spatial Data, Part 2
SQL Server 2008: Spatial Data, Part 3
SQL Server 2008: Spatial Data, Part 4
SQL Server 2008: Spatial Data, Part 5
SQL Server 2008: Spatial Data, Part 6
SQL Server 2008: Spatial Data, Part 7

Thursday, June 19, 2008
Tuesday at the NWNUG meeting, Steven Smith spoke on various ways to squeeze performance out of your ASP.NET applications. This was a fantastic talk, and gave me plenty to think about (since ASP.NET is not my forte, I only consider myself to have an intermediate skillset on this topic).
One suggestion that he made involved caching database writes. That is, instead of immediately writing logging-type information to the database for every request, which is a relatively expensive operation considering the small payload size, that you could accumulate them in a short-term cache, and then perform the write operation periodically. Fewer database calls = faster performance.
In his example, he spoke of his advertisement server that might serve many impressions per second, but he doesn't want each impression to incur an expensive database write. So, he keeps track of the activity locally, and then persists to the database every 5 seconds using a single database call containing multiple data points.
The code that Steve demonstrated utilized XML to contain the data within a single block of text (read: can be passed in as a single parameter to a stored procedure):
<ROOT>
<Activity customerId="ALFKI" viewCount="5" />
<Activity customerId="ANATR" viewCount="7" />
</ROOT>
Now, consuming XML from T-SQL is an area that I know very well, so I cringed a little bit when Steve showed the actual stored procedure code itself:
CREATE PROCEDURE dbo.BulkLogCustomerViews
@@doc text -- XML Doc...
AS
DECLARE @idoc int
-- Create an internal representation (virtual table) of
the XML document...
EXEC sp_xml_preparedocument @idoc OUTPUT, @@doc
-- Perform UPDATES
UPDATE TopCustomerLog
SET TopCustomerLog.ViewCount = TopCustomerLog.ViewCount
+ ox2.viewCount
FROM OPENXML (@idoc, '/ROOT/Activity',1)
WITH ( [customerId] NCHAR(5)
, viewCount int
) ox2
WHERE TopCustomerLog.[customerId] = ox2.[customerId]
-- Perform INSERTS
INSERT INTO TopCustomerLog
( CustomerID
, ViewCount
)
SELECT [customerId]
, viewCount
FROM OPENXML (@idoc, '/ROOT/Activity',1)
WITH ( customerId NCHAR(5)
, viewCount int
) ox
WHERE NOT EXISTS
(SELECT customerId FROM TopCustomerLog
WHERE TopCustomerLog.customerId = ox.customerId)
-- Remove the 'virtual table' now...
EXEC sp_xml_removedocument @idoc
Now, to Steve's credit, this code works just fine, and can probably be used as-is on all versions of SQL Server from 7.0 through 2008. But, since we really don't write ASP applications consisting entirely of Response.Write any longer, I'd like to see Steve update his demo to use more modern techniques on the database as well. ;-)
The first thing that he could do is update the procedure to utilize the XML data type that was first introduced in SQL Server 2005. This would simplify the code a little bit, and would get rid of the dependency on the COM-based MSXML.dll, which the sp_xml_preparedocument and OPENXML() uses.
CREATE PROCEDURE dbo.BulkLogCustomerViews
@doc xml
AS
-- Perform UPDATES
UPDATE TopCustomerLog
SET TopCustomerLog.ViewCount = TopCustomerLog.ViewCount
+ ox2.viewCount
FROM (
SELECT T.activity.value('@customerId', 'nchar(5)')
as CustomerID,
T.activity.value('@viewCount', 'int') viewCount
FROM @doc.nodes('/ROOT/Activity') as T(activity)
) ox2
WHERE TopCustomerLog.[customerId] = ox2.[customerId]
-- Perform INSERTS
INSERT INTO TopCustomerLog
( CustomerID
, ViewCount
)
SELECT [customerId]
, viewCount
FROM (
SELECT T.activity.value('@customerId', 'nchar(5)')
as CustomerID,
T.activity.value('@viewCount', 'int') viewCount
FROM @doc.nodes('/ROOT/Activity') as T(activity)
) ox
WHERE NOT EXISTS (
SELECT customerId FROM TopCustomerLog
WHERE TopCustomerLog.customerId = ox.customerId )
Note that the XML data type in SQL Server doesn't need to be a well-formed document. In this case, Steve could just pass in series of "Activity" elements (no "ROOT" element would be required by SQL Server, so he would also be able to simplify the .NET code that actually creates the XML string):
<Activity customerId="ALFKI" viewCount="5" />
<Activity customerId="ANATR" viewCount="7" />
Consequently, the XPath (XQuery, actually) within the nodes() method of the stored procedure code would need to change as well:
@doc.nodes('Activity') as T(activity)
But, we can kick this up a notch and use some SQL Server 2008 features as well. First, there's new "Upsert" capabilities (MERGE statement) that tries to simplify what Steve does with the UPDATE followed by INSERT:
CREATE PROCEDURE dbo.BulkLogCustomerViews
@doc xml
AS
MERGE TopCustomerLog AS target
USING (SELECT T.activity.value('@customerId', 'nchar(5)')
as CustomerID,
T.activity.value('@viewCount', 'int') as viewCount
FROM @doc.nodes('Activity') as T(activity)) AS source
ON (target.CustomerID = source.CustomerID)
WHEN MATCHED
THEN UPDATE SET target.ViewCount = target.ViewCount + source.viewCount
WHEN NOT MATCHED
THEN INSERT (CustomerID, ViewCount)
VALUES (source.CustomerID, source.viewCount);
One more thing that could be done to further simplify this T-SQL is to use a Table-valued Parameter instead of the XML. This would allow Steve to pass a fully populated table of data into the stored procedure and consume it directly by the MERGE statement.
The first step is to create a T-SQL type that defines the table structure of the parameter (this is a one-time operation, unless the table structure changes):
CREATE TYPE CustomerViewType AS TABLE
(
CustomerID nchar(5) NOT NULL,
ViewCount int NOT NULL
);
Now, a parameter can be defined of this type, and used just like any other table-value variable:
ALTER PROCEDURE dbo.BulkLogCustomerViews
@views CustomerViewType READONLY
AS
MERGE TopCustomerLog AS target
USING @views AS source
ON (target.CustomerID = source.CustomerID)
WHEN MATCHED
THEN UPDATE SET target.ViewCount = target.ViewCount
+ source.viewCount
WHEN NOT MATCHED
THEN INSERT (CustomerID, ViewCount)
VALUES (source.CustomerID, source.viewCount);
On the ADO.NET side, the table-valued parameter could be represented as a DataTable object (other options also exist), and can be assigned directly as the value of the stored procedure's parameter object:
// Create a data table, and provide its structure
DataTable customerViews = new DataTable();
customerViews.Columns.Add("CustomerID", typeof(string));
customerViews.Columns.Add("ViewCount", typeof(int));
// Fill with rows
customerViews.Rows.Add("ALFKI", 5);
customerViews.Rows.Add("ANATR", 7);
using (SqlConnection conn = new SqlConnection("..."))
{
SqlCommand cmd = conn.CreateCommand();
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "dbo.BulkLogCustomerViews";
SqlParameter param
= cmd.Parameters.AddWithValue("@views", customerViews);
conn.Open();
cmd.ExecuteNonQuery();
}
This morning's goal was to quickly install SQL Server 2008 RC0, and then move on with some project work. Let's just say that my project work should resume by this afternoon...
In the interest of disk space, I removed an existing installation of SQL Server 2005 Developer Edition. And then the installation of 2008 RC0 began by installing the "Microsoft.NET Framework 3.5 SP1 (Beta)"... which is probably "install-smell" for me needing to pave my machine when the product finally RTM's. But, I digress...
The installation went pretty smoothly until it came time for the "System Configuration Check" that takes place after you select everything that you would like to install, but before the files actually get installed. In my case, this check failed because "The SQL Server 2005 Express Tools are installed. To continue, remove the SQL Server 2005 Express Tools." (This is the "Sql2005SsmsExpressFacet" rule of the installation)
Thank you, Microsoft, for that succinct failure message that includes instructions for resolution... Except, I didn't have the SQL Server 2005 Express Tools installed. They didn't show up in my Programs list, in the Start menu, or on my C: drive at all. How am I to uninstall something that isn't installed? Hrmmm....
After about an hour's search around my hard drive, I finally went into the registry, and discovered the following key:
HKLM\Software\Microsoft\Microsoft SQL Server\90\Tools\ShellSEM
Note: Jan Sotola reports that the affected 64-bit version key is:
HKLM\Software\Wow6432Node\Microsoft\...
...\Microsoft SQL Server\90\Tools\ShellSEM
Contained within was some registry information belonging to Red Gate SQL Prompt. Apparently, despite my removing of the SQL 2005 Express Tools some time ago, this registry key was not removed because the Red Gate information was still there.
On a hunch, I renamed the key to "ShellSEM.old", and the SQL Server 2008 installation carried on.
UPDATE: Shortly after posting this, Theo Spears from Red Gate sent the following email:
"I apologise for this issue; the SQL Prompt team here has been working to address it. You and your readers may be interested to hear that we now have a version which works with SQL Server 2008 RC0, and no longer blocks the installation. To get a copy send us an email at support@red-gate.com"
I should clarify that my little rant above was not targeted at Red Gate, but I'm so happy to hear that they are proactively working to resolve this little issue. I would have just liked for Microsoft to use more than a single registry key as evidence of a conflicting product installation, that's all.