Monday, December 31, 2007
Friday, December 21, 2007
Debugging WizardExtensions for Visual Studio Templates
As per my previous post, this exercise would probably be much easier if I used the Guidance Automation Toolkit, but in the spirit of Twelve Days of Code, I promised to boldly venture into areas I normally don't go. I decided that I wanted to try out a WizardExtension so that I could compare the experience with the Guidance Automation Toolkit. So I created a new project and added the following references:
- EnvDTE
- ENvDTE80
- Microsoft.VisualStudio.TemplateWizardInterface
- System.Windows.Forms
The Visual Studio Template documentation says you need to sign your assembly and install it into the GAC, but that's crazy. Rather than jumping through hoops, I found a handy forum post that describes that the Visual Studio follows the standard assembly probing sequence, so the assembly needs to be in a place that the devenv.exe process can find it. Signing and installing into the GAC is simply a security measure. I didn't want to dump my custom assembly into Visual Studio's assemblies (where I would forget about it) so I created a custom folder in %program files%\Microsoft Visual Studio 8\Common7\IDE and added this to the probingPath of the devenv.exe.config. To enable debugging for my custom wizard-extension, I use two Visual Studio instances. One for my wizard-extension, the other for testing the template. Here are the steps involved:
- Add the assembly and class name to your ProjectGroup.vstemplate file:
<wizardextension> <assembly>Experiments.TemplateWizard</assembly> <fullclassname>Experiments.TemplateWizard.CustomizeProjectNameWizard</fullclassname> </wizardextension>
- Zip up the updated template and copy it into the appropriate Visual Studio Templates folder.
- Compile the wizard-extension assembly and copy it and its pdb to a path where visual studio can find it
- Launch a new instance of Visual Studio
- Switch back to the other visual studio instance, attach to the "devenv" process (the one that says it's at the start page) and set your break-points
- Switch back to the new instance of Visual Studio and start the template that contains your wizard extension
- debugging goodness!!
Well, at least I saved myself the effort of signing, etc. This exercise showed that very little is actually done at the ProjectGroup level of a Multi-Project template: the RunStarted is called, followed by ProjectFinishedGenerating method. The biggest disappointment is that the project parameter in the ProjectFinishedGenerating is null. This is probably because the item being created is a Solution, not a project.
The last ditch (seriously, ditch!) is to cast the automationObject passed into RunStarted to _DTE, and the work through COM interop to manage the Solution. That sounds romantic.
by bryan at 1:42 AM 0 comments
Thursday, December 20, 2007
Bundling Visual Studio templates for distribution
Microsoft's done a fairly good job in packaging for Visual Studio templates. Simply:
- Create an xml file that adheres to the Visual Studio Content Installer Schema Reference
- Rename the xml with a "vscontent" extension
- Place the vscontent file and your template zip into another zip file
- Rename that zip file with a "VSI" extension.
Now, when you double click the VSI file it runs a wizard that installs your template into the appropriate Visual Studio Template folder.
by bryan at 10:03 PM 0 comments
Wednesday, December 19, 2007
Visual Studio 2005 Multi-Project Templates - a waste of time?
As part of my twelve-days-of-code, I'm tackling a set of small projects geared towards simple project automation. I've discovered in recent projects that although the road is always paved with good intentions, other tasks, emergencies and distractions always prevent you from accomplishing what seem to be the most minor tasks. So when starting out on a new task, we always cut corners with the intention of returning to these trivial tasks whenever we find the time, or when they become justified in our client's eyes. However, if we started out with these things done for us, no one would question their existence or worry about a catch-up penalty; we would just accept these things as best-practice.
Visual Studio Project templates are interesting, though my first encounters with them suggest they miss the mark. For my projects, I find the effort isn't about creating the project, it's about creating the overall solution: project libraries, web sites, test harnesses, references to third-party libaries and tools, build-scripts, etc. Visual Studio supports the concept of "Multi-Projects Templates", which are limited (see below), but I suspect that the Guidance Automation Extensions might fill in the gaps.
Visual Studio supports two types of templates within the IDE, and a third type which must be stitched together using XML. The first type refers to "Item Templates" which refer to single files which can be included in any project. I'm focusing more on Project templates and Multi-Project templates.
Within Visual Studio, the concept of a project template is extremely easy: you simply create the project the way you like and then choose the "Export Templates..." option from the File menu. The project and its contents are published as a ZIP file in "My Documents\Visual Studio 2005\My Exported Templates". A big plus on the template structure is that all the files support parameterization, which means you can decorate the exported files with keywords that are dynamically replaced when the template is created by the user. The export wizard takes care of most of the keyword substitution for you, such that root namespaces in all files will match the name of the user's solution. With this in mind, a Project Template is "sanitized" and waiting for your client to adopt your structure with their name.
Multi-Project Templates stitch multiple Project Templates together by using a master xml-based template file. These templates can't be created using the IDE, but you can create a solution and then export each project out as a Project Template, then following this handy MSDN article and the Template Schema reference, you can quickly parcel together a master template.
However, there's a few really nasty limitations with multi-item projects templates. The biggest issue is that the Project Name cannot be parameterized, so the template adopts the names that are defined in your configuration file. As a result, the only thing you can really customize is the name of the solution. I was completely baffled by this: I thought I must be doing something wrong. However, after a few minutes of googling, others had come to the exact same conclusion.
Fortunately, the template system supports a Wizard framework, which would allow you to write some code to dynamically modify the solution. Unfortunately, the code for this would have to be strong-named and installed in the GAC. I'm tempted to wade into this, but I fear that I might be better off looking at the Guidance Automation Toolkit.
by bryan at 9:04 PM 0 comments
Monday, December 17, 2007
Visual Studio Templates - Export Template not available
So I was started into my foray of the Twelve Days of Code, focusing on a "Software Automation" theme. First stop, "Visual Studio Project templates". I've played with these templates before, and because Visual Studio makes them considerably easy to do, this first stop should be an easy one. I had an interesting problem with Visual Studio that slowed me down.
The steps to create a template are straight forward: you create the item and then use the "Export Template" wizard in the "File" menu. However, the "Export Template..." option did not appear in the File menu.
I recently got a new laptop, and had to reinstall everything from scratch. At first I thought it was because IT only installed Visual Studio Professional instead of the Enterprise version. But there have been some other peculiarities, for example, the "Debug" tool bar was missing crucial items like "Step In", and "Step Out".
The culprit was that I had installed SQL Server 2005 after Visual Studio. Because they share the same shell (sounds a lot like the Component UI Application Block) SQL Server had changed the default settings for Visual Studio.
To fix:
- Select "Import and Export Settings..." from the Tools menu.
- Choose the option to "Reset all Settings."
- Choose the option to save your current settings, just in case.
- Pick an option that doesn't include "SQL Server". I chose the "Visual C# Development Settings"
- Finish
Perhaps the software-automation tip here is to configure your settings with defaults for your team, export them out and share the vssettings file (xml) the team.
by bryan at 9:00 PM 0 comments
Sunday, December 16, 2007
The Twelve Days of Code
One of the things I always have enjoyed over Christmas holidays is finding a few hours here and there for myself to explore new technolgies or tinker with side-projects. With all the events and distractions, finding time for yourself can be a pretty challenging task. This year, I'm thankful to have a lot of time off, so I've decided the best way to tackle this is to find projects that are interesting but don't require a huge investment of thought. So I'm not going to bother with cracking open the XAML specifications and trying a SilverLight project. Too much time. Too much thought.
So I've come up with the "Twelve Days of Code". Catchy, but not corny. Here's the rules:
- Tackle a small project each day
- Use technologies I haven't used before or wouldn't naturally use in the course of a work project
- No project should take more than an hour.
- If you get snagged, the hour is about the snag.
- All the projects have to share a common theme
- The projects need to provide value to me beyond the scope of the holidays.
- Keep 'em blog worthy
I've decided my common theme should be "Software Automation" which is anything that saves time for repeated tasks. If I can save other developers on my team time, also great.
I've started a small list of to-do's, and I'm using http://www.rememberthemilk.com to help me with the scheduling, etc.
The list of thing I want to play with are:
- Visual Studio Templates
- Custom Installers / Wizards
- Click Once deployment
I'll keep ya posted.
by bryan at 2:12 PM 0 comments
Thursday, July 05, 2007
Technorati
This last few months I've been playing with all sorts of web 2.0 tools: - TadaLists - Twitter - Jaiku - wakoopa - Flickr - Del.icio.us - and others I just started Technorati, and I'm using this blog post to "claim" by blog. (Technorati Profile)
by bryan at 12:15 PM 0 comments
Tuesday, July 03, 2007
Simpsons Avatars
The web site is entirely flash, and they have a concept where your avatar appears in the site in various locations. It's a neat idea. Although it appears that most of the site is in "coming soon" mode, which reaks-of web-agency poor planning and scope creep challenges (or a series of well designed strategies...)
Unless I missed it in the UI, the site doesn't provide an ability to export your avatar as an image though it really should! (This image was taken from a screen capture and manipulated in MSPaint) I learned of the site and it's avatar abilities through the viral aspect at work: everyone was making them and posting them to facebook.
I had a lot of fun putting the avatars together and I guess I'm more stoked about the movie than I was before. Since the "coming-soon" aspect didn't capture my attention (the content has to be amazing for that attraction to work) I probably won't be returning to the site anytime soon. My work group is very web-saavy, but I wonder how many other individuals are hacking the site in this nature? If the attraction is the avatar, they should be milking that and allowing users to send them to friends, etc....
Try it out for yourself: http://www.simpsonsmovie.com
by bryan at 11:52 AM 0 comments
Friday, June 01, 2007
Karma bottleneck
I can't believe how buried I've been recently. Couped up. Recoiled. Tucked away.
I think things went south at the beginning of March.
I went away on a business trip for a full week. The flight down wasn't direct, so it was broken up into two small uneventful flights which would make a normal 90 minute direct flight about 6 hours long. On the second leg of the trip, I was tucked in the very back of the plane (did I mention my employer was paying for the trip, we seem to always get the crappiest seats at the last minute deals). On the decent, I was listening to a podcast on my first iPod. The whole podcast experience was significant because my iPod was still very new and you know how us guys get with our new toys, we fall into a ritual with them that seems to define who we are at that time. While they eventually lose their appeal and just become elements of the grind, my iPod was different: It's prototypical of this decade, an extension of yourself in musical form. In that sense, I was very caught up in it.
The odd thing is -- somehow, iPods crash planes -- or at least that's how the flight attendants make it seem, because they want you to turn it off while they descend. I comply, but keep my cherished iPod close at hand by winding up the headphones and setting it into my lap. After a few long solitary seconds, I switch to the book I brought in my carry on.
When we land, it's six hours into my 90 minute flight, and in my haste, I quickly pack up and get off the plane. I'm just worn out. It's not until I'm in the rental car 20 minutes later that I think about resuming my podcast. Since I'm in a strange city on my own in the middle of the night, my attention is focused on getting to the hotel, but in the back of my mind I can't place what I did with my iPod: it's either in my bag or in my jacket even though I don't recall putting it away.
When I arrive at the hotel, I check my jacket pockets as I walk toward the front lobby. It's strange that it's not there, so it must be in the carry on though that seems really "off" somehow. In the back of my mind, I begin to imagine what my wife would do to me when I tell her I lost the Christmas gift she bought me. Chuckle. Let's hope not.
I check-in.
I jump in the elevator. I pat down my carry on.
Yeah, that's not, ...uh, ...not good.
I get to my room and I quickly empty my pockets and check my carry on.
I open all the bizarre pockets that I never use.
I empty the carry-on and shake it.
FaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaK!!!
Yeah, screwed.
I call the airport, and get through to Lost and Found. My flight was the last flight for the night. My iPod is probably sitting on the floor, on the seat, or tucked between the seats of the 9:35pm Delta flight from Cincinnati to Richmond. Although it's only been 40 minutes from the flight, it's gone. Translation: "The white one with no scratches or identifiable marks? Yeah, it's going for $15 on eBay right now"
Somehow, this event has caused a ripple effect in what I envisioned this year to be. The choices between carrying-cases that my wife wanted, the iPod dock in the living room, the new iMac that would house my iTunes library -- all of these decisions, interrupted and sidelined indefinitely.
Instead of moving forward, I seemed plagued with indecisions of either buying a new one, or finding a cheaper one, or buying a stolen one from eBay. All these crazy thoughts happening simultaneously like a neuron misfire causing bottlenecks all the way down the line. In the event, an extension of myself was lost, and it was as though I accepted defeat and stopped making decisions. Coupled with the onslaught of spring-cleaning and never-ending hunny-do-lists, it was though I shut down.
Earlier this week, I managed to get away from the grind. I bought an iPod replacement. I bought an car-charger with FM unit and carrying case. And suddenly, just as summer arrived, so did I.
by bryan at 8:53 PM 0 comments
Sunday, April 29, 2007
Impressed with Google's manage History feature
It's been a few months since my last post -- I got sidetracked off my NCover tutorial I was trying a few months back. (Incidentally, my project desperately needs NCover, so I'll finally get a chance to jump back into that game).
Ages ago, I relied exclusively on the Google Toolbar in Internet Explorer. But when I realized that big-brother was analyzing every move I made, I got a bit creep'd out so I uninstalled the bugger. The heavy influence of FireFox and it's built-in search toolbar (CTRL+K) made enough impact that I've never looked back.
However, a lot of things have changed in the Internet landscape as well as my personal lifestyle in the last few years: Google Analytics and GMail have rolled into my digital life and even this blog now uses my Google Account. I've come to know of Google's "Do no evil" motto, which has relaxed some of my concerns that gradually, behind the scenes, they are collecting my information without requiring me to install software. Now that I'm using my Google Account for so many things, all my FireFox searches in the last 15 months (bless'ed CTRL+K) have quietly been captured. Although I've seen my searches appearing on my personalized homepage, I haven't been that concerned: at least I know what Google knows about me.
Today I noticed Google's new "Manage History" feature, which required me to install the Google Toolbar before using it. Although I installed the toolbar with some reluctance, I am wildly surprised with the manage history feature. Not only can I view my search history, but I can also selectively edit any item -- it's my data!
Google tells me that I've done waay too many searches, and it tells me what I clicked on per search. It provides trend information on my usage: monthly, by day, by hour. (FYI: I do more searches in January, Thursdays are most popular, and I've never searched for anything between 1 and 5 am) It can tell me the top ten search queries in the last week, month, year or all time.
I can view my history in terms of web, news, images, sponsored links -- even Google Maps and Video. Even scarier: it can offer links that I might be interested in based on my previous usage.
What also seems interesting is that Google is providing bookmark functionality -- a competing feature to online bookmark services such as del.icio.us.
What would be really interesting is to be able to cross reference this history to my activity elsewhere -- the topics in my inbox, the tasks or projects I was working on at that time.
Also -- if Google could have tracked my history using my google-account then why did I need the Toolbar in order to use the history feature?
by bryan at 2:04 PM 0 comments
Tuesday, February 13, 2007
NCover Setup - Part II
Following up on my previous post, I'm continuing to set up code coverage in my .NET 2.0 project with some specific criteria.
Kudos to Grant Drake (aka Kiwidude) who is very committed to the cause. I spent some time on his site last night, pouring over some of his FAQ. He left a comment on my blog earlier, citing an FAQ that I missed on his site. Plus he recommended version 1.5.4 of NCover instead of the latest 1.5.5beta. Good stuff!
So to follow through on my previous post.
The first thing I did was move my test namespace and their classes into a separate ClassLibrary. This included all the standard stuff:
- remove the reference of NUnit from the Core library
- add an NUnit reference to the Test library.
- add a Core Library reference to the Test project.
After a quick compile, I dropped down to a command prompt and executed the same statement, except this time I had to specify the name of the Test harness assembly:
ncover.console nunit-console Test.dll //a Core
This worked and ...satisifies my second objective! The added bonus is now the coverage report only contains information about the Core namespace. This is great, but as an aside, since nothing has changed in the code I wonder if this is a bug in the "//a" switch? Update: The //a switch is for assemblies without their extensions, not namespaces.
As a further refinement, I can begin to take advantage of NUnit's Project capabilities. This enhancement simplifies things greatly. It allows me to:
- test/cover multiple assemblies at a time
- shield my build script from having any knowledge about the test harness configuration.
- specify where to locate the configuration file (this should solve my third objective)
To create the Nunit project file:
- open the NUnit-Gui and choose "New Project". I like to save the nunit file at the root of my solution.
- add in the assemblies that contain your test fixtures. In my case, I have to specify the relative path to my Test Harness: /Test/bin/Debug/Test.dll
As an NUnit project file is simply an xml file, my nunit project file contains the following:
<NUnitProject> <Settings activeconfig="Debug" /> <Config name="Debug" binpathtype="Auto"> <assembly path="Test\bin\Debug\Test.dll" /> </Config> </NUnitProject>
With this in place, I can now generate my coverage report at the command line with the following statement:
ncover.console nunit-console TestProj.nunit //a Core
So far, this pretty good. The second objective was fairly easy, and although I've violated my first objective, Kiwidude has suggested this might be something I could correct with part of the build script, so I'll get to that soon enough.
Tomorrow, I'll tackle my third objective of being able to resolve configuration settings.
by bryan at 9:17 PM 1 comments
Monday, February 12, 2007
NCover Setup - Part I
About a year ago, one of my projects established some best practices for test-driven development where we found our sweet spot using .NET 1.1, NUnit, NCover and NCoverBrowser. Things were going great until one day, NCover stopped producing coverage reports. Despite my best efforts, I couldn't get it working again. Panic. Frustration. Pain. Despair. Acceptance: no more coverage reports....
Now, a new project is about to take off, this one using .NET 2.0, and I'm determined to get NCover running on this project. Problem is, documentation for NCover is still no where to be found online, only mixed messages in their forums.
So, for my reference, and potentially yours, I thought I'd share how I set this up. As I'm building an application for an enterprise the requirements can get pretty complex... here's what I'm shooting for:
- I typically structure a project so that I have a solution folder which contains copies of all the libraries my project will require to compile. I also bundle third-party tools with the solution folder so that when a developer checks the project out of source-control, the build script has everything it needs to operate without any configuration on behalf of the developer.
- I want to structure the solution so that the application code and the test harness are in different assemblies.
- In addition to separate test and application assemblies, configuration settings from the application code must be used with the test harness.
- I need to drive the code coverage from a NAnt script
- I need to integrate the output into CruiseControl.NET
So I downloaded NCover 1.5.5 beta and cracked open a new ClassLibrary project to simulate code coverage. I wrote two classes: a simple class "Core.Class1" and a test fixture "Test.Class1Test". I compiled the solution, dropped to a command prompt and manually tried to execute ncover using relative file paths. "Unable to locate file" errors.
As soon as I put both NCover and NUnit file paths into my PATH environment variable, the error messages started to change. The following statement, executed from the "bin\Debug" folder of my project:
"ncover.console nunit-console Core.dll //a Core"...worked!
Although adding PATH variables violates objective #1 it did produce a coverage report for the Core and Test namespaces. Since my NUnit test cases are guaranteed to be 100% covered by NUnit, I'll need to find a way to exclude the Test namespace from the report.
I'll reorganize the project into separate assemblies and see how that changes this configuration tommorrow.
Read part two: NCover Setup - Part II
by bryan at 9:28 PM 3 comments
Friday, February 09, 2007
VB.NET Logical Operators are lame
While writing some application architecture documentation for my current project, I drew a blank looking for the technical term "short-circuit" logical operators.
These are really simple in C#.
public bool Method1()
{
Console.WriteLine("Method1 called.");
return false;
}
public bool Method2()
{
Console.WriteLine("Method2 called.");
return true;
}
public void ShowShortCircuiting()
{
Console.WriteLine("Regular AND:");
Console.WriteLine("Result is: {0}", Method1() & Method2());
Console.WriteLine("Short-Circuit AND:");
Console.WriteLine("Result is: {0}", Method1() && Method2());
}
Produces the following output:
Regular AND:
Method1 called.
Method2 called.
Result is: False
Short-Circuit AND:
Method1 called.
Result is: False
The concept is really simple too: If the first item isn't true then don't bother executing the next condition.
When speaking with a colleague, he asked if this was possible in VB.NET. Google says yes. But isn't syntax in VB is completely backward??
The C# equivalents map to VB.NET, thusly:
C#: & VB: And C#: && VB: AndAlso
To quote the article I've highlighted:
If you want the second test to be executed every time, use And. If that's not what you want, use AndAlso.
Syntax-wise, I think that this reads as: "I want this and also this to be evaluated". But if the first condition doesn't pass, (insert smirk) wouldn't it better to say, "I want this, and maybe this to be evaluated."??
I'm not sure what prompted me to switch from VB6 to C#, but boy, I'm glad I did.
by bryan at 2:43 PM 2 comments
Friday, February 02, 2007
Eating dog food I preach
There's a fairly large disconnect between what I personally use and what my industry has highlighted as holy-grail great. So I finally got around to creating a del.icio.us account, and managed to find the time to add it to my blog. (I found a great resource for a list of online-del.icio.us tools here)
While I'm not convinced I need the social-networking aspect, an online bookmark solution makes sense for a few reasons:
Too many browsers. I use FireFox for my day-to-day, but occasionally I'll encounter some weird bugginess in FireFox 2.0 so I'll boot up IE 7. What's interesting about switching back and forth between browsers is that once you get settled into a session with a half-dozen tabs open, you don't want to switch back during that session. I also find that I don't bookmark most pages, I use the Browser History sidebar (CTRL+H) to relocate helpful sites. An online solution makes a lot more sense; I just have to bookmark more frequently.
Obsessive Compulsion Disorder and Format C Colon Slash Syndrome. If I kept an organized list of links from the time that I started out online it would be several reams thick. Unfortunately, I never keep a list of bookmarks for too long because I start to suspect my windows machines with performance and garbage in the registry after the install is over a year old. (I start to get anxi around the six-month mark...) I tend to give into this paranoia and format my drives somewhat frequently and in doing so I always forget to backup my bookmarks.
Blog-worthy, maybe. I started my blog to capture thoughts and findings on the web, but some days, posting a blog entry is a challenge. While I do use my blog to capture URLs for reference, not all URLs are blog worthy.
...and heck, if a colleague wanted to find a URL that I found useful, I could point them to my del.icio.us account. So, maybe I do want the social aspect after all. Hmm.
Well, let's see how this experiment plays out -- I've added the delicious tag cloud to my blog on the right. If it's not there in a few months, maybe I stopped using it.
by bryan at 12:48 AM 0 comments
Monday, January 29, 2007
the Church of Home Depot
I once heard that Home Depot does more business with Canada than the United States does with France. I'd believe it too, I go to Home Depot on the weekends so frequently, I call it Church.
by bryan at 4:08 PM 0 comments
Saturday, January 27, 2007
Trusted computing
Great animated short that stabs at Trusted Computing. From the short:
In a trusted computing environment the major goal is to protect us from potential threats. The original trusted computing idea is designed to let you decide what to consider as threat and what to consider as trust worthy. You can control by your own personal conviction. The industry's interpretation of the trusted computing idea looks quite similar, aiming at the same to fight threats and to make computing trustworthier. The main difference is that you cannot decide by your own what is trustworthy and what is not because they already decided for you... and they already decided not to trust you.
by bryan at 12:15 AM 0 comments
Friday, January 26, 2007
Can't Touch This - Touch Screen
I was really quite impressed with the iPhone's multi-touch technology as demonstrated in the MacWorld keynote, but this puts the iPhone to shame. This article on Fast Company might have you thinking that you accidentally woke up in the year 2054. Even better, check out this video demonstration. Update: This based on some amazing work that Jeff Han of NYU has produced. Multi-touch is probably a better name than Frustrated Total Internal Reflection.
by bryan at 11:28 PM 0 comments
Thursday, January 18, 2007
Wired 14.08: Be an Expert on Anything
Wired magazine did a how-to issue back in august where Stephen Colbert had the cover: how to be an expert on anything. My favourite: Don't be afraid to make things up. Never fear being exposed as a fraud. Experts make things up all the time. They're qualified to.
by bryan at 3:58 PM 0 comments
Friday, January 12, 2007
Internet Explorer 7: Quick Reference Sheet
I've always loved that FireFox has CTRL+K for a shorcut to the search window. Turns out it's CTRL+E: Internet Explorer 7: Quick Reference Sheet Here's a shortcut key listing for Firefox which conveniently lists Internet Explorer and Opera against FireFox, though I think it might be for IE6.
by bryan at 7:18 PM 0 comments
Monday, January 08, 2007
Updated Template
Along with Blogger's version upgrades are some new templates. Enjoy the new look. I chose this template because the width of the posts expands with the browser window. Maybe now I'll consider posting code examples :D
by bryan at 9:18 PM 0 comments
Sunday, January 07, 2007
VS.NET Blogging help
Stumbled across this great post over at Stephen Chu's blog, which points to an open source Visual Studio Add-in. The Add-in allows you to copy any code snippet into the clipboard as properly formatted HTML. A great tool for blogging!
by bryan at 2:08 PM 0 comments