Tuesday, March 31, 2009

The Lord of the Rings and Atlas Shrugged (via Reddit)

"There are two novels that can change a bookish fourteen-year old’s life: The Lord of the Rings and Atlas Shrugged. One is a childish fantasy that often engenders a lifelong obsession with its unbelievable heroes, leading to an emotionally stunted, socially crippled adulthood, unable to deal with the real world. The other, of course, involves orcs."

http://www.amptoons.com/blog/archives/2009/03/29/quote-8/

Justice and accountability, the American way (via Reddit)

1. The hospital makes a medical error; patient dies.
2. The hospital falsifies the medical record, including the testimony of the doctor given during the investigation.
3. The doctor who gave the testimony produces the original copy.
4. The hospital settles out of the court. The article does not say this, but I bet that the people who were involved in hiding the evidence probably still work for that hospital.

“There isn’t going to be a trial,” he said. “The hospital offered the patient’s family an 8-figure settlement, and they have accepted.”

http://open.salon.com/blog/amytuteurmd/2009/03/30/they_killed_my_patient_then_they_tried_to_hide_it

Malevich hits 1000th code review!

We've been using it for 3 months now in our team of roughly 45 people - 20 devs and 25 testers. This week we've had 1000th code review completed.

Here are a few stats, as of right now:

1008 change lists
17081 file versions
2863 review iterations
7851 comments

If you work at Microsoft and would like to try Malevich, I've set up a test change list here: http://sergeysomsg2/Malevich/default.aspx?cid=299. You can add yourself as a reviewer, leave comments in files, and submit review iterations.

If you're outside Microsoft and want to try it, the source and installation instructions are here: http://www.codeplex.com/Malevich.

Monday, March 30, 2009

Welcome to the ultimate in banana republics! (via Reddit)

Written by a former chief economist at IMF.

"Typically, these countries are in a desperate economic situation for one simple reason—the powerful elites within them overreached in good times and took too many risks. Emerging-market governments and their private-sector allies commonly form a tight-knit—and, most of the time, genteel—oligarchy, running the country rather like a profit-seeking company in which they are the controlling shareholders. When a country like Indonesia or South Korea or Russia grows, so do the ambitions of its captains of industry. As masters of their mini-universe, these people make some investments that clearly benefit the broader economy, but they also start making bigger and riskier bets. They reckon — correctly, in most cases — that their political connections will allow them to push onto the government any substantial problems that arise."

(emphasis mine)

http://www.theatlantic.com/doc/print/200905/imf-advice

Sunday, March 29, 2009

Empire strikes forward

It seems that vast majority of Star Wars technology was designed and built by the marketing department. Here are a few engineering suggestions that could help the bad guys win in Epsiodes -3 to 0...

1) Use firearms. Blasters are slow, not very powerful, and easily deflected by a light saber. An AK-47 would be far, far more effective. And don't forget the training! Despite the advantage in fire power, the storm troopers clearly can't shoot straight. Take them out to a range once in a while, and see how much more effective they become only after a couple training sessions!

2) If you use droids to fight your wars, build the best kind - do not waste time and money on anything else. It looks like the Destroyer model is more effective than50 units of the other kind (and even more effective than a Jedi). I doubt it could be more than 3 times more expensive. Why not just build Destroyers?

3) And while we are on this subject, let's have droids use more effective communication channels than human speech when they talk to each other. The same goes for all interfaces between the droids and everything else - spaceships, vehicles, and weapons. There is no reason while a droid should press a button to fire a weapon - a wireless interface would control it far more efficiently. Also, do invest in computerized targeting for your weapons. If your droids can already see, making them very accurate shooters is a relatively trivial task - ask any engineer.

4) Missiles seem to be an extremely effective weapon in Star Wars, except that there are very few of them. Why not build more of them and launch, say, 20 or 30 at a time? I doubt the Jedi, who have visible trouble dealing with one or two, would be able to escape ten. Also, consider building missiles that are FASTER than the star fighters they attack.

5) Consider using encryption. It looks like anyone can plug into the system anywhere and take control of everything in the space ships. This problem has been solved years ago! It really is not that hard.

6) Pack animals are slow, can't carry much, and are very, very hard to maintain. They need food, living compartments, etc. Consider eliminating them from the army in favor of mechanized transporters. Animals are not very effective in executions either - this has been proven many, many times. Instead of staging elaborate shows with giant predators that always fail to kill the prisoners, just shoot your captives, and put their heads on a stick.

7) I cannot overemphasize the importance of conventional firearms. If the probe sent to assassinate Princess Amidala had used a regular rifle instead of the poisonous centipedes, the subsequent events might have taken a very different turn. And in space, consider using nuclear weapons. The laser guns you currenlty mount on your ships are massively underpowered.

8) Did you know that space ships do not need wings to fly in space? Once you get entirely comfortable with this (yes, I do know it's extremely hard, given that they even make fying noises while moving through vacuum, but making this leap of faith might be crucial to your survival - do it!) you can start using very different design paradigms - like, for example, minimizing the surface area so your spacecraft is easier to protect and harder to target.

9) On the subject of spaceship designs - there must be something in the way you build them that makes them explode after they are hit. Unfortunately, this applies to all other vehicles as well. While this generates impressive visuals, this design point leads to unnecessary casualties among your troops. Consider enclosing whatever it is that explores in extra layers of protection. You have already solved this for your small weapons, which do not seem to have the same problem. Why not use similar design everywhere?

10) This is more of a tactical rather than an engineering advice, but I will give it anyway. Instead of deploying the Walkers and other weird ground assault vehicles, consider attacking from the air to suppress the adversary's ground troops.

Follow these simple steps, and you will be invincible - at least as long as the Good Guys(TM) are controlled by the marketing people.

Saturday, March 28, 2009

Free as in beer!

The GNU connotation of software as a form of speech never made any sense to me.

"Free speech" means the freedom of a person to express himself or herself without a fear of persecution. It does not mean the freedom to use the results of this expression by everyone.

Quite the contrary - the literary works by the greatest writers are the results of exercising free speech. But rip "Harry Potter", and you will - deservedly - find yourself slapped with a lawsuit, a fine, and maybe a jail sentence.

The free software movement has produced a lot of great things - from GNU development tools to Linux, BSD, Apache, and Firefox. It has also produced legions of activists that bring in the level of politics, acrimony, and fanboyism that is unbecoming to honorable competition in an area that is as much a science as it is an art.

By doing so they actually take the freedom away from people who chose to distribute the results of their work through channels that are different from the ones favored by the Free Software Foundation.

So here's my variant of "free software". The code distributed on this blog is free - free as in beer. You can redistribute it in full or in parts, with or without source code. You can use it in any derivative work without giving me any credit.

//-----------------------------------------------------------------------
// <copyright>
// Copyright (C) Sergey Solyanik.
//
// This software is in public domain and is "free as in beer". It can be
// redistributed in full or in parts for free and without any preconditions.
// </copyright>
//-----------------------------------------------------------------------

De-gnoming your blog

My blog has been filling up with spam over the last few months - the nasty, long messages that pollute the comment space and are a pain to delete because the bastards were programmatically sticking them in tens and tens of posts.

Luckily, Google has APIs to Blogger, and better yet, they even have C# libraries than make coding against these API easy. I didn't even have to dust off Eclipse!

So an hour later I had the program that allowed me to clean up the spam quickly.

The code sucks in the entire blog (including the abstracts of posts and comments) and allows one to quickly search and delete spam. Type 'help' on the command prompt, and it will explain the usage.

A few convenient shortcuts:

List or delete all comments from someone:
list|delete comments by name
e.g.
list comments by peter.w

List or delete all comments that have the same author as well as the text:

list|delete comments like sample_comment_uri
e.g.
list comments like http://www.blogger.com/feeds/3554166144204741789/3010398536200323405/comments/default/4945114575786176865

List or delete all comments with a phrase in title:
list|delete comments with text_string
e.g.
list comments with wow gold

List comments in a blog or a message:
list comments in blog_name|first_words_of_post_title
e.g.
list comments in Back to Microsoft
list comments in 1-800-MAGIC


If you're one of the truly insane types that would run code compiled by a random guy from the Internet, the built version is here: http://www.solyanik.com/drop/BlogDeSpammer.zip. Otherwise, the code is below, free as in beer!

Incidentally, it might make a good primer on how to retrieve and manipulate Blogger posts and comments programmatically.


//-----------------------------------------------------------------------
// <copyright>
// Copyright (C) Sergey Solyanik.
//
// This software is in public domain and is "free as in beer". It can be
// redistributed in full or in parts for free and without any preconditions.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Google.GData.Client;

namespace BlogDeSpammer
{
/// <summary>
/// Comment class. Just a trivial holder of essential comment data.
/// </summary>
class Comment
{
public string Text;
public string Author;
public string EditUri;

public Comment(string text, string author, string editUri)
{
Text = text;
Author = author;
EditUri = editUri;
}
}

/// <summary>
/// Post class. Just a trivial holder of essential post data.
/// </summary>
class Post
{
public string Title;
public string CommentsFeed;
public ICollection<Comment> Comments;

public Post(string title, string commentsFeed)
{
Title = title;
CommentsFeed = commentsFeed;
Comments = null;
}
}

/// <summary>
/// Blog class. Just a trivial holder of essential blog data.
/// </summary>
class Blog
{
public string Name;
public string Feed;
public ICollection<Post> Posts;
public Blog(string name, string feed)
{
Name = name;
Feed = feed;
Posts = null;
}
}

/// <summary>
/// Main functionality.
/// </summary>
class Program
{
/// <summary>
/// Gets collection of blogs belonging to the user.
/// </summary>
/// <param name="blogger"> Blogger service. Must be initialized with the parameters.
/// </param>
/// <returns> Collection of blogs. </returns>
private static ICollection<Blog> GetBlogs(Service blogger)
{
List<Blog> blogs = new List<Blog>();

FeedQuery query = new FeedQuery();
query.Uri = new Uri("http://www.blogger.com/feeds/default/blogs");
AtomFeed results = blogger.Query(query);
while (results != null && results.Entries.Count > 0)
{
foreach (AtomEntry entry in results.Entries)
{
string blogFeedLink = null;
foreach (AtomLink link in entry.Links)
{
if (BaseNameTable.ServiceFeed.Equals(link.Rel))
{
blogFeedLink = link.HRef.ToString();
break;
}
}
blogs.Add(new Blog(entry.Title.Text, blogFeedLink));
}

if (results.NextChunk == null)
break;

query.Uri = new Uri(results.NextChunk);
results = blogger.Query(query);
}

return blogs;
}

/// <summary>
/// Retrieves all the posts for the blog.
/// </summary>
/// <param name="blogger"></param>
/// <param name="blog"></param>
/// <returns> Collection of posts. </returns>
private static ICollection<Post> GetPosts(Service blogger, Blog blog)
{
List<Post> posts = new List<Post>();

FeedQuery query = new FeedQuery();
query.Uri = new Uri(blog.Feed);
AtomFeed results = blogger.Query(query);
while (results != null && results.Entries.Count > 0)
{
foreach (AtomEntry entry in results.Entries)
{
string commentsFeedLink = null;
foreach (AtomLink link in entry.Links)
{
if ("replies".Equals(link.Rel) &&
"application/atom+xml".Equals(link.Type))
{
commentsFeedLink = link.HRef.ToString();
break;
}
}
Console.Write(".");
if (commentsFeedLink != null)
posts.Add(new Post(entry.Title.Text, commentsFeedLink));
}

if (results.NextChunk == null)
break;

query.Uri = new Uri(results.NextChunk);
results = blogger.Query(query);
}

return posts;
}

/// <summary>
/// Gets comments for the given post.
/// </summary>
/// <param name="blogger"> Blogger service, initialized with credentials. </param>
/// <param name="post"> The post. </param>
/// <returns> Collection of credentials. </returns>
private static ICollection<Comment> GetComments(Service blogger, Post post)
{
List<Comment> comments = new List<Comment>();

FeedQuery query = new FeedQuery();
query.Uri = new Uri(post.CommentsFeed);
AtomFeed results = blogger.Query(query);
while (results != null && results.Entries.Count > 0)
{
foreach (AtomEntry entry in results.Entries)
{
Console.Write(".");
comments.Add(new Comment(entry.Title.Text, entry.Authors[0].Name,
entry.EditUri.ToString()));
}

if (results.NextChunk == null)
break;

query.Uri = new Uri(results.NextChunk);
results = blogger.Query(query);
}
return comments;
}

/// <summary>
/// Main. Does all the work.
/// </summary>
/// <param name="args"> Command line parameters. </param>
static void Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("Usage: blogdespammer username password");
return;
}

string username = args[0];
string password = args[1];

Console.Write("Loading");

Service blogger = new Service("blogger", "BlogDeSpammer");
blogger.Credentials = new GDataCredentials(username, password);

ICollection<Blog> blogs = GetBlogs(blogger);
foreach (Blog blog in blogs)
{
blog.Posts = GetPosts(blogger, blog);
foreach (Post post in blog.Posts)
post.Comments = GetComments(blogger, post);
}

Console.WriteLine("\n\nType 'help' for help, 'exit' to quit.");
for (; ; )
{
Console.Write("> ");
string input = Console.ReadLine();
if (input.Equals("exit", StringComparison.CurrentCultureIgnoreCase))
break;

if (input.Equals("help", StringComparison.CurrentCultureIgnoreCase))
{
Console.WriteLine(
"Type:\n\texit\n\t\t-- to quit.\n\thelp\n\t\t--to get this text.\n");
Console.WriteLine(
"\tlist blogs\n\t\t-- to list available blogs.\n");
Console.WriteLine(
"\tlist posts in blog\n\t\t-- to list posts in a blog.\n");
Console.WriteLine(
"\tlist comments in blog\n\t\t-- to list comments in a blog.\n");
Console.WriteLine(
"\tlist comments in post\n\t\t-- to list comments in a post.\n");
Console.WriteLine(
"\tlist comments by author\n\t\t-- to list comments by " +
"a particular author.\n");
Console.WriteLine(
"\tlist comments like commenturi\n\t\t-- to list comments " +
"identical to a comment.\n");
Console.WriteLine(
"\tlist comments with text\n\t\t-- to list comments " +
"containing text segment.\n");
Console.WriteLine(
"\tdelete comments by author\n\t\t-- to delete all " +
"comments by a particular author.\n");
Console.WriteLine(
"\tdelete comments like commenturi\n\t\t-- to delete " +
"all comments that are the same as a " +
"comment with this URI.\n");
Console.WriteLine(
"\tdelete comments with text\n\t\t-- to list comments " +
"containing text segment.\n");
Console.WriteLine("Names above could be either text names or URIs.");
continue;
}

if (input.Equals("list blogs",
StringComparison.CurrentCultureIgnoreCase))
{
foreach (Blog blog in blogs)
Console.WriteLine("{0} ({1})", blog.Name, blog.Feed);
continue;
}

if (input.StartsWith("list posts in ",
StringComparison.CurrentCultureIgnoreCase))
{
string blogname = input.Substring(14);
foreach (Blog blog in blogs)
{
if (blog.Name.StartsWith(blogname) ||
blog.Feed.Equals(blogname))
{
foreach (Post post in blog.Posts)
Console.WriteLine("{0} ({1})", post.Title,
post.CommentsFeed);
}
}
continue;
}

if (input.StartsWith("list comments in ",
StringComparison.CurrentCultureIgnoreCase))
{
string name = input.Substring(17);
foreach (Blog blog in blogs)
{
if (blog.Name.StartsWith(name) || blog.Feed.Equals(name))
{
foreach (Post post in blog.Posts)
{
Console.WriteLine("{0} {1}", post.Title,
post.CommentsFeed);
foreach (Comment comment in post.Comments)
Console.WriteLine("-- {0} ({1})\n{2}\n",
comment.Author, comment.EditUri,
comment.Text);
}
break;
}

foreach (Post post in blog.Posts)
{
if (post.Title.StartsWith(name) ||
post.CommentsFeed.Equals(name))
{
Console.WriteLine("{0} {1}", post.Title,
post.CommentsFeed);
foreach (Comment comment in post.Comments)
Console.WriteLine("-- {0} ({1})\n{2}\n",
comment.Author, comment.EditUri,
comment.Text);
}
}
}
continue;
}

if (input.StartsWith("list comments by ",
StringComparison.CurrentCultureIgnoreCase))
{
string author = input.Substring(17);
foreach (Blog blog in blogs)
{
foreach (Post post in blog.Posts)
{
foreach (Comment comment in post.Comments)
{
if (comment.Author.Equals(author,
StringComparison.CurrentCultureIgnoreCase))
{
Console.WriteLine("{0} : {1}",
blog.Name, post.Title);
Console.WriteLine("-- {0} ({1})\n{2}\n",
comment.Author, comment.EditUri,
comment.Text);
}
}
}
}
continue;
}

if (input.StartsWith("list comments with ",
StringComparison.CurrentCultureIgnoreCase))
{
string text = input.Substring(19);
foreach (Blog blog in blogs)
{
foreach (Post post in blog.Posts)
{
foreach (Comment comment in post.Comments)
{
if (comment.Author.Contains(text) ||
comment.Text.Contains(text))
{
Console.WriteLine("{0} : {1}",
blog.Name, post.Title);
Console.WriteLine("-- {0} ({1})\n{2}\n",
comment.Author, comment.EditUri,
comment.Text);
}
}
}
}
continue;
}

if (input.StartsWith("list comments like ",
StringComparison.CurrentCultureIgnoreCase))
{
string prototypeUri = input.Substring(19);
Comment prototypeComment = null;

foreach (Blog blog in blogs)
{
foreach (Post post in blog.Posts)
{
foreach (Comment comment in post.Comments)
{
if (prototypeUri.Equals(comment.EditUri))
{
prototypeComment = comment;
goto found;
}
}
}
}
continue;

found:
foreach (Blog blog in blogs)
{
foreach (Post post in blog.Posts)
{
foreach (Comment comment in post.Comments)
{
if (comment.Author.Equals(prototypeComment.Author,
StringComparison.CurrentCultureIgnoreCase) &&
comment.Text.Equals(prototypeComment.Text))
{
Console.WriteLine("{0} : {1}", blog.Name, post.Title);
Console.WriteLine("-- {0} ({1})\n{2}\n",
comment.Author, comment.EditUri,
comment.Text);
}
}
}
}
continue;
}

if (input.StartsWith("delete comments by ",
StringComparison.CurrentCultureIgnoreCase))
{
string author = input.Substring(19);

foreach (Blog blog in blogs)
{
foreach (Post post in blog.Posts)
{
List<Comment> deleted = new List<Comment>();

foreach (Comment comment in post.Comments)
{
if (comment.Author.Equals(author,
StringComparison.CurrentCultureIgnoreCase))
{
Console.WriteLine("{0} : {1}", blog.Name, post.Title);
Console.WriteLine("-- {0} ({1})\n{2}\n",
comment.Author, comment.EditUri,
comment.Text);
blogger.Delete(new Uri(comment.EditUri));
deleted.Add(comment);
}
}

foreach (Comment comment in deleted)
post.Comments.Remove(comment);
}
}
continue;
}

if (input.StartsWith("delete comments like ",
StringComparison.CurrentCultureIgnoreCase))
{
string prototypeUri = input.Substring(21);
Comment prototypeComment = null;

foreach (Blog blog in blogs)
{
foreach (Post post in blog.Posts)
{
foreach (Comment comment in post.Comments)
{
if (prototypeUri.Equals(comment.EditUri))
{
prototypeComment = comment;
goto found;
}
}
}
}
continue;

found:
foreach (Blog blog in blogs)
{
foreach (Post post in blog.Posts)
{
List<Comment> deleted = new List<Comment>();

foreach (Comment comment in post.Comments)
{
if (comment.Author.Equals(prototypeComment.Author,
StringComparison.CurrentCultureIgnoreCase) &&
comment.Text.Equals(prototypeComment.Text))
{
blogger.Delete(new Uri(comment.EditUri));
deleted.Add(comment);
Console.WriteLine("{0} : {1}", blog.Name, post.Title);
Console.WriteLine("-- {0} ({1})\n{2}\n",
comment.Author, comment.EditUri,
comment.Text);
}
}
foreach (Comment comment in deleted)
post.Comments.Remove(comment);
}
}
continue;
}

if (input.StartsWith("delete comments with ",
StringComparison.CurrentCultureIgnoreCase))
{
string text = input.Substring(21);
foreach (Blog blog in blogs)
{
foreach (Post post in blog.Posts)
{
List<Comment> deleted = new List<Comment>();

foreach (Comment comment in post.Comments)
{
if (comment.Author.Contains(text) ||
comment.Text.Contains(text))
{
blogger.Delete(new Uri(comment.EditUri));
deleted.Add(comment);
Console.WriteLine("{0} : {1}", blog.Name, post.Title);
Console.WriteLine("-- {0} ({1})\n{2}\n",
comment.Author, comment.EditUri,
comment.Text);
}
}

foreach (Comment comment in deleted)
post.Comments.Remove(comment);
}
}
continue;
}

Console.WriteLine(
"Can't parse. Please use 'help' to look up valid commands.");
}


Console.WriteLine("Done!");
}
}
}

Thursday, March 26, 2009

Funny quote: arguing with idiots

"Never argue with an idiot. They bring you down to their level and beat you with experience."

Source unknown.

Monday, March 23, 2009

Death of journalism

So this is what passes for publishable news these days - Paul Krugman writes two pages in NYT Opinions section (http://www.nytimes.com/2009/03/23/opinion/23krugman.html?_r=1), and Reuters immediately quotes half of it on its web site: http://www.reuters.com/article/GCA-CreditCrisis/idUSTRE52M4SS20090323.

Guess which one of them was cross-linked from Reddit? You guessed right - the Reuters one.

What should have been a link to the original article, has become a copycat in its own right. A page was added to the Web. A "journalist" was paid for it. The reporting standards went down one notch further. The life continues...

Sunday, March 22, 2009

The big takeover (via Reddit)

"These people need their trips to Baja, their spa treatments, their hand jobs," says an official involved in the AIG bailout, a serious look on his face, apparently not even half-kidding. "They don't function well without them."

http://www.rollingstone.com/politics/story/26793903/the_big_takeover/print

If you thought iFart and iBoobs were weird...

...behold the Cylon Detector iPhone App!

http://www.scifi.com/battlestar/iphone/

Hooray for long American tradition for consumer-driven innovation!
----
Thought about it some more, and came to conclusion that the consumers of this stuff are no weirder than Sarah Palin voters after all :-)...

Friday, March 20, 2009

On hacking cute shiny objects (via Reddit)

"Safari on the Mac is easier to exploit. The things that Windows do to make it harder (for an exploit to work), Macs don’t do. Hacking into Macs is so much easier. You don’t have to jump through hoops and deal with all the anti-exploit mitigations you’d find in Windows.

It’s more about the operating system than the (target) program. Firefox on Mac is pretty easy too. The underlying OS doesn’t have anti-exploit stuff built into it."

http://blogs.zdnet.com/security/?p=2941

Thursday, March 12, 2009

New TFS documentation pushed

I blogged earlier (http://1-800-magic.blogspot.com/2009/02/everything-you-know-about-diffing-files.html) about how terrible TFS API documentation was.

This is no longer true - the latest round that just got pushed on MSDN is solid. Good job, team!

http://msdn.microsoft.com/en-us/library/bb130146.aspx

Monday, March 9, 2009

How to install TFS on a single domain-joined server

Since TFS documentation is so convoluted, I thought I would put together a SIMPLE list of what needs to be done.

Software required:

  • Windows Server 2008 Standard

  • SQL Server 2008 Standard Edition

  • Sharepoint Server 2007 WITH SP1

  • TFS 2008

  • TFS 2008 SP1



All this software is available from MSDN.

Also: one domain account different from the installing user. Should NOT have admin rights. Should NOT be a local account. This is very important.

Prework:
Prepare TFS 2008 slipstreamed with SP1 as follows:

  1. Copy the contents of TFS DVD AT folder to a directory, say c:\tfs\base:
        xcopy /die d:\at c:\tfs\base

  2. Unpack the SP1 installer to a different directory, say c:\tfs\sp1:
        en_visual_studio_team_system_2008_team_foundation_server_service_pack_1_x86_x64wow.exe /extract:c:\tfs\sp1

  3. Slip-stream the sp1:
        msiexec /a c:\tfs\base\vs_setup.msi /p TFS90sp1-KB949786.msp TARGETDIR=c:\tfs\slipstream

  4. Create ISO and burn it on a DVD using ISO Recorder (http://isorecorder.alexfeinman.com/W7.htm)



Installation steps:

You must be logged as a domain user and have admin rights to the machine.

If you are installing on a VM, take snapshots between the installation steps, so if something goes wrong, you can restart easily.


  1. Install Server 2008. Configure, join the domain, apply all updates. Reboot if necessary.

  2. Add Web Server (IIS) role from Server Manager->Roles->Add roles. Select all components.

  3. Install Windows Sharepoint Server 2007 SP1 using all defaults (non-SP1 build will not install on Server 2008).

  4. Install SQL Server 2008.
    • Select all components to install.

    • On the page that allows you to specify the instance name, select the default instance.

    • On service accounts page, specify "Network Service" everywher (where it's editable) and make all services that can be made autostart do so.

    • Ensure Windows authentication is selected (the default), and click "Add current user" to administrators of the computer on the two pages which ask you to specify the admin user (one for SQL, one for analysis services).

    • On the page that asks you about configuring reporting, select "Install, but leave Reporting Services unconfigured" option.

  5. Install TFS.
    • Do not change the default SQL instance name (it will be your computer name - definitely DO NOT switch it to '.').

    • Use "Network Service" where it allows you to (the default), and specify that non-admin user account from pre-requisitive section on the next page.

    • The installation will also ask you for the sharepoint admin URL. The default is incorrect. You can find the right port by opening the sharepoint admin link from the start menu.

  6. RESTART THE SERVER. TFS does not prompt you to do this, but the permissions are synchronized on the service restart, so you won't be able to create a new project until you do this.



You're done.

Ensure that the client computer has Team Explorer that is upgraded to SP1 (creating project will fail in misterious ways if server has SP1 and client does not), and create a new project to test the newly installed system.

Adventures in TFS, continued

I was chronicling my attempt to install TFS on a VM at home so I have a better platform for fixing bugs related to that source control system. The beginning of this adventure is here: http://1-800-magic.blogspot.com/2009/03/adventures-in-tfs.html.

I should have given up on this thing a long, long time ago, but it started feeling like a technical challenge. One man vs. TFS. "The old man and the sea" of sorts. Although there the challenge was much less wanton :-).

Day 7. Create a new VM. This time I take a snapshot in Hyper-V every time something new gets installed.

  • Windows Server 2008 + all updates

  • Snapshot

  • Sharepoint Server 2007 SP1

  • Snapshot

  • SQL Server 2008 Standard

  • Snapshot


This time around TFS setup crashes after complaining that a command with half-a-page worth of command line options returned a non-zero result. Looking at winerror.h, the returned result is an unhandled exception. When I run the same command from a prompt (lots of retyping!), the exception has something to do with SQL reporting services.

At this point I give up and start reading the manual. TFS installation manual is an exercise in hyper-linking, forming a complex graph with many cycles in it. Instead of having 2-3 lists (if you are creating a single server deployment, do this, this, and this), it is an unwieldy bowl of spaghetti docs, each step cross-linking at least 5 more subtopics.

After spending 30 minutes down this maze of twisty little passages, all alike, the only difference I can find is about installing SQL server with unconfigured reporting services.

I roll back to the checkpoint after sharepoint, and this time I choose "Install but not configure reporting services".

TFS install can now complete, but I am back to the square one - trying to create the project in Visual Studio does not work - again, the error message is vague, something about reporting services.

Day 8.

I suddenly remember that I applied Visual Studio SP1 BEFORE I installed TFS client on my client machine. Everything worked so far just editing/checking files in, but I decide to reinstall SP1 just to make sure.

An hour and a half later - yes, this is how long it takes VS SP1 to install - everything works! That was the problem...

Thursday, March 5, 2009

Adventures in TFS

Now that more and more teams are using Malevich (http://www.codeplex.com/Malevich), I am getting more and more TFS users. TFS had become quite popular at Microsoft because it integrates very nicely with Visual Studio, and of course DevDiv is using it (as they should) for dogfood.

TFS has many idiosyncrasies that Perforce does not, so occasionally I am getting bug reports for things that work nicely in Perforce/Source Depot, but not in TFS.

For a while, I was using CodePlex's Malevich enlistment for fixing bugs, but now that the bugs had become harder and harder to reproduce and require more and more setup, I decided to install my own version of TFS.

One thing that most TFS developers usually don't have to do, is TFS setup. Perforce is simple, really simple. You just copy an executable to a computer and run it - that's all. Well, you do have to open a port in the firewall, then the first time you log in, set up an admin account.

Here's what I had to go through to get TFS to work. Of course I was only working at home, at night, so a day is not really a day - it's just a few hours, sometimes even less.

Day 1. Try to install TFS on one of my servers. Discover that it does not work on 64-bit machines (huh???).

Day 2. Try to install TFS on one of my 32-bit servers. The setup at least starts now. If I use MYCOMPUTERNAME as a SQL Server target (the default instance is SQL 2008 on that box), the setup dies with many complaints about SQL Full Text Search not being installed/started/set up to start automatically. If I use '.' as an instance name, the installation goes through - up to the point where it wants a Sharepoint server. I don't have the Sharepoint server.

Day 3. I am turning on Hyper-V on one of my bigger servers. It's a dual Xeon5030 box, so 4 cores. Nice machine. Server 2008 installs almost instantaneously in Hyper-V, and the speed - I can't tell the difference. I also install SQL Server 2008.

Day 4. I have Sharepoint 2007 from MSDN, but it does not install on Server 2008. Need SP1. So I download Sharepoint 2007 with SP1. Install it. Trying to install TFS. No luck - the installation fails because it cannot find SQL Reporting Services. I am verifying - yes, reporting services are installed.

Day 5. I realize that when I use '.' it is finding a SQL 2005 instance brought in by Sharepoint. When I use MACHINENAME, it is finding SQL 2008 instance that I installed for it. SQL 2005 (that instance) does not have reporting services. SQL 2008 does not have Full Text Search as a separate service, so TFS setup is incapable to detect it. Google says I need SP1 for TFS 2008 to install on SQL Server 2008.

Day 6. Checking MSDN - MSDN does not have TFS with slip-streamed SP1. Google gets me an article that describes how to do it myself. The article is more than a bit imprecise, so I am reproducing the actual steps here:
  • Copy the contents of TFS DVD AT folder to a directory, say c:\tfs\base (xcopy /die d:\at c:\tfs\base).

  • Unpack the SP1 installer to a different directory, say c:\tfs\sp1 ( en_visual_studio_team_system_2008_team_foundation_server_service_pack_1_x86_x64wow.exe /extract:c:\tfs\sp1). Why does this have x64wow in its name despite the fact that it won't work on x64, wow or no wow - no idea.

  • Slip-stream the sp1 (msiexec /a c:\tfs\base\vs_setup.msi /p TFS90sp1-KB949786.msp TARGETDIR=c:\tfs\slipstream)


You're done, the slipstream directory contains TFS 2008 SP1. The rest of the stuff on the TFS DVD (build, TE) is not slip-streamable, so just the TFS part is what you need. Make an image from this directory and voila - this will actually install on Server 2008 with SQL Server 2008!

I am trying to now create a new TFS project. I can connect to the server, but when I do try to create a project, it fails.

TF30004: The New Team Project Wizard encountered an unexpected error while initializing the Microsoft.ProjectCreationWizard.Reporting plug-in.

Explanation
TF30171: The Microsoft.ProjectCreationWizard.Reporting plug-in used to create the new team project could not be initialized and returned the following error: TF30224: Failed to retrieve projects from the report server. Please check that the SQL Server Reporting Services Web and Windows services are running and you have sufficient privileges for creating a project..

User Action
Contact your Team Foundation Server administrator.


I am trying to investigate this. It turns out that I have no access to SQL Reporting services - I've installed SQL server as a local admin, and despite the fact that I've added Domain Admins, my user name, and everything to the admins, it did not work.

I decide that the easiest thing would be to just uninstall everything, and reinstall it from a normal user account.

Unfortunately, I start uninstallation from SQL Server. After I uninstall SQL Server, TFS installer crashes on uninstall. It looks like the VM is toast...

To be continued...

Tuesday, March 3, 2009

A very wonderful opportunity...

...has just presented itself in the form of you potentially paying $1000 for 4 gigs of RAM:

http://store.apple.com/us/configure/MB419LL/A?mco=NDE4Mzg5MA

Also, did you know that FileMaker is "#1 name in databases"? You do now!

Sunday, March 1, 2009

Just how dumb are we, really?

http://www.gallup.com/poll/116065/Americans-Views-Bank-Takeovers-Appear-Fluid.aspx

Gallup poll asks half of their polling sample whether they are for or against temporary "takeover" of the banks, and the other half whether they are for or against temporary "nationalization". The rest of the question is identical.

On the takover question: 54% for, 44% against, 3% no opinion.

On the nationalization question: 37% for, 57% against, 6% no opinion.

These people are allowed to vote?