Monday, March 30, 2009

Send emails in Hindi – GMail Transliteration

Google today released its automatic transliteration technology for emails in Indian language (via the Official Google Blog). This means that you can type out words as they sound in hind and Gmail will automatically translate them to their Indian script. From my limited testing, I found it works very well…

Here is a video of my test of the technology.

imageThe english text that I typed: “Namaste India, yeah GMAIL hai!” (namaste, this is GMail) –> नमस्ते इंडिया, येः जीमेल है!

To be able to take advantage of the automatic transliteration, you need to turn it on using the GMail toolbar: 

If you do not see the Indian language button, then you need to enable transliteration from the Settings –> Languages menu.


ASP.Net and Databases – Tip for making databinding faster

Do you set the DataSourceMode property on your DataSources?

image The DataSourceMode property on DataSources (such as SqlDataSource) allows you to specify whether the reading of the data will be performed using a DataReader or a DataSet.

Why does this matter? A DataReader is a forward only reader and hence is faster and requires less memory. On the other hand DataSets are allow you to read, update, insert, delete data and hence require more memory and are slower than DataReaders. Knowing this is important, because if you are loading data into a ComboBox, why use a DataSet, when a DataReader would do?

Remember: The default value for DataSourceMode is DataSet.

<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
            ConnectionString="<%$ ConnectionStrings:AdventureWorks2008ConnectionString %>" 
            SelectCommand="SELECT ProductCategoryID, Name FROM Production.ProductCategory"></asp:SqlDataSource>

ASP.Net page life-cycle and data-binding

1. The page object is created
2. The page life cycle begins, and the events Page.Init and Page.Load fire.
3. All other control events fire.
4. The data source controls perform any updates. If a row is being updated, the Updating and
Updated events fire. If a row is being inserted, the Inserting and Inserted events fire. If a
row is being deleted, the Deleting and Deleted events fire.
5. The Page.PreRender event fires.
6. The data source controls perform any queries and insert the retrieved data in the linked controls.
The Selecting and Selected events fire at this point.
7. The page is rendered and disposed of.

Remember: Data binding is performed on every postback

Bookmarking pages in a PDF document

One of the greatest mysteries to me is the fact that Adobe completely skipped adding the ability for the readers of a PDF document to bookmark pages. Especially when some of the pDF documents can be 1000s of pages long, having the ability to add a bookmark, I think is an extremely important feature that would increase the usability of Acrobat Reader.

So, while we wait for Adobe to add the feature to Acrobat, here is a work-around:

The work-around uses JavaScript to add additional commands that allow you to bookmark the current page, manage multiple bookmarks and the clincher, when you open a PDF document, the java-script takes you directly to the last bookmarked page. Extremely nifty and I found it here: 100 Industrial-Strength Tips & Tools

Here is what you need to do:

Install the javaScript widget to enable bookmarking in Acrobat Reader.

1. Go to the folder: C:\Program Files\Adobe\Reader 8.0\Reader\Javascripts

2. Create a new text file and rename it to bookmarks.js

3. Copy the following text and paste it into the bookmarks.js file

// use this delimiter for serializing our array
var bp_delim= '%#%#';

function SaveData( data ) {
  // data is an array of arrays that needs
  // to be serialized and stored into a persistent
  // global string
  var ds= '';
  for( ii= 0; ii< data.length; ++ii ) {
    for( jj= 0; jj< 3; ++jj ) {
      if( ii!= 0 || jj!= 0 )
        ds+= bp_delim;
      ds+= data[ii][jj];
  global.pdf_hacks_js_bookmarks= ds;
  global.setPersistent( "pdf_hacks_js_bookmarks", true );

function GetData() {
  // reverse of SaveData; return an array of arrays
  if( global.pdf_hacks_js_bookmarks== null ) {
    return new Array(0);

  var flat= global.pdf_hacks_js_bookmarks.split( bp_delim );
  var data= new Array();
  for( ii= 0; ii< flat.length; ) {
    var record= new Array();
    for( jj= 0; jj< 3 && ii< flat.length; ++ii, ++jj ) {
      record.push( flat[ii] );
    if( record.length== 3 ) {
      data.push( record );
  return data;

function AddBookmark() {
  // query the user for a name, and then combine it with
  // the current PDF page to create a record; store this record
  var label= 
    app.response( "Bookmark Name:",
                  "Bookmark Name",
                  false );
  if( label!= null ) {
    var record= new Array(3);
    record[0]= label;
    record[1]= this.path;
    record[2]= this.pageNum;

    data= GetData();
    data.push( record );
    SaveData( data );

function ShowBookmarks() {
  // show a pop-up menu; this seems to only work when
  // a PDF is alreay in the viewer;
  var data= GetData();
  var items= '';
  for( ii= 0; ii< data.length; ++ii ) {
    if( ii!= 0 )
      items+= ', ';
    items+= '"'+ ii+ ': '+ data[ii][0]+ '"';
  // assemble the command and the execute it with eval()
  var command= 'app.popUpMenu( '+ items+ ' );';
  var selection= eval( command );
  if( selection== null ) {
    return; // exit

  // the user made a selection; parse out its index and use it
  // to access the bookmark record
  var index= 0;
  // toString() converts the String object to a string literal
  // eval() converts the string literal to a number
  index= eval( selection.substring( 0, selection.indexOf(':') ).toString() );
  if( index< data.length ) {
    try {
      // the document must be 'disclosed' for us to have any access
      // to its properties, so we use these FirstPage NextPage calls
      app.openDoc( data[index][1] );
      app.execMenuItem( "FirstPage" );
      for( ii= 0; ii< data[index][2]; ++ii ) {
        app.execMenuItem( "NextPage" );
    catch( ee ) {
      var response= 
        app.alert("Error trying to open the requested document.\nShould I remove this bookmark?", 2, 2);
      if( response== 4 && index< data.length ) {
        data.splice( index, 1 );
        SaveData( data );

function DropBookmark() {
  // modelled after ShowBookmarks()
  var data= GetData();
  var items= '';
  for( ii= 0; ii< data.length; ++ii ) {
    if( ii!= 0 )
      items+= ', ';
    items+= '"'+ ii+ ': '+ data[ii][0]+ '"';
  var command= 'app.popUpMenu( '+ items+ ' );';
  var selection= eval( command );
  if( selection== null ) {
    return; // exit

  var index= 0;
  index= eval( selection.substring( 0, selection.indexOf(':') ).toString() );
  if( index< data.length ) {
    data.splice( index, 1 );
    SaveData( data );

function ClearBookmarks() {
  if( app.alert("Are you sure you want to erase all bookmarks?", 2, 2 )== 4 ) {
    SaveData( new Array(0) );

app.addMenuItem( {
cName: "-",              // menu divider
cParent: "View",         // append to the View menu
cExec: "void(0);" } );

app.addMenuItem( {
cName: "Bookmark This Page &5",
cParent: "View",
cExec: "AddBookmark();",
cEnable: "event.rc= ( != null);" } );

app.addMenuItem( {
cName: "Go To Bookmark &6",
cParent: "View",
cExec: "ShowBookmarks();",
cEnable: "event.rc= ( != null);" } );

app.addMenuItem( {
cName: "Remove a Bookmark",
cParent: "View",
cExec: "DropBookmark();",
cEnable: "event.rc= ( != null);" } );

app.addMenuItem( {
cName: "Clear Bookmarks",
cParent: "View",
cExec: "ClearBookmarks();",
cEnable: "event.rc= true;" } );


Using the bookmark widget:

If you installed the widget correctly, you should get a few options under the “View” menu to manage bookmarks:


Using these commands, you can create, delete and navigate to bookmarks.

Important: Keep in mind that these bookmarks are not saved within the document and will be lost if you copy the PDF document to a different computer. The bookmarks work like normal browser bookmarks and are stored locally on your machine.

Reopening a PDF document will automatically take you to the last bookmarked page.

Sunday, March 29, 2009

Saturday, March 28, 2009


Taking great baby pictures – 10 tips from Nick Kelsh

Here are the ones that I think are useful from Nick Kelsh’s post:


Get Close

Most amateurs never shoot a close-up and close-ups are so powerful. Fill the frame with your baby’s face and leave out the lamps and furniture and all of that other visual clutter. A good close-up of a baby can be other-worldly.

Baby in bath tub closeup

Experiment with the flash off

A flash on a camera is a very handy thing. It allows you to take sharp pictures in dark rooms. But it does something else, too. It ruins the mood. It’s about as romantic as the headlights on your car. A picture taken with a flash is the signature look of amateur snapshots. Use it if you’re shooting snapshots (don’t get me wrong - I love snapshots) but if you want to take pictures that will make other people say, “Hey you’re a great photographer!” turn off the flash.


Find beautiful light

If you want to shoot beautiful baby portraits this may be the most important step. With your flash off, put your baby in some soft window light or the light of an open, outside door . This is the light Rembrandt built a career around. If you get just how profound this tip is you are well are your way to moving up the photographic food chain.

Baby drinking bottle

Keep your backgrounds simple

You could not possibly error on the side of too simple with this. I spend about half my time shooting pictures trying to find clean, simple backgrounds. Why is that stop sign sticking out of your baby’s head?

Baby in bath tub

Take advantage of the moment

You have what every professional photographer wants – access. Store your camera is the same place all the time and always turn your camera off with the same settings. Be ready for the stuff only parents see.

Baby smiling

Crank up your ISO

I don’t want to get too technical here, but I rarely take a picture with the ISO below 400. Do pictures get a little noisy (grainy) when the ISO is high? Sometimes. How often will get you get complaints from viewers when you’ve captured a great moment and there’s a little noise? Never.

Wednesday, March 25, 2009

Google Search – Become part of the leading edge crowd

Google has been releasing some major enhancements to its Search Technology. For the lucky few, when they perform a search, they will see the “Search Options” link in the blue bar.


The “Show Options” takes you to a whole new world of looking at your search results.


The ones I like the most:

Wonder Wheel and Timeline.

Timeline: This is very cool, as it shows you the number of pages that were found over time for a particular search term. This will make it easy to find pages that were created at a certain time. In addition, it will make it real easy to find the latest information on a topic.


Wonder wheel: Uses a wheel and spoke design to help you find related topics. Check out this search on team foundation server:


When I clicked on Team Foundation Server 2008… I get these topics:


A great way to find your way around the tons of data sitting out in cyber-space.

Bummed that you don’t have the “Search Options” link…. fret not. The following link, runs some java-script that will create a cookie on your machine, fooling Google Search into thinking that you are part of the group allowed to check out this new cool feature. (if it does not work, copy the code that follows the link into the address bar of your browser and hit enter).

Enable Google Advanced Search Options on my computer


Tuesday, March 24, 2009

Setting up a TFS Administrator account, without having to assign the user to the local administrators group.

When I first got TFS running, I found that for every user that needed the ability to create a project in TFS, I had to add them to the local administrators account. Giving every user that needed to be able to create a project in TFS the privileges of a local admin just gave me the hibbie jibbies.

After some investigation, I found out the exact set of permissions that one needs to give a user in TFS so that they have the permissions to create a project, without having to make them an admin on that machine.

The complication arises from the fact that TFS is not a single application, but a stack of products that includes the TFS server, a SharePoint portal and a Sql Reporting Services server. A user who is to have the ability of creating projects in TFS needs to be assigned to the TFS Administrators group (theoretically they need only the “Create New Projects” permission, which is provided to the TFS Admin group). Unfortunately, adding the user to the TFS Admin group, does not setup the permissions required for SharePoint and Reporting Services. Which is why you end up getting a whole set of error messages such as:

“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.”

Because Microsoft has not provided a single tool that will setup the permissions across the other two apps, you need to do it manually. And here are steps to do just that:

  1. Add the user to the TFS Administrators Group

Select the TFS server node in TFS Explorer.

Right click and browse to –> Team Foundation Server Settings –> Group Membership


Double click on the Team Foundation Administrators group, to open its properties dialog.


Select “Windows User or Group” and click on “Add…”.


Enter the user id and click ok.


Click on Ok and close to close out of all the dialogs.

  1. Setup SharePoint security settings

A). Determine location of SharePoint Admin Pages

The following actions need you to know the URLs to some SharePoint administration pages. These can be obtained by by running the “TfsAdminUtil.exe ConfigureConnections view” command on the TFS server. (TfsAdminUtil.exe can be found in the folder: C:\Program Files\Microsoft Visual Studio 2008 Team Foundation Server\Tools)

The 2 links that are important for this part are: SharepointUri SharepointAdminUri (This is the same as the SharePoint Central Administration page, which can be opened by going to Administrative Tools à SharePoint 3.0 Central Administration)


B). Add the user to the SharePoint Farm Administrators Group.

Open up SharePoint Central Administration (—> Administrative Tools –> SharePoint 3.0 Central Administration)


(Alternatively, you can browse to the SharepointAdminUri 2(A))

Go to the Operations tab:


Click on “Update Farm Administrator’s Group”


Click on “New –> Add Users”


Enter the user id and then click on the “Check Names” icon to make sure SharePoint was able to find the correct user. Next, make sure that “Farm Administrators” group is selected and click Ok.


C). Add the user to the “Site Collection Administrators” list.

Visit the SharePoint Team Site page. (This is the SharePointUri link)

Click “Site Actions –> Site Settings”


Click on “Site collection administrators”


Enter the user id, click on “check names” to confirm that you have entered the correct user id and then click ok.


You will be returned to the “Site Settings” page.

D). Give the user “Full Control” to the entire SharePoint portal.

On the “Site Settings page”, Click on “Advanced Permissions”.


Click on “New –> Add Users”


Enter the user-id and click on “check names” to confirm that you entered the correct user-id. Select “Full control” under the “Give users permissions directly” heading.


Click Ok.

You might need to restart SharePoint Products and Technologies or Internet Information Services (IIS) before these changes will take effect.

  1. Setup Reporting Services server settings

Browse to the Reporting Services administration page: http://TFSServerName/Reports/Pages/Folder.aspx

A). Add the user as a Content Manager to the Reporting Services Server.

Go to the “Properties” tab and then click on “New Role Assignment”.


Enter the user-id, select “Content Manager” and then click Ok.


B). Give the user site-wide “System Administrator” privileges to the Reporting Services portal.

Click on “Site Settings”.


Click on “Configure Site Wide Security”


Click on “New role assignment”.



Enter the user-id, select “system-administrator” and then click Ok.


The user for whom you did the above steps should “theoretically” be able to create new projects in TFS now.

Best of luck!

Note (added 04.06.2009): To give a AD group or user full control over all newly created TFS SharePoint portals you need to do the following: To fix this, access SharePoint Central Administration. You will need to do this with your TFSSetup account (or equivalent) since it will be the only account out of the box that has Central Admin privileges. Go to: ‘Application Management –> Policy for Web Application’ and click ‘Add Users’. On the first screen, ensure Zones is set to ‘All Zones’ and click Next. Then under ‘Choose Users’ add your user account to the box and click the little ‘tick’ icon below to ‘check names’. Check the box for ‘Full Control’ and click ‘Finish’. This gives your user full access over the whole application, which means all future sub sites that may get created.

References: How to: Set Team Foundation Server Administrator Permissions

More information regarding permissions for all sub-portals!B2EFDBF0964586B3!363.entry

Monday, March 23, 2009

Words – sciolism

A new addition to this blog…. I plan on posting cool words that I come across.

sciolism [sahy-uh-liz-uh m]
pretentious superficiality of knowledge

Some blogs are an exercise in sciolism ;)

OpenID – Implementing OpenID in an ASP.Net website the easy way

I have been looking into OpenID as a possible identity management system for a website that I might have to build. As a first step, I looked into using the RPXNow webservice to cheat my way into creating a very basic implementation. Why do I say “cheat”, because RPXNow has a very simple interface that allows you to quickly implement OpenID authentication for your website, but on the flip side, it adds another layer between your website and the OpenId provider service (as shown in the following diagram).


In addition, the UI experience is nice, allowing the user to choose the OpenID provider that they wish to authenticate their identity.

Here is how simple it is to implement RPXNow into your ASP.Net website.

The first thing you need to do is go to RPXNow and create an account. In addition you will need to register your domain. (Once you have registered an account, you can test it from your local dev machine).

After you register for an account, you will be able to access a dash-board for your website, where you will be able to generate code, like the one below, to begin using RPX now in your website (This is available under the Quick Start link for your website).

There are two parts to the JavaScript code that you need to add to your ASP.Net webpage. The first part, shown below goes into the head portion of your website. The parts that need customization are:

RPXNOW.token_url: this is the web-page to which will be opened once the user’s OpenID identify has been authenticated.

<script src="" type="text/javascript"></script>
<script type="text/javascript">
        RPXNOW.token_url = http://localhost:51766/OpenIDTest/default.aspx;
        RPXNOW.realm = "aggregatedintelligence";
        RPXNOW.overlay = true;
        RPXNOW.language_preference = 'en';
        RPXNOW.default_provider = "google";

The next piece of code that you need to include is the following:

<a class="rpxnow" onclick="return false;" href="'http://localhost:51766/OpenIDTest/Default.aspx'">
    Sign In </a>

This will add a “Sign In” link to your page. The important piece to note here is the href attribute: It points to your rpxnow signin method page and has a query param called token_url, which is set to the same value as the “RPXNOW.token_url” variable in the script that we included in the header.

Thats all you need to do for the front end ASPX page. The above code will add a “Sign In” link, which when clicked, will pop up a frame that will provide the user a choice of OpenID providers that they can use.

image image

The next step is to add the code that will process the web-page, after OpenID authentication has been successful for the user. The following code can be added to the Page_Load event method. The code checks whether a token was provided as part of the request. (A token in the query params means that authentication was successful). Once the token is extracted, it is sent as a parameter to “” (in addition, we tell RPXNow how we want them to send back the user information – JSON/XML).

if (!string.IsNullOrEmpty(Request.QueryString["token"]))
    //read the token returned by RPXNow, when a authentication occurred successfully.
    //token is returned only when a authentictaion occured successfully.
    string token = Request.QueryString["token"];//token={a big identifier string};
    //Create a web-request that will be used to post the token and the special API key provided
    //to you by RPXNow, to get more information about the authentication.
    WebRequest rpxRequest = WebRequest.Create("");
    rpxRequest.Credentials = CredentialCache.DefaultCredentials;
    rpxRequest.Method = "POST";

    Stream requestStream = rpxRequest.GetRequestStream();
    StreamWriter streamWriter = new StreamWriter(requestStream);
    streamWriter.Write("apiKey=" + RPNOW_API_KEY);//RPNOW_API_KEY - provided by rpx 
    streamWriter.Write("&format=xml");//request response in XML, if omitted - response is in JSON
    streamWriter.Write("&token=" + token); //token - retrieved from the request

    //get the response for the request sent to
    WebResponse rpxResponse = rpxRequest.GetResponse();
    Stream responseStream = rpxResponse.GetResponseStream();
    StreamReader streamReader = new StreamReader(responseStream);
    string responseText = streamReader.ReadToEnd();

    XmlDocument xdoc = new XmlDocument();
    XmlElement resp = xdoc.DocumentElement;
    //check if status was ok
    if (!resp.GetAttribute("stat").Equals("ok"))
        Response.Redirect("~/default.aspx?loginStatus=failed");//query param to signal a failure

    //the identifier returned by RPXNow is what should be stored by you, to keep track of the user
    //remember, when you store the identifier, you need to have a mechanisim where one user
    //can be associated with multiple OpenIDs 
    string identifier = xdoc["rsp"]["profile"]["identifier"].InnerText;
    if (!string.IsNullOrEmpty(identifier))
        Session["name"] = xdoc["rsp"]["profile"]["preferredUsername"].InnerText;
}//if (!string.IsNullOrEmpty(Request.QueryString["token"]))

With that you have all the code you need to begin using RPXNow in your website.

Having implemented RPXNow, here are some thoughts:


1. Its very easy to use

2. The UI experience is compelling and you get it for free

3. Very easy to add to ASP.Net website. (No additional DLLs or resources needed on your web-site).


1. RPXNow is another layer that you have added between your website and the OpenID providers. This can be bad if RPXNow goes bust.

2. The extra hop adds a delay.

3. The extra hop can be a security issue (what happens if RPXNow gets hacked). In addition, we have no idea what kind of information they store about the user authenticating themselves via RPXNow.

4. Another big issue – the user is told that they are providing access to a {website_name} via the OpenID authentication. This doesn’t look very nice.

Sunday, March 22, 2009

Print version of Concango’s “Scrum for Team System” Process Guidance

I wanted a printable version of the "Scrum for Team System" process guidance, so that I could read it offline. Unfortunately, I could not find a doc/pdf version anywhere online. So here is one that I created from the content on their website, that you can download and print.
Remember - the material is from Concago's website -, and the copyright for the content is owned by them.
The documentation for this version was downloaded on 03.22.2009 and represents version 2 of "Scrum for TFS".

Saturday, March 21, 2009

DotNetNuke 5.0 - getting it to work on a Windows Vista machine

In a previous post, I had written about installing DNN 5.x on a Windows XP machine.

Building and running DNN 5 on a Windows Vista machine is a little different, because of how IIS is different on Vista.

Here is what you need to do to install IIS on Vista with the correct set of options so that DNN works on it. (note: I am working with the Home Premium version of Vista).

Install IIS for Vista:

Go to Start->Control Panel->Programs and features. Click on the link “Turn Windows features on or off”.


Make sure that IIS is installed with the following set of features.

The following are important:

  • IIS 6 Management Compatibility
  • Application Development Features (Asp.Net)
  • Common Http Features
  • Security


Also, make sure that the folder containing the DNN files provides the Network Service account with modify permissions (root folder as well as sub-folders). Right click on the folder and go to “properties”. Select the “Security” tab and add Network Service account and click on the “Modify” option.


Hit Ctrl + F5 to start the web-site. And if you did everything correctly, the installation page should come up with all the images showing and the format setup correctly (if not, then some setting is not setup correctly).

Thursday, March 19, 2009

Mass transit systems of North America

Radical Cartography has an interesting graphic that compares public transit networks in some of North America’s large cities. It puts into perspective how complex some of the transit systems are when compared to other cities (eg: New York’s system compared to Denver’s relatively newer network).

 image  imageimage
 image image