Welcome to MSDN Blogs Sign in | Join | Help

You know the MSBUILD drill. Everything is a custom task. Need to add two numbers? Write a custom task.
Need to replace spaces in a string with '_' character? Write a custom task. I don't like the overhead of
writing custom tasks.

What you have with MSBuild is bascially an XML file and good amount of "build logic". Anything that you
could have done with even a simple batch script has to be in a custom task.  I don't like to have
some code in the MSBUILD script and some other code in a custom task tucked in somewhere else.
I want to be able to embed a simple script in the MSBuild file. I searched for it and I could not find a way.

I have to to roll my own way? Well thats what I did.
I found that I could actually embedding IronRuby in MSBuild file.

<Project DefaultTargets="ExecuteTest" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<
UsingTask TaskName="MSBuildTasks.RubyHost" AssemblyFile="MSBuild_Ruby.dll"/>
<
PropertyGroup>

   <RubyScript><![CDATA[

              File.open("MyTestFile.txt", "w") do |file|
                5.times do |i|
                  file.write "This is line #{i}\n"
                end
             
end
             "success"

]]></RubyScript>

</PropertyGroup>

<Target Name="ExecuteTest">
    <
RubyHost ScriptSource="$(RubyScript)">
          <
Output TaskParameter="ReturnValue" PropertyName="ReturnValue" />
    </
RubyHost>
    <
Message Text="$(ReturnValue)" />
</
Target>
</
Project>

With this, you can see that the ruby code is embedded within the MSBUILD file itself, and no need to maintain
separate set of tasks. NO MORE a task for adding, deleting, and multiplying numbers!!.

Of course you need 1 custom task that hosts the ruby engine and runs the ruby code for you.  

Here is the source code for the custom task

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Scripting.Hosting;

using IronRuby.Runtime;

using IronRuby;

using Microsoft.Build.Framework;

namespace MSBuildTasks

{

public class RubyHost : ITask

{

private IBuildEngine engine;

public IBuildEngine BuildEngine

{

get { return engine; }

set { engine = value; }

}

private ITaskHost host;

public ITaskHost HostObject

{

get { return host; }

set { host = value; }

}

private string scriptSource;

[Required]

public string ScriptSource

{

get { return scriptSource; }

set { scriptSource = value; }

}

 

private string returnValue;

[Output]

public string ReturnValue

{

get { return returnValue; }

}

 

private void LogMessage(string message, MessageImportance imp)

{

BuildMessageEventArgs args = new BuildMessageEventArgs(

message, string.Empty, "RubyIntegration.RunRake", MessageImportance.Normal);

engine.LogMessageEvent(args);

}

public bool Execute()

{

try

{

var runtime = IronRuby.Ruby.CreateRuntime();

ScriptEngine Ruby_Engine = runtime.GetEngine("Ruby");

ScriptSource src = Ruby_Engine.CreateScriptSourceFromString(scriptSource, Microsoft.Scripting.SourceCodeKind.Statements);

Object result = src.Execute();

returnValue = result.ToString();

}

catch (Exception ex)

{

LogMessage(ex.ToString(), MessageImportance.High);

}

return true;

}

}

}

I know that the code is quick and dirty and can be improved on this.
I hope to enhance it soon. But I can't contain my joy and wanted to
spread the love....

Let me know if you liked this idea and share any further thoughts

In my last post I mentioned getting into Ruby and making MABUILD and rake work together.
I think I found a better way to do the same with PowerShell integration, which I will
write up sometime this week.

Now, it is inevitable that Ruby leads to Ruby On Rails. I needed to get RoR work on my dev machine with VISTA SP1 and IIS7.
I was little surprised to find that there are very few articles on this. A few I found were outdated. Then I landed on Ruslan's post.
It got me to a point but then I was stuck. So I walked to Ruslan's office and we discussed this quite a bit. I went back to my office and researched this some more.
I finally got this working after about 3 days of stumbling around. So here is the post that builds on Ruslan's
post and clarifies a few things. Mostly I wanted to make sure that even folks like me, who are beginners in the Ruby universe,
could follow the steps to get RoR and IIS 7 working.

I am starting here with a clean system with VISTA SP1.

Step 1:  INSTAL IIS AND CGI

Go to control panel, Programs and Features and then select "Turn features on or off". You can get there quicker
by typing appwiz.cpl on a command prompt and then selecting "Turn features on or off " from the left side
image

From the features select World Wide Web Services
and make sure that you select the following

  1. REQUIRED: CGI. This is the same as the FastCGI support
  2. REQUIRED: IIS Management Console
  3. OPTIONAL: Custom HTTP Features: Static Content, etc
    [We will use this to check if IIS is installed correctly]
  4. OPTIONAL : Other health and diagnostics info as
    Shows in the picture below

image

Click OK and let the Windows install IIS and FastCGI for you.

Step 2:  VERIFY THAT IIS IS WORKING

Once installed you can verify that the IIS is installed by going to the
http://localhost/iisstart.htm
image

Please note: If you did not enable STATIC CONTENT feature when you installed, you will get a 404 NOT FOUND.
You see the file right there but you still get 404 NOT FOUND. Security aside, it would be great
if there is a more easier way to diagnose this, I ended up researching this for more than 3 hours wondering
why am I getting the 404 not found. Anyway, now you know and you don't need to waste those hours.
Again, enable the static content feature to be able to see this static content page.

 

Step 3: INSTALL RUBY

Navigate to Ruby 1.8.6 One-Click Installer and install RUBY
CAUTION: DONT INSTALL TO A FOLDER that has SPACES in the name or its path.  like "C:\program Files\Ruby"
I had strange errors and I had to reinstall RUBY. It is best to stick with the C:\Ruby.

As a side and tangential note, what's up with installing to C:\Ruby? Aren't the people in that
alternate universe hate installing to root directories like C:\? Why can't it default to C:\Program Files\Ruby?
RUBY is supposed to make you HAPPY!! Huh!

 

Step 4: VERIFY RUBY IS IN THE PATH

RUBY Installation should have added the C:\ruby\bin [or whatever the path you gave] to the path.
Open a command prompt; type "SET PATH" and look at the path. You should see the ruby\bin in the path.
If not, no big deal, add ruby to the path in the System Variables.
Type ruby -help to see that you get some help on the screen. This verifies that Ruby is indeed installed.

 

Step 5: INSTALL RAILS

on a command prompt, type "Gem Install Rails". [The dependencies are already
included, contrary to popular belief that suggests the -includedependencies option
is required to do so]
Once installed, do "gem list" to list the gems locally installed. You should see
something like this - which should include rails.

C:\Windows\system32>gem list

*** LOCAL GEMS ***

actionmailer (2.3.2)
actionpack (2.3.2)
activerecord (2.3.2)
activeresource (2.3.2)
activesupport (2.3.2)
fxri (0.3.6)
fxruby (1.6.16)
hpricot (0.6.164)
log4r (1.0.5)
ptools (1.1.6)
rails (2.3.2)
rake (0.8.7, 0.8.1)
ruby-opengl (0.60.0)
test-unit (2.0.1)
win32-api (1.2.1, 1.2.0)
win32-clipboard (0.4.4)
win32-dir (0.3.2)
win32-eventlog (0.5.0)
win32-file (0.5.5)
win32-file-stat (1.3.1)
win32-process (0.5.9)
win32-sapi (0.1.4)
win32-sound (0.4.1)
windows-api (0.2.4)
windows-pr (0.9.3)

Step 6: Install FASTCGI Update for IIS7
http://www.microsoft.com/downloads/details.aspx?FamilyID=19600729-8470-4956-a276-200450d814bd&displaylang=en&Hash=CUX9vrzjILBL3AghMTxtsgswXdorhJT4JhGOGHeRvFL9AQlhlkUkNtnVcQflyQD7VwtQyh51gAxTnsa4f4OlBA%3d%3d

I am not quite sure if this is a general update for FastCGI or just for PHP. To avoid hitting my head against a wall, I installed this anyway.


Step 7: Pretend that you are hosting a website called  RoRIIS7

Add a HOST Entry so that the name resolver won't go out to the DNS Server.
Don't forget to use an administrative command prompt from which you
launch notepad. edit Windows\System32\Driver\Etc\Hosts like below.
image

Step 8  Add a new Website to IIS

Go to C:\InetPub [or where ever you installed the inetpub to]

create a directory called RoRIIS7. We are not touching the wwwroot directory for this experiment.
i will post an update dealing with default web site later.

Open MMC, Add the Internet Services Manager to the MMC Console.
Expand the IIS Manager. It will show the local web server.
Click on Add WebSite to add a website like below
image

Here we are adding a new WebSite RoRIIS7
mapped to the physical directory C:\Inetpub\RoRIIS7
Please note that the HostName we are using RoRIIS7
AND that I am using port 8080
image

The website should start.

Step 9 Verify that the new website is working
To verify that the website is working, copy the IISSTART.HTM and the
welcome.png files into the RoRIIS7 directory.
Then navigate to http://RoRIIS7/iisstart.htm
You should see a page similar to step 2.
 
Step 10 Generate a RAILS APP
NOTE: When you generate the Rails app, make sure that
you use -D option. -D option generated the dispatchers
for CGI and FastCGI
Luanch CMD prompt as administrator
cd C:\inetpub\RoRIIS7
rails -D MyApp
cd MyApp
ruby script\generate Controller Test Index

 

Step 11 Generate a RAILS APP

Modify the app\controllers\test_controller.rb like below
This will enable it to display some test when we navigate to this URL

class TestController < ApplicationController
   def index
      render :text=>"The index action"
   end
   def about
      render :text=>"The about action"
   end
end

Step 12 Hook up IIS to Ruby

So far, we have IIS Setup and a Ruby App Setup
We need to hook them together now.

Go to the MMC/Internet Services Manager, Go to the site RoRIIS7 in the
MMC snapin. We are now going to add a module mapping

image

We are mapping * ==> All requests To the FastCGI Module.
The executable we use is of course the ruby interpreter.
But then there you see a PIPE character and the path to the
dispatch.fcgi. The reason is that Ruby by iteself does not handle
the FCGI. The FCGI handling is provided by the Rails
Dispatcher.

Once you add the mapping go to
C:\windows\system32\inetsrv\config and open
applicationhost.config
In this config file you should see a section for fast cgi

<fastCgi>
    <application fullPath="C:\ruby\bin\ruby.exe" arguments="C:\inetpub\RoRIIS7\MyApp\public\dispatch.fcgi" />
</fastCgi>

and another section for
the module mapping.

</system.webServer>
<location path="RoRIIS7">
    <system.webServer>
        <handlers>
            <add name="RubyFastCGI" path="*" verb="*" modules="FastCgiModule" scriptProcessor="C:\ruby\bin\ruby.exe|C:\inetpub\RoRIIS7\MyApp\public\dispatch.fcgi" resourceType="Unspecified" />
        </handlers>
    </system.webServer>


These sections are created for you when you added the module mapping
through the GUI above. Now, you could directly edit the file yourself if you wanted to.

Sensing the smell of completion you go to http://roriis7/test/about
but then you are greeted by the
image

Step 13 Fixing Permissions

When working with IIS, I always turn on the security audit.
Turning the security audit on can it self be somewhat of a challenge.
Without going into detail, what is required apparently is to
grant NETWORK Service Account permissions to the RoRIIS7 Folder.
Since Ruby will write some logfiles etc to that dir. You could
possibly fine tune this later if you needed to.
image

Do an IISRESET and go visit http://roriis7:8080/test/about
OOPS! What went wrong now?
image
Apparently the RAILS needs SQLITE and it is not happy that it can't find SQLLite

Step 14 Installing SQLITE and the SQLLITE GEM

Download SQLITE-3.6.15 ZIP file and place the exe in the Ruby\Bin directory
this is the command line config  tool

Download SQLITEDLL-3.6.15 ZIP and place the dll in the system32 directory.
This is the engine for SQLITE.

Now we need to install the GEM

http://blog.anlek.com/2008/09/installing-updating-sqlite3-on-windows/ has the
tip on getting this done

gem install --version 1.2.3 sqlite3-ruby

Step 14 COMPLETE!!!

Now you navigate to http://roriis7:8080/test/about

and you should see

image

There you go ladies and gentlemen, I give you Ruby On Rails with IIS7

These days, I am spending quite a bit of time with Ruby,  Ruby On Rails and RAKE. I am not into the Ruby religion yet,
and not drinking the ruby Kool-aid that much. Just a little.

I needed to integrate the TeamBuild (Team System) with Ruby/RAKE.  So in other words, I needed to
call RAKE from MSBUILD. It turned out to be slightly harder than I expected. I am new to MSBUILD, Ruby and RAKE,
so I had to figure out quite a few of things.

I ended up writing a custom task. From the custom task, I called Rake and captured the output.
Definitely not rocket science, but getting everything working properly is not easy either.

So here it is.

First the custom task.

Here is the entire code file

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Framework;
using System.Diagnostics;
using System.IO;
namespace RubyIntegration
{
    public class RunRake : ITask
    {
        private IBuildEngine engine;              
        public IBuildEngine BuildEngine
        {
            get { return engine; }
            set { engine = value; }
        }       

        private ITaskHost host;
        public ITaskHost HostObject
        {
            get { return host; }
            set { host = value; }
        }

        private string RakeFileDir;
        [Required]
        public string RakeFileDirectory
        {
            get { return RakeFileDir; }
            set { RakeFileDir = value; }
        }

        private string RubyInstallDir;
        [Required]
        public string RubyInstallDirectory
        {
            get { return RubyInstallDir; }
            set { RubyInstallDir = value; }
        }
        private string RakeTargetName;
        [Required]
        public string RakeTarget
        {
            get { return RakeTargetName; }
            set { RakeTargetName = value; }
        }

        private string strOutput;
        [Output]
        public string RakeOutput
        {
            get { return strOutput; }
            set { strOutput = value; }
        }

        private void LogMessage(string message, MessageImportance imp)
        {
            BuildMessageEventArgs args = new BuildMessageEventArgs(
                message, string.Empty, "RubyIntegration.RunRake", MessageImportance.Normal);
            engine.LogMessageEvent(args);
        }

        public bool Execute()
        {
            bool result = false;
            try
            {
                LogMessage("***** MSBUILD  <--> RAKE ********", MessageImportance.Normal);
                LogMessage("Preparing to invoke Rake", MessageImportance.Normal);
                LogMessage("*** INPUTS", MessageImportance.Normal);
                LogMessage("Ruby Install Directory " + RubyInstallDir, MessageImportance.Normal);
                LogMessage("Rake File Directory " + RakeFileDirectory, MessageImportance.Normal);
                LogMessage("RakeTargetName " + RakeTargetName, MessageImportance.Normal);
                LogMessage("***", MessageImportance.Normal);

                LogMessage("*** CHEKING PARAMETERS", MessageImportance.Normal);
                if (Directory.Exists(RubyInstallDir)){
                    LogMessage("Ruby Install Directory " + RubyInstallDir + " exists.", MessageImportance.Normal);
                }
                else{
                    LogMessage("Ruby Install Directory " + RubyInstallDir + " does not exist - exiting task.", MessageImportance.High);
                }
                string RubyPath = Path.Combine(RubyInstallDir, "ruby.exe");
                if (File.Exists(RubyPath))
                {
                    LogMessage("Ruby.exe is found at " + RubyPath, MessageImportance.Normal);
                }
                else
                {
                    LogMessage("Ruby.exe is not found at path" + RubyPath + " . - exiting task.", MessageImportance.High);
                }
                string RakePath = Path.Combine(RubyInstallDir, "rake");
                if (File.Exists(RakePath))
                {
                    LogMessage("rake file (rake ruby code) is found at " + RakePath, MessageImportance.Normal);
                }
                else
                {
                    LogMessage("rake file (rake ruby code) is is not found at path" + RakePath + ". - exiting task.", MessageImportance.High);
                }

                LogMessage("***", MessageImportance.Normal);
                ProcessStartInfo info = new ProcessStartInfo();
                info.RedirectStandardOutput = true;
                info.UseShellExecute = false;
                info.FileName = RubyPath;
                info.Arguments = RakePath + " "  + RakeTargetName;
                info.WorkingDirectory = RakeFileDirectory;
                Process p = Process.Start(info);
                strOutput = p.StandardOutput.ReadToEnd();
                p.WaitForExit();
                result = true;
            }
            catch (Exception ex)
            {
                LogMessage(ex.ToString(), MessageImportance.High);
            }
            return result;
        }
    }
}

 

Here is the MSBUILD FILE

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask AssemblyFile="RunRake.dll" TaskName="RubyIntegration.RunRake" />
  <Target Name="default" >
    <RunRake    RubyInstallDirectory ="C:\ruby\bin"
                RakeFileDirectory="$(MSbuildProjectDirectory)\ruby\edgeserver"
                RakeTarget="alcoholic:getSmashed">
      <Output TaskParameter="RakeOutput" PropertyName="RakeOutput"/>
    </RunRake>
    <Message Text="The output from Rake is... $(RakeOutput)" />
  </Target>
</Project>

NOTES

Fist I attempted to creating a RAKE process by trying to call rake.  (pass rake as the file name to the process start info).
The process failed to start saying that the file not found.
Then I tried with rake.bat
(pass rake.bat as the file name to the process start info).
The problem seems to be that RAKE.BAT does some
manipulation and calls ruby.exe and passes the file "rake" to ruby.exe.  Normally it would end up something like
[ruby.exe c:\ruby\bin\rake ..].
However The path to rake is getting resolved incorrectly when I pass the rake.bat as the
program name.  It is using the working directory and ending up with

[ruby.exe <MY WORKING DIR>\rake...]
Obviously there is no file called "rake"  in my working directory.
I need to resolve this, but for now this should get me going.

 

The installation for for Ruby could be possibly obtained by resolving
the file extension .rb to an executable. That is another task that can be added
to eliminate the need to send the ruby install directory as a parameter.

A while back in the C++ days, there were class libraries doing things like
sending mail, opening/reading cert store, event log writing reading, etc. These were written in C++ to encapsulate some of the pain associated with
routine tasks. Great Idea!!

MFC's CReg CSocket and a bumch of classes started with C made things easier. If you need to do stuff
you would use one of those reusable libraries or you would write one of those reusable classes.

Then came COM. Once again there are COM components that did read/write registry, read/write to event log.
you can see the ATL's incarnation of Registry classes as an example.

Then came ASP/VbScript. You need to read write the file system and registry from ASP files. Enter the Scripting.FileSystemObject
and its cousins. Due the popularity of ASP and web development, great amount of functionality needed to be cast through
IDispatch interfaces to make it available for VBScript programmers.


Human beings want to make progress. So .NET arrived. So there are new way to read  / write registry
and read write event log and read write files. OK

What about powershell? The world then needs to be packaged in Cmdlets to be consumed from the
powershell. great.


Well how about MSBuild? How can I read write registry from MSBUILD? Of course you need to
have the MSBuild tasks. Custom or not custom, you need to view the world through the MSBUILD Tasks.
There are community libraries that help you read registry and do great things like send mail, etc.

I wonder. How many ways do we need to read/write the registry?

I have been pondering over the Agile, Iterative, XTreme, waterfall and several other software methodologies. I think that each of them have their strengths, I have no reason to bash one methodology or pump some other methodology.
Personally, I really don't care about the methodology. I don't think it matters. Let me explain.

Since I started working about 20 years back, I always wanted to be organized and accomplish more. In one training class, 20 years back, the instructor said
"I know of no better idea than to just write down your tasks and prioritize". I heard it, I nodded. Since then, I have been trying to follow that simple principle.
I start using Outlook tasks and not really update them or use them as guidance. Before outlook, I used to purchase day planners from a reputed company
and I eventually left 95% of the days unfilled or unused. Then I bought a palm pilot. Then upgraded the memory. Then decided to switch to the paper based planners.
You get the idea. I am still looking for  a methodology.

The point is that the methodology, or the means, is not the problem. "I" am the problem. If "I" decide to be more disciplined, a mere post it note would have
served the purpose. Well may be a $2 note book. The problem is that I imagine the most complex of situations, and imagine my self following the
discipline to the core and create all sorts of scenarios where I need to have more elaborate mechanisms to keep me disciplined. Some where along the line,
my focus goes form "I" to the "methodology" and one more year passes before I get really disciplined.

There is NOTHING wrong with the waterfall methodology. I am sure that if we send a bi-partisan committee they will find projects where waterfall is just worked fine.
There is NOTHING wrong with agile either. There are projects where it succeeds and there are projects where it fails.

But I BET that in both cases, the projects that succeeded would have succeeded with either methodology, and that they would have succeeded not because of
the methodology, but because of the fundamental discipline of how they conducted the business.

When it comes to software "engineering", there are some fundamental principles. Like Mr.Covey says in his "Principle Centered Leadership", there are some
fundamental principles when it comes to software engineering

  1. Have your requirements
  2. Have an idea of what you are building
  3. Develop the fundamental architecture of the components
  4. Have a release plan
  5. Write very detailed specs and design documents (sorry agile folks!). Spend time in making sure
    that the design is solid. The code should be a mechanical reproduction of the design (as much as possible.not taking a religion here)
  6. Don't pretend Quality is #1, behave so. Role model Quality as fundametal principle
  7. Take time to test - functional, API, Perf, Stress
  8. Fix bugs. Don't let service packs be the bug fix vehicles.

Fundamental, natural principles don't change. It is our never ending quest to invent schemes that lead us to believe that we can defy them - that blinds
us.

Let me tell you about losing weight. Atkins, South Beach, Nutri System? Or Eat Sensible and Exercise?
Let me go buy the South Beach Diet book. The girls on the cover page look beautiful.

Comments?

 

In my previous blog article, I pointed out that in a workgroup environment, Windows XP has the force guest policy on and it prevents agents and clients connecting to the controller.

In this post, I want to expand on this and talk a bit more about VSTT controller and agent setup. Particularly, I will talk about the user account requirements on the machines running controller, agent and the client.

Ed Glas already talked about the setup in general at http://blogs.msdn.com/edglas/pages/load-agent-and-load-controller-installation-and-configuration-guide.aspx, where he has a list of user account and password requirements on the machines running controller and agent. In this post, I want to drill down further into the reasons behind those user account requirements. I will do this by walking you through the installation process of a controller, agent and a client and troubleshooting the authentication issues.

To start with, I have 3 machines. VSTTClient, VSTTController and VSTTAgent1. I have Windows XP SP2 on all the 3 machines. They are all in a workgroup TESTRIG. I have intentionally made the administrator password different on all the 3 machines. The idea is not accidentally authenticate to another box as an Admin. Other than this, all the machines are clean and no other setup has been done.

Before we proceed to the installation, I want to go over a few things.

1. Workgroup authentication
In a Windows domain environment, there is a central authority to validate credentials. In a workgroup environment, there is no such central authority. Still, we should be able to have computers in a workgroup talk to each other and authenticate users. To enable this, local accounts have a special characteristic that allows the local security authority on the computer to authenticate a "principal" in a special way.
If you have two computers and a principal "UserXYZ" on both machines the security identifiers are different for MACHINE1\UserXYZ and MACHINE2\UserXYZ and for all practical purposes they are two completely different "Principals". However if the passwords are the same for them on each of these computers, the local security authority treats them as the
same principal.

So when MACHINE1\UserXYZ tries to authenticate to MACHINE2\UserXYZ, and if the passwords are the same, then on MACHINE2, the UserXYZ is authenticated successfully and
is treated as MACHINE2\UserXYZ. Note the last sentence. The user MACHINE1\UserXYZ
is authenticated as MACHINE2\UserXYZ if the passwords are the same.

2. Force Guest
As I mentioned in the previous article, in a default Windows XP workgroup environment, the force guest policy is on by default. You can quickly check this out by mapping a share from a one machine to the other. Here, I am trying to map a share from the VSTTController to VSTTAgent1 and I am forced to login as a Guest, and the User Name box is grayed out.

forcegueston

If the force guest policy is on, it does not matter if you have the same users and passwords, you can not authenticate to the machine as any other principal than the Guest. You can turn it off using Regedt32, HKLM\System\CurrentControlSet\Control\Lsa and editing the forceguest value to 0.

3. Security Audit
If you are having authentication issues and don't know what is happening, the security audit policy is your good friend. Normally security audit is turned off and hence you will not see the events in the event viewer.
To turn the security auditing, open mmc, add group policy object editor, select local computer. Then Select Local Computer Policy\Computer Configuration\Windows Settings\Security settings\Local Policies\AuditPolicy and turn on Success and Failure auditing for the events of interest. For troubleshooting purposes, I turn on the audit for everything here. Granted this generates a lot of auditing but at least I know what is going on and once I am done troubleshooting, I can turn the auditing off. 
Once done, on a command prompt, run gpupdate /force to apply the policy.

 

Installing the Controller and Agent
Now that we have the basics covered, I started the installation process with the VSTTController box on which I intend to install the Controller. The first thing is to turn off the force guest policy since I already know that the agent and the client needs to connect and authenticate to the controller. Then I added a user CSUser. This is the user the controller service will run as. During the installation process, you need to pick a user under which the controller service runs under and I picked the CSUser.

After the installation of the controller, I have, on the VSTTController machine, 

  • A user account CSUser
  • An empty TeamTestAgentService group
  • An empty TeamTestControllerAdmins group
  • An empty TeamTestControllerUsers group.

At this point the controller service seems to be running, so I am happy so far.


The next step then is to go to the VSTTAgent1 machine and install the Agent. Note that on this machine I did not yet turn the ForceGuest policy off on VSTTAgent1. The reason, once again, is that I wanted to understand the minimal stuff that I need to do get this rig working. 
Before I started the agent installation, I added the user AgentUser to the machine. This is the identity under which the agent process will run. I logged in as Administrator on this box and started the agent installation. The installation process asks for the user identity of the agent process and I specified the identity as AgentUser. About 15 seconds in into the process, the agent installation has trouble. Apparently connecting to the controller failed.
   AgentInstallationIssue
To start troubleshooting, I switched to the VSTTController machine, turn on the security auditing on the VSTTController machine as described above and then switched back to the VSTTAgent1 machine and clicked Retry on the dialog. As expected it fails again. Now when I switched to the VSTTController box and looked at the Event Viewer, under Security folder, I see the following event.

AuthFailure

The VSTTController machine is rejecting the logon VSTTAgent1\Administrator. This is for good reason. The Administrator passwords are intentionally not the same!

It is easy to see why the agent installation connects to the controller box under the identity of the person who is installing the Agent. The user that is installing the agent is supposed to be part of the Administrators that administer the controller and agents. The act of installing an Agent and connecting to a controller makes the agent to be registered with the controller.
This needs special permissions.

One thing we can do now is to adjust the administrator passwords to be the same. I don't particularly like the idea, since I don't want Administrators of one box to automatically connect to other boxes.

So what I decided to do is to add a user AgentAdmin on the VSTTAgent1 machine and add it to the Administrators group. The idea is that I will restart the Agent installation as AgentAdmin, not as Administrator. Since AgentAdmin is part of the Administrators group on VSTTAgent1 machine, the installation should work fine. On the VSTTController machine,
I need to add the same user AgentAdmin with the same password. However VSTTController\AgentAdmin will not be part of the Administrators group on VSTTController box. The idea is to not unnecessarily grant permissions that are not required.

So at this point, here is what I have on each box

VSTTController VSTTAgent1
ForceGuest Off ForceGuest ON
AgentAdmin as a normal user AgentAdmin user part of the Administrators group
CSUser as a normal user. The controller service is running under this user AgentUser as a normal user. This is the intended identity under which the agent should be running

An empty TeamTestAgentService group
An empty TeamTestControllerAdmins group
An empty TeamTestControllerUsers group

N/A
Security Audit turned on Security Audit off

With this arrangement, I logged off as an Administrator on the VSTTAgent1 box and then logged in back as a AgentAdmin.

But before I proceed, I will let you in on a cool trick. The .NET tracing's default trace listener writes the trace using the API called OutputDebugString and you can view the output in real time using a tool called DebugView. DebugView is a tool that is freely downloadable from the Technet. Go here to download.What this means is that you don't need to configure file based trace listeners, and keep refreshing the file editor to see the latest contents. Since I wanted to see the controller's trace messages, I switched to the VSTTController box, edited the QtController.exe.Config file (under <rootdrive>:\Program Files\Microsoft Visual Studio 9.0 Team Test Load Agent\LoadTest), and adjusted the EqtTraceLevel to a value of "4" like shown below, saved the file and restarted the controller service for the settings to take effect.

<switches>
  <!-- You must use integral values for "value".
       Use 0 for off, 1 for error, 2 for warn, 3 for info, and 4 for verbose. -->
  <add name="EqtTraceLevel" value="4" />
</switches>

Once I did this, I fired up the DebugView. You should see something like this on the VSTTController box.

DebugView

Now that I have both the security audit and the controller tracing on the VSTTController box, I came back to the VSTTAgent1 box and started the install process. Remember that now I am logged in on the VSTTAgent1 Box as AgentAdmin, which is part of the VSTTAgent1\Administrators group.

The Agent installation fails again. On the VSTTController box, I look at the event viewer. I see a successful logon of the AgentAdmin. The Logon type = 3 which means that this is a network logon. So we made progress. We are able to authenticate to the VSTTContoller machine as VSTTController\AgentAdmin from VSTTAgent1 machine using VSTTAgent1\AgentAdmin principal.

Successlogin

The next thing to look at is the QtController's trace messages from the DebugView tool. The trace messages show that the controller service is checking for group membership of this authenticated principal (user) AgentAdmin.  It is checking to see if the AgentAdmin is a member of TestTestControllerUsers or TeamTestControllerAdmins group. Since I did not add the AgentAdmin as part of any group on the VSTTController box, the membership check fails and controller returns an error which is showing up as an installation failure on VSTTAgent1 machine.

To fix the error it is not clear to me as to what group I need to add the AgentAdmin user to
on the VSTTController box. At first I tried adding the AgentAdmin to the TeamTestControllerUsers group, went back to the VSTTAgent1 box and retried the installation. Even though the installation was successful, the agent was never registered with the controller. I could have tried to fix through other means, but decided to try this through the user scenario. So I added the AgentAdmin as part of the TestTestControllerAdmins group on the VSTTController box. I turned the security auditing on on the VSTTAgent1 machine too, just to aid in debugging.

I uninstalled the agent from the VSTTAgent1 box and  attempted a reinstall of the Agent.
Here is what I have just before the reinstall of the Agent on VSTTAgent1 machine

VSTTController VSTTAgent1
ForceGuest Off ForceGuest ON
AgentAdmin as a normal user
and part of the TeamTestControllerAdmins
AgentAdmin user part of the Administrators group
CSUser as a normal user. The controller service is running under this user AgentUser as a normal user. This is the intended identity under which the agent should be running

Empty TeamTestAgentService group 

TeamTestControllerAdmins group
has  AgentAdmin as a member

Empty TeamTestControllerUsers

N/A
Security Audit turned on Security Audit ON

The installation seems to be successful. On the VSTTController machine I see trace messages showing that the installation went well and the VSTTAGENT1 is added to the AgentManager. So far so good.

Now that the installation of the agent is successful, I really don't see the point of having the AgentAdmin as a user on the VSTTController machine or on the VSTTAgent1 machine. I want to delete this account so that I can reduce any unnecessary user accounts with the same passwords floating around. So I deleted the AgentAdmin account from both the VSTTController and VSTTAgent1 machines ( first I need to logoff as AgentAdmin on the VSTTAgent1 box, log back in as Administrator before deleting the AgentAdmin user). With the AgentAdmin account deleted from both the machines, here is the configuration

VSTTController VSTTAgent1
ForceGuest Off ForceGuest ON
CSUser as a normal user. The controller service is running under this user AgentUser as a normal user. This is the intended identity under which the agent should be running

Empty TeamTestAgentService group
Empty TeamTestControllerAdmins group
Empty TeamTestControllerUsers group

N/A
Security Audit turned on Security Audit ON

The Agent installation suggested a reboot after the installation so I did that.
After the reboot when I logged into the VSTTAgent1 box as an Administrator and opened the event viewer. I am expecting that the Agent service will not be able to connect to the controller. Why? The agent is running as VSTTAgent1\AgentUser and there is no corresponding user on the VSTTController box. So Agent should fail to connect.
Sure enough the event viewer on VSTTAGent1 machine and the event viewer on the VSTTController machine both point to the failed logon of VSTTAgent1\AgentUser on VSTTController machine.
FailedAgentUser

Since we need the agent to connect to controller, we must add the AgentUser to the VSTTController machine with the same password as it has on VSTTAgent1 machine.
Even after adding this, the agent is still not happy since it still can't connect yo the controller service. Looking at the debug view, you can figure out that the AgentUser needs to be part of the TeamTestAgentService group. So I added the AgentUser to the TeamTestAgentService group. Still no success.  
Looking at the Event viewer on VSTTAgent1, I find that the VSTTController\CSUser is trying to authenticate to VSTTAgent1 machine. This because the controller wants to callback to  the agent on a different connection and that connection needs to be authenticated.

image
The auth fails due to the fact that we have force guest on and we don't have CSUser as a local user on VSTTAgent1. Lets fix this now. After fixing, and rebooting, this is what I have for configuration

VSTTController VSTTAgent1
ForceGuest Off ForceGuest Off
CSUser as a normal user. The controller service is running under this user CSUser as a normal user.
The AgentUser as a normal user. AgentUser as a normal user. This is the intended identity under which the agent should be running

TeamTestAgentService group contains the AgentUser
Empty TeamTestControllerAdmins TeamTestControllerUsers group contains AgentUser

N/A
Security Audit turned on Security Audit ON

That does it. The agent and controller are now happy and are communicating to each other.

Installing the Client and getting it to connect to the controller

I created a ClientUser on the VSTTClient machine. I installed VSTT, logged off and logged in back again a VSTTClient\ClientUser. I fired up VS, created a test project, specified VSTTController as the remote controller and started a run. As expected, the VS is trying to
open an authenticated connection to the VSTTControlelr as VSTTClient\ClientUser. Since that user is not defined on the VSTTController machine, the logon fails.

 image
The fix is to add the ClientUser to the VSTTController box and then to the TeamTestControllerUsers group.
Even after doing that you would still find that you are able to execute tests.
Following the same troubleshooting techniques and turning on the security audit on the
VSTTClient, the VSTTController\CSUser is failing to authenticate to the VSTTClient.
So the fix is to turn off the force guest poilicy and create the CSUser on the VSTTClient machine.

Now I can run tests in my multi machine workgroup topology....

Here is the final recipe for the VSTT Client, Controller and agent setup

  1. On all machines in the workgroup, turn off the ForceGuest policy.
  2. Determine the user account under which the controller is going to run under [CSUser]
    Add this account to *all* the machines - client, controller and agent machines [VSTTClient, VSTTController, and VSTTAgent1 in my case]
  3. On the controller machine, install the controller. When you install the controller it will
    create a set of user groups mentioned above.
  4. Determine the account under which the agent is going to run under [AgentUser]. Add the account to the controller machine and to the TeamSystemAgentService Group.
  5. On the agent machine, create a temporary account called AgentAdmin. Add the account to the Administrators group. Add the AgentAdmin account to the controller machine and to the TeamSystemControllerAdmins user group
  6. Login to the Agent machine as the AgentAdmin
    Install the agent, specify AgentUser as the user underwhich the server is going to run under
  7. Once the install is complete, on the Agent machine, logoff as AgentAdmin, log back in as Administator and delete the AgentAdmin account. Delete the AgentAdmin account on the Controller machine. There is no need for this account any more.
  8. Determine the account under which you are going to run the Visual Studio on the
    client machine. [ClientUser on VSTTClient].  Add the account to the controller machine with the same password. On the controller machine, add the client user to the TeamSystemControllerUsers group.

Thats it. I hope this article showed you in detail how to install controller and agent in a workgroup and troubleshoot any authentication issues using the DebugView and Security Audit policy.

In Rosario Team Foundation Server, significant new functionality is added for liking work item types
http://blogs.msdn.com/bharry/archive/2007/08/06/work-item-tracking-enhancements-in-the-aug-rosario-ctp.aspx

http://blogs.msdn.com/teams_wit_tools/archive/tags/Rosario/default.aspx

Here, I will try to describe how to query on link through the work item API.

 

First let’s get a list of work item types in the server.

After the obligatory logging in into the TFS and getting the Work Item Store, you need to go to a
specific project and then get the work item types defined for that project.   Here is a code snippet
that prints all the work item types in a project


TeamFoundationServer TFS;

WorkItemStore WIStore;

 

TFS = TeamFoundationServerFactory.GetServer(http://"<your tfs server>:<port typically = 8080>");

WIStore = TFS.GetService(typeof(WorkItemStore)) as WorkItemStore;

Project TeamProject = WIStore.Projects["MyProject"];

WorkItemTypeCollection workItemTypes = TeamProject.WorkItemTypes;

foreach (WorkItemType workItemType in workItemTypes)

{

    Console.WriteLine(workItemType.Name);

}

 

Now that we know how to get the work item types, we can look at the work item link types

While the work item types are project specific, work item link types are global to all projects.
The code snippet below shows how to get the work item link types.

WorkItemLinkTypeCollection worItemLinkTypes = WIStore.WorkItemLinkTypes;

foreach (WorkItemLinkType workItemLinkType in worItemLinkTypes)

{

    Console.WriteLine("-----------------------------------------");

    Console.WriteLine("Base ReferenceName: {0}", workItemLinkType.BaseReferenceName);

    Console.WriteLine("Id: {0}", workItemLinkType.Id);

    Console.WriteLine("Name: {0}", workItemLinkType.Name);

    Console.WriteLine("IsDirectional: {0}", workItemLinkType.IsDirectional);

    Console.WriteLine("IsForwardLink: {0}", workItemLinkType.IsForwardLink);

    Console.WriteLine("IsNonCircular: {0}", workItemLinkType.IsNonCircular);

    Console.WriteLine("IsOneToMany: {0}", workItemLinkType.IsOneToMany);

    Console.WriteLine("LinkTopology: {0}", workItemLinkType.LinkTopology.ToString());

    WorkItemLinkType workItemLinkTypeReverse = workItemLinkType.Reverse;

    Console.WriteLine("\t....");

    Console.WriteLine("\tId: {0}", workItemLinkTypeReverse.Id);

    Console.WriteLine("\tName: {0}", workItemLinkTypeReverse.Name);

    Console.WriteLine("\tIsDirectional: {0}", workItemLinkTypeReverse.IsDirectional);

    Console.WriteLine("\tIsForwardLink: {0}", workItemLinkTypeReverse.IsForwardLink);

    Console.WriteLine("\tIsNonCircular: {0}", workItemLinkTypeReverse.IsNonCircular);

    Console.WriteLine("\tIsOneToMany: {0}", workItemLinkTypeReverse.IsOneToMany);

    Console.WriteLine("\tLinkTopology: {0}", workItemLinkTypeReverse.LinkTopology.ToString());

}

 

 

 

When you run the code                it prints something like
Base ReferenceName: Microsoft.VSTS.Common.Produces

Id: 4

Name: Produces

IsDirectional: True

IsForwardLink: True

IsNonCircular: True

IsOneToMany: False

        ....

        Id: -4

        Name: Is Produced By

        IsDirectional: True

        IsForwardLink: False

        IsNonCircular: True

        IsOneToMany: False

 

So it seems that a “Relation” is represented by two “Work Item Link Type” instances.  Each direction of the “relation” is a work item link type. The other direction is the “reverse” work item link type.

Let’s take a concrete example:
A feature is produced by one or more deliverables. A deliverable produces one or more features.
This relation is a many to many relation. This relation has a BaseReferenceName – “Produces” and is represented by two work item link type instances.

 

Features Is Produced by  Deliverable

Deliverable Produces Feature.

Each work item link type has a “Reverse” property that points to its counterpart.

 

Once I got the work item types and work item link types, I tried to see if there are constraints on what link types a work item type can have. For example, can a feature have a parent as a deliverable?
Can a deliverable have a parent Task? It seems that given a set of work item types, only certain relations are valid for a work item of a give type. Unfortunately, as of now, this feature is not implemented.
So any work item type can have any relation or link type as you desire.  The team foundation server team is apparently thinking about adding this in the subsequent CTP releases, but nothing is guaranteed.

 

Querying on Links

Let’s take a look at how to query the work item links.
The WorkItemLinks property will give you all the work item links. Since there could be other types of links, the BaseLinkType property is looked at to make sure that this is the work item link. By querying the WorkItemLink.SourceId and TargetId properties, you can discover what the work item is on the other side of the relation. In this case, my feature 9999, is hooked up to a deliverable via a “Produces” relation
and with a “Is Produced By “directional link.

 

WorkItem wiFeatureGroup = WIStore.GetWorkItem(9999);

foreach (WorkItemLink wiLink in wiFeatureGroup.WorkItemLinks)

{

    if (wiLink.BaseType == BaseLinkType.WorkItemLink)

    {

 

        if(wiLink.LinkType.BaseReferenceName == "Microsoft.VSTS.Common.Produces" && wiLink.LinkType.Name == "Is Produced By")

        {

            WorkItem wiDeliverable = WIStore.GetWorkItem(wiLink.TargetId);

            Console.WriteLine("The target WorkItemID is {0} and its type is {1}", wiDeliverable.Id, wiDeliverable.Type.Name);

        }

    }

}



Adding a link

 

Adding a link is simple. You create a link and add it to the collection, then save the work item

 

 

WorkItem wiFeatureGroup = WIStore.GetWorkItem(9999);

WorkItemLink wiLink = new WorkItemLink(WIStore.WorkItemLinkTypes["Is Produced By"], 9999, 8888);

wiFeatureGroup.WorkItemLinks.Add(wiLink);

wiFeatureGroup.Save();

 

 

 

Deleting a link

To delete a link, you would need to identity the link you want to delete and call the Remove() method on that. Note that you need to save the WorkItem to persist the changes.

 

WorkItem wiFeatureGroup = WIStore.GetWorkItem(642);

foreach (WorkItemLink wiLink in wiFeatureGroup.WorkItemLinks)

{

    if (wiLink.BaseType == BaseLinkType.WorkItemLink)

    {

 

        if(wiLink.LinkType.BaseReferenceName == "Microsoft.VSTS.Common.Produces" && wiLink.LinkType.Name == "Is Produced By")

        {

            WorkItem wiDeliverable = WIStore.GetWorkItem(wiLink.TargetId);

            if (wiDeliverable.Id == 887)

            {

                wiFeatureGroup.WorkItemLinks.Remove(wiLink);

                wiFeatureGroup.Save();

                break;

            }

 

        }

    }

}



All in all, you will generally find that querying, adding and deleting work item links are easy and intuitive.
The ability to add “relations” to work items opens tremendous flexibility and gives you the ability to write cool new applications that use the power of TFS work item store.

 

 



So I moved from the networking group to Visual Studio Team System Test. Recently, I was investigating an issue where the client, controller and agent are not working together
when in a workgroup topology. In other words, the machines that are running the client, controller service, and agent service are *non* domain joined and are
part of a workgroup. In this situation, the client is simply failing to connect to the controller. The client gets the message
"The server rejected the client's credentials - Log on failed".

For this discussion let’s say I have Machine CLEINT with user Tony. The machine that is running controller (lets say CONTROLLER)  has the same user and password  Tony.

I started investigating this issue by enabling the security audit in the security policy. In examining the security audit log, I found that the user on the client
machine was rejected. The message goes like "Bad user name or password, user Tony, Domain CLIENT". On the first thought it kind of makes sense that the user
CLIENT\Tony will be unknown to CONTROLLER. However, the windows workgroup security needs to enable this scenario since there is no central
domain controller. So CLIENT\Tony should be able to authenticate to CONTROLLER as CONTROLLER\Tony. This scenario should work. So what gives?

The next thing I tried is to simply map a network share from CLIENT to CONTROLLER, something like \\controller\c$. Since both CLIENT and CONTROLLER
have Tony as the interactive user, this file share mapping should work. (I already enabled the firewall for this). What I got is a dialog box asking me for credentials.
The strange thing is that the user box is grayed out and says CONTROLLER\Guest. I wondered why it is forcing me to login as guest.  A quick call to
the security team gave me the clue.

For Windows XP, in a workgroup situation, there is a setting that forces all NETWORK logins as GUEST. Which means that even if you
are trying to authenticate with a legitimate user and password credentials, it will force you to login as GUEST. Clearly, this is failing
because I am trying to login as Tony and not as Guest.

The fix is to edit the registry HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\ForceGuest
and set the value to 0. This will remove the restriction of forcing the network logins as "Guest". Once I set this value to 0 on *all* the machines (client, controller and agent) and rebooted  
the machines, it all worked perfectly.

To recap,

1. Windows XP "ForceGuest" is on by default in a workgroup scenario, which means all network logins are allowed as Guest only..

2.  Set the registry key to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\ForceGuest to 0 to remove this restriction

3. Reboot after applying the security setting

I will post a more detailed article on the controller, agent and client setup, users and permissions shortly. 

  

These days connected applications are everywhere. Web Services and Indigo based applications are used to hook up various services over the internet. When something goes wrong you can debug all you want but ultimately you need to look at what is really going on the wire. Network Monitor is your friend. I am surprised how many people are not really aware of this tool called Network Monitor. Many people who have heard about it think that it is too complex to capture network traffic and understand the traffic.

In this article I would like explain in easy to understand terms, how to install a network monitor, how to capture traffic, how to save it and how to interpret it. My belief is that once you understand this and used the tool, you will wonder how you ever lived without it. Let’s jump in.

 

1.      How to install a Network Monitor.
You might want to know that Network Monitor is a free tool.
Go to Control Panel, Add Remove Programs applet.
Then choose Add Remove Windows Components
Then Select Management and Monitoring Tools and click details.
Choose Network Monitor and proceed with the installation.
You may be prompted for the Windows Installation CD
After the installation, you will see the Network Monitor in the Administrative Tools.

2.      Launching the Network Monitor for the first time.
When you launch the Network Monitor for the first time you will see a dialog that says “Please specify the network on which you want to capture the data”.
What does this mean?
Network Monitor captures data on a SPECIFIC network adapter. Even if you have only one physical network card, you may have more than one adapter. Don’t worry about this. It will be clear in a second.
Click OK to this dialog.
The dialog that pops up next [You can access the same dialog through the Capture/Networks menu option] is the one where you pick the “Network” you want to monitor. Expand the Local Computer node. You will see one or more Networks.
Which one to choose? Well it depends.
If you have a computer with one network card, simply choose the LAN Connection and click OK
If you have a computer with more than one network card, you have to choose one.
Open a command prompt and type IPCONFIG /all. Then for each Network interface, note down the physical address or MAC address. Once you know the MAC Address,
in the properties windows of the “Select a network” dialog we are talking about,
choose the network whose MAC address matches the MAC address of the network you want to monitor. Then Click OK.

3.      Using Network Monitor.
Once you have selected a network to monitor, simply press the button that looks like the PLAY button on the toolbar or choose Capture/Start or press F10.
You should see some activity in the Frames per second and other bars. Launch a browser and go to a site. You should see some more activity. If you don’t see any activity on the screen, then you have selected a wrong network. Repeat #2.

At this point you can run any application including the one you want to debug.
The traffic generated by the application is captured. When you are ready to stop
capturing p
ress the stop button or select Capture/Stop or press F11.
To display what you just captured, press F12.
To save the capture use File/Save as.
That’s it you have a capture file. You can send it to other people or look at it later.

4.      What is in a capture file?
You might be wondering what exactly is in a capture file. The capture file contains every single frame that the computer received or sent on the “network” you chose. Even if you capture for a very small amount of time, you can get a LOT of frames.
But you don’t need to worry. Network Monitor has a lot of capabilities to filter and sort.

5.      How do I find what I want in the capture file?
When you are looking at a capture you will find that there are several columns. First is the frame number. When you are explaining the issue to someone you can say something like  “I see something interesting in frame 999”. Then you can see the Time, Source MAC Address, Dest MAC address, Protocol, Description, etc. You may not find the columns exactly in the order I described but you can order the columns by drag and drop.

The protocol column is very interesting. This column tells you what the protocol is for the frame captured. On a network a lot of things are going on. On my computer I see HTTTP, BONE, ARP, RARP, DNS, TCP, ICMP6 among many other things.

Let’s say you are having an issue with one of your HTTP requests. You want to only
look at the HTTP requests and filter out the noise

Click on the funnel toolbar or choose Display/Filter or press F8. You will see a filter dialog. Double click on Protocol=Any line. Click Disable All. AT this point all the protocols are disabled. Then Click on HTTP [Use the scroll bars to find HTTP] and enable the protocol. Click OK.

Now you should see Protocol=HTTP. This means that the current filter only shows HTTP protocol. Now you can see only HTTP frames. Note that all the other protocols are still there in the capture, you are only seeing the HTTP frames. There is a way to not EVEN CAPTURE other protocols – this is achieved by the CAPTURE filter. We will not discuss that now.

OK now you are looking at HTTP frames. How to make sense of these? At this point You need to have knowledge on HTTP protocol and how to interpret the protocol.
If you double click it shows you the complete details of the HTTP protocol information in each packet.
Let me know if this is helpful and if you need more information.

Some people have asked me how to get the SubnetMask for the network interfafce.
The subnet mask is valid only for IPv4.
The solution may not jump at you since there is no property called SubnetMask,
however there is a property called IPv4Mask.
Here is the code to get the subnet mask for each IPv4 address on the box

using System;
using System.Net.NetworkInformation;

public class test
{
 public static void Main()
 {
  NetworkInterface[] Interfaces = NetworkInterface.GetAllNetworkInterfaces();
  foreach(NetworkInterface Interface in Interfaces)
  {
   if(Interface.NetworkInterfaceType == NetworkInterfaceType.Loopback) continue;
   Console.WriteLine(Interface.Description);
   UnicastIPAddressInformationCollection UnicastIPInfoCol = Interface.GetIPProperties().UnicastAddresses;
   foreach(UnicastIPAddressInformation UnicatIPInfo in UnicastIPInfoCol)
   {
    Console.WriteLine("\tIP Address is {0}", UnicatIPInfo.Address);
    Console.WriteLine("\tSubnet Mask is {0}", UnicatIPInfo.IPv4Mask);
   }
  }
 }
}

In Whidbey (.NET Framework 2.0) System.Net has a new feature called Tracing.
This uses the builtin CLR Tracing functionality. What is interesting about the System.Net Tracing is that
You can easily  see the data that is being sent or received without having to use the NETMON.

If you have used Network Monitor tools like NETMON/Ethereal you will find that
there is so much noise that you need to weed through. This can be partially addressed through
capture filters and display filters. But then looking at a frame in the capture you can't
easily determine which process issued that request. Similarly if there are multiple threads,
you can't easily determine which thread issued that request. One more thing is that if the
client and server are on the same machine you can;t capture the loop back traffic. Perhaps the most limiting of all
is that if the application is using SSL (https or FTP with SSL) then it is not possible to look at the traffic.
You can at most see the TCP frames with encrypted payload. Not much of use. Right?

With System.Net, we addressed all these issues. System.Net tracing is
1) Per process
2) Shows threads
3) Works for SSL
4) Works for Loopback.
5) You don't need to recompile the code

With all these advantages I find the System.Net tracing a very compelling feature that will not be able to live without.
Lets look at some examples.

1. Consider the following code
//JScript.Net
import System;
import System.Net;
(new WebClient()).DownloadString(http://www.microsoft.com/office);

I wrote the above JScript.Net code. You can compile the code with Jsc <filename>
What if you really want to see the trace for that code and see what is going on the wire?

You can use the following configuration file. Save this file as app.exe.config file.
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <system.diagnostics>
        <trace autoflush="true" />
            <sources>
                <source name="System.Net" maxdatasize="1024">
                    <listeners>
                        <add name="MyTraceFile"/>
                    </listeners>
                </source>
            </sources>

            <sharedListeners>
                <add
                  name="MyTraceFile"
                  type="System.Diagnostics.TextWriterTraceListener"
                  initializeData="System.Net.trace.log"
                />
            </sharedListeners>

            <switches>
                <add name="System.Net" value="Verbose" />
            </switches>

    </system.diagnostics>
</configuration>


<!--
You can use these two attributes on the Trace Sources
tracemode="protocolonly"
maxdatasize="1024"
-->

<!--
You can choose from 4 sources

<source name="System.Net" maxdatasize="1024">
    <listeners>
        <add name="MyTraceFile"/>
    </listeners>
</source>

<source name="System.Net.Sockets">
    <listeners>
        <add name="MyTraceFile"/>
    </listeners>
</source>  
<source name="System.Net.Cache">
    <listeners>
        <add name="MyTraceFile"/>
    </listeners>
</source>
<source name="System.Net.HttpListener">
    <listeners>
        <add name="MyTraceFile"/>
    </listeners>
</source>               
-->


Now when you run the application you will find the log file as System.Net.Trace.Log file

System.Net Information: 0 : [3560] HttpWebRequest#33736294 - Request: GET /office HTTP/1.1

System.Net Information: 0 : [3560] ConnectStream#31914638 - Sending headers
{
Host:
www.microsoft.com
Connection: Keep-Alive
}.
System.Net Information: 0 : [3560] Connection#48285313 - Received status line: Version=1.1, StatusCode=301, StatusDescription=Moved Permanently.
System.Net Information: 0 : [3560] Connection#48285313 - Received headers
{
Connection: Keep-Alive
Proxy-Connection: Keep-Alive
Content-Length: 155
Content-Type: text/html
Date: Mon, 12 Sep 2005 21:51:00 GMT
Location:
http://www.microsoft.com/office/
P3P: CP="ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI"
Server: Microsoft-IIS/6.0
Via: 1.1 MSCORPGATE12
X-Powered-By: ASP.NET
}.

You can see that the log file shows what is being sent on the wire and what headers are being received.
You don't need to use NETMON any more to see what traffic is being sent on the wire. Very good indeed.

You can also see that we show the objects and hash codes for each object so you can follow an object
using this log file. You can also see [3560]as the thread id of the thread that is issuing the request.

2.What about SSL? What about Local Host?
Lets consider the following code

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Net.Security;
using System.Security.Policy;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
class Program
{
 static void Main(string[] args)
 {
  Stream s = null;
  StreamReader sr = null;
  HttpWebResponse res = null;
  try{
      //Hook a callback to verify the remote certificate
      ServicePointManager.ServerCertificateValidationCallback =
        new RemoteCertificateValidationCallback(MyCertValidationCb); 

      HttpWebRequest req
        = (HttpWebRequest)
           WebRequest.Create("
https://localhost/SecureNoClientCerts/test.htm");

   req.Proxy = null;

   res = req.GetResponse() as HttpWebResponse;
   s = res.GetResponseStream();
   sr = new StreamReader(s, Encoding.UTF8);
   Console.WriteLine(sr.ReadToEnd());
  }
  catch(Exception ex){
   Console.WriteLine(ex);
  }
  finally{
   if(res != null) res.Close();
   if(s != null) s.Close();
   if(sr != null) sr.Close();
  }
 }

  public static bool MyCertValidationCb(
        object sender,
        X509Certificate certificate,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
  {
    if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors)
              == SslPolicyErrors.RemoteCertificateChainErrors)
    {
      return false;
    }
    else if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch)
                    == SslPolicyErrors.RemoteCertificateNameMismatch)
    {
      Zone z;
      z = Zone.CreateFromUrl(((HttpWebRequest)sender).RequestUri.ToString());
      if (z.SecurityZone == System.Security.SecurityZone.Intranet
        || z.SecurityZone == System.Security.SecurityZone.MyComputer)
      {
        return true;
      }
      return false;
    }
    return false;
  } 
}


This code is trying to make an SSL request to the local host. You will also find that the
RemoteCertificationCallback sample.

You can enable both the Sockets and System.Net sources with the following config file
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <system.diagnostics>
        <trace autoflush="true" />
            <sources>
                <source name="System.Net" maxdatasize="1024">
                    <listeners>
                        <add name="MyTraceFile"/>
                    </listeners>
                </source>
              <source name="System.Net.Sockets" maxdatasize="1024">
                    <listeners>
                        <add name="MyTraceFile"/>
                    </listeners>
                </source>  
           </sources>

            <sharedListeners>
                <add
                  name="MyTraceFile"
                  type="System.Diagnostics.TextWriterTraceListener"
                  initializeData="System.Net.trace.log"
                />
            </sharedListeners>
            <switches>
                <add name="System.Net" value="Verbose" />
              <add name="System.Net.Sockets" value="Verbose" />
            </switches>
    </system.diagnostics>
</configuration>


Here is the log file produced

System.Net Verbose: 0 : [3848] WebRequest::Create(https://localhost/SecureNoClientCerts/test.htm)
System.Net Verbose: 0 : [3848] HttpWebRequest#33574638::HttpWebRequest(
https://localhost/SecureNoClientCerts/test.htm#1109589874)
System.Net Verbose: 0 : [3848] Exiting HttpWebRequest#33574638::HttpWebRequest()
System.Net Verbose: 0 : [3848] Exiting WebRequest::Create()  -> HttpWebRequest#33574638
System.Net Information: 0 : [3848] Associating HttpWebRequest#33574638 with ServicePoint#33736294
System.Net Verbose: 0 : [3848] HttpWebRequest#33574638::GetResponse()
System.Net Information: 0 : [3848] Associating Connection#35191196 with HttpWebRequest#33574638
System.Net.Sockets Verbose: 0 : [3848] Socket#48285313::Socket(InterNetwork#2)
System.Net.Sockets Verbose: 0 : [3848] Exiting Socket#48285313::Socket()
System.Net.Sockets Verbose: 0 : [3848] Socket#48285313::Connect(1:443#16777668)
System.Net.Sockets Verbose: 0 : [3848] Exiting Socket#48285313::Connect()
System.Net Information: 0 : [3848] TlsStream#31914638::.ctor(host=localhost, #certs=0)
System.Net Information: 0 : [3848] Associating HttpWebRequest#33574638 with ConnectStream#18796293
System.Net Information: 0 : [3848] HttpWebRequest#33574638 - Request: GET /SecureNoClientCerts/test.htm HTTP/1.1

System.Net Information: 0 : [3848] SecureChannel#34948909::.ctor(hostname=localhost, #clientCertificates=0)
System.Net Information: 0 : [3848] Enumerating security packages:
System.Net Information: 0 : [3848]     Negotiate
System.Net Information: 0 : [3848]     Kerberos
System.Net Information: 0 : [3848]     NTLM
System.Net Information: 0 : [3848]     Microsoft Unified Security Protocol Provider
System.Net Information: 0 : [3848]     Schannel
System.Net Information: 0 : [3848]     WDigest
System.Net Information: 0 : [3848]     DPA
System.Net Information: 0 : [3848]     Digest
System.Net Information: 0 : [3848]     MSN
System.Net Information: 0 : [3848] SecureChannel#34948909 - Left with 0 client certificates to choose from.
System.Net Information: 0 : [3848] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent  = Outbound, scc     = System.Net.SecureCredential)
System.Net Information: 0 : [3848] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = (null), targetName = localhost, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [3848] InitializeSecurityContext(In-Buffer length=0, Out-Buffer length=70, returned code=ContinueNeeded).
System.Net.Sockets Verbose: 0 : [3848] Socket#48285313::Send()
System.Net.Sockets Verbose: 0 : [3848] Data from Socket#48285313::Send
System.Net.Sockets Verbose: 0 : [3848] 00000000 : 16 03 00 00 41 01 00 00-3D 03 00 43 26 02 90 83 : ....A...=..C&...
System.Net.Sockets Verbose: 0 : [3848] 00000010 : 33 F1 48 D6 4B 74 DB E2-47 6E 7A 3D 55 D2 43 E2 : 3.H.Kt..Gnz=U.C.
System.Net.Sockets Verbose: 0 : [3848] 00000020 : FC 02 CF 0B AF D4 E0 83-40 59 33 00 00 16 00 04 :
........@Y3.....
System.Net.Sockets Verbose: 0 : [3848] 00000030 : 00 05 00 0A 00 09 00 64-00 62 00 03 00 06 00 13 : .......d.b......
System.Net.Sockets Verbose: 0 : [3848] 00000040 : 00 12 00 63 01 00                               : ...c..
System.Net.Sockets Verbose: 0 : [3848] Exiting Socket#48285313::Send()  -> 70#70
System.Net.Sockets Verbose: 0 : [3848] Socket#48285313::Receive()
System.Net.Sockets Verbose: 0 : [3848] Data from Socket#48285313::Receive
System.Net.Sockets Verbose: 0 : [3848] 00000000 : 16 03 00 06 09                                  : .....
System.Net.Sockets Verbose: 0 : [3848] Exiting Socket#48285313::Receive()  -> 5#5
System.Net.Sockets Verbose: 0 : [3848] Socket#48285313::Receive()
System.Net.Sockets Verbose: 0 : [3848] Data from Socket#48285313::Receive
System.Net.Sockets Verbose: 0 : [3848] (printing 1024 out of 1545)
System.Net.Sockets Verbose: 0 : [3848] 00000005 : 02 00 00 46 03 00 43 26-02 90 92 8B 0F 1A 72 76 : ...F..C&......rv
System.Net.Sockets Verbose: 0 : [3848] 00000015 : A2 48 A6 F3 3D 43 22 ED-D5 63 15 8E E3 BD 4F AD : .H..=C"..c....O.
System.Net.Sockets Verbose: 0 : [3848] 00000025 : DD 5A F4 42 B3 72 20 2B-0D 00 00 A7 86 54 E0 9B : .Z.B.r +.....T..
System.Net.Sockets Verbose: 0 : [3848] 00000035 : C7 9A 00 FB 5D 3C AC DD-3E D9 50 FC 08 D8 AD 9F : ....]<..>.P.....
System.Net.Sockets Verbose: 0 : [3848] 00000045 : 14 C7 97 AB 56 98 D4 00-04 00 0B 00 05 B7 00 05 : ....V...........
System.Net.Sockets Verbose: 0 : [3848] 00000055 : B4 00 05 B1 30 82 05 AD-30 82 04 95 A0 03 02 01 : ....0...0.......
System.Net.Sockets Verbose: 0 : [3848] 00000065 : 02 02 0A 34 74 D0 6D 00-02 00 00 01 2B 30 0D 06 : ...4t.m.....+0..
System.Net.Sockets Verbose: 0 : [3848] 00000075 : 09 2A 86 48 86 F7 0D 01-01 05 05 00 30 77 31 13 : .*.H........0w1.
System.Net.Sockets Verbose: 0 : [3848] 00000085 : 30 11 06 0A 09 92 26 89-93 F2 2C 64 01 19 16 03 : 0.....&...,d....
System.Net.Sockets Verbose: 0 : [3848] 00000095 : 63 6F 6D 31 19 30 17 06-0A 09 92 26 89 93 F2 2C : com1.0.....&...,
System.Net.Sockets Verbose: 0 : [3848] 000000A5 : 64 01 19 16 09 6D 69 63-72 6F 73 6F 66 74 31 14 : d....microsoft1.
System.Net.Sockets Verbose: 0 : [3848] 000000B5 : 30 12 06 0A 09 92 26 89-93 F2 2C 64 01 19 16 04 : 0.....&...,d....
System.Net.Sockets Verbose: 0 : [3848] 000000C5 : 63 6F 72 70 31 17 30 15-06 0A 09 92 26 89 93 F2 : corp1.0.....&...
System.Net.Sockets Verbose: 0 : [3848] 000000D5 : 2C 64 01 19 16 07 72 65-64 6D 6F 6E 64 31 16 30 : ,d....redmond1.0
System.Net.Sockets Verbose: 0 : [3848] 000000E5 : 14 06 03 55 04 03 13 0D-4E 43 4C 43 45 52 54 53 : ...U....NCLCERTS
System.Net.Sockets Verbose: 0 : [3848] 000000F5 : 45 52 56 45 52 30 1E 17-0D 30 35 30 38 32 31 30 : ERVER0...0508210
System.Net.Sockets Verbose: 0 : [3848] 00000105 : 30 33 30 32 35 5A 17 0D-30 36 30 38 32 31 30 30 : 03025Z..06082100
System.Net.Sockets Verbose: 0 : [3848] 00000115 : 34 30 32 35 5A 30 2E 31-2C 30 2A 06 03 55 04 03 : 4025Z0.1,0*..U..
System.Net.Sockets Verbose: 0 : [3848] 00000125 : 13 23 64 67 6F 72 74 69-6C 74 2E 72 65 64 6D 6F : .#dgortilt.redmo
System.Net.Sockets Verbose: 0 : [3848] 00000135 : 6E 64 2E 63 6F 72 70 2E-6D 69 63 72 6F 73 6F 66 : nd.corp.microsof
System.Net.Sockets Verbose: 0 : [3848] 00000145 : 74 2E 63 6F 6D 30 81 9F-30 0D 06 09 2A 86 48 86 : t.com0..0...*.H.
System.Net.Sockets Verbose: 0 : [3848] 00000155 : F7 0D 01 01 01 05 00 03-81 8D 00 30 81 89 02 81 : ...........0....
System.Net.Sockets Verbose: 0 : [3848] 00000165 : 81 00 CF E2 93 87 AF 2A-19 56 47 CF E0 2F E3 13 : .......*.VG../..
System.Net.Sockets Verbose: 0 : [3848] 00000175 : B8 4F B7 20 C7 FA 1A 55-5F B0 3E 6E B6 4B 9B DC : .O. ...U_.>n.K..
System.Net.Sockets Verbose: 0 : [3848] 00000185 : 50 1F 7E A7 5A 13 49 6D-8B 4A B1 27 50 91 47 51 : P.~.Z.Im.J.'P.GQ
System.Net.Sockets Verbose: 0 : [3848] 00000195 : 6A 8D 21 D1 DC 6C C2 AD-1D 38 E2 20 8A 1A 75 24 : j.!..l...8. ..u$
System.Net.Sockets Verbose: 0 : [3848] 000001A5 : F3 8F 51 E9 BD E7 F9 FE-8D 11 C9 3A 9E 88 B4 D0 : ..Q........:....
System.Net.Sockets Verbose: 0 : [3848] 000001B5 : A3 A9 C7 A6 7B C0 91 D9-1D 10 AE 00 38 C6 A9 8E : ....{.......8...
System.Net.Sockets Verbose: 0 : [3848] 000001C5 : 27 52 A5 48 5D DD DF 4B-BF F8 D0 43 AE 43 11 0F : 'R.H]..K...C.C..
System.Net.Sockets Verbose: 0 : [3848] 000001D5 : 0D A5 5E 2C A6 0A 37 9C-EF 9B 5A 30 FC D3 8A 54 : ..^,..7...Z0...T
System.Net.Sockets Verbose: 0 : [3848] 000001E5 : 55 33 02 03 01 00 01 A3-82 03 06 30 82 03 02 30 : U3.........0...0
System.Net.Sockets Verbose: 0 : [3848] 000001F5 : 0E 06 03 55 1D 0F 01 01-FF 04 04 03 02 04 F0 30 : ...U...........0
System.Net.Sockets Verbose: 0 : [3848] 00000205 : 44 06 09 2A 86 48 86 F7-0D 01 09 0F 04 37 30 35 : D..*.H.......705
System.Net.Sockets Verbose: 0 : [3848] 00000215 : 30 0E 06 08 2A 86 48 86-F7 0D 03 02 02 02 00 80 : 0...*.H.........
System.Net.Sockets Verbose: 0 : [3848] 00000225 : 30 0E 06 08 2A 86 48 86-F7 0D 03 04 02 02 00 80 : 0...*.H.........
System.Net.Sockets Verbose: 0 : [3848] 00000235 : 30 07 06 05 2B 0E 03 02-07 30 0A 06 08 2A 86 48 : 0...+....0...*.H
System.Net.Sockets Verbose: 0 : [3848] 00000245 : 86 F7 0D 03 07 30 1D 06-03 55 1D 0E 04 16 04 14 : .....0...U......
System.Net.Sockets Verbose: 0 : [3848] 00000255 : 25 9C 39 A7 F3 81 B2 20-79 95 22 C4 BD E3 2E A3 : %.9.... y.".....
System.Net.Sockets Verbose: 0 : [3848] 00000265 : 01 98 E6 AF 30 13 06 03-55 1D 25 04 0C 30 0A 06 : ....0...U.%..0..
System.Net.Sockets Verbose: 0 : [3848] 00000275 : 08 2B 06 01 05 05 07 03-01 30 1F 06 03 55 1D 23 : .+.......0...U.#
System.Net.Sockets Verbose: 0 : [3848] 00000285 : 04 18 30 16 80 14 53 FA-E2 5F A8 9E B1 10 55 65 : ..0...S.._....Ue
System.Net.Sockets Verbose: 0 : [3848] 00000295 : E3 53 13 1B 9E EF 2B 23-D8 F5 30 82 01 56 06 03 : .S....+#..0..V..
System.Net.Sockets Verbose: 0 : [3848] 000002A5 : 55 1D 1F 04 82 01 4D 30-82 01 49 30 82 01 45 A0 : U.....M0..I0..E.
System.Net.Sockets Verbose: 0 : [3848] 000002B5 : 82 01 41 A0 82 01 3D 86-81 A8 6C 64 61 70 3A 2F : ..A...=...ldap:/
System.Net.Sockets Verbose: 0 : [3848] 000002C5 : 2F 2F 43 4E 3D 4E 43 4C-43 45 52 54 53 45 52 56 : //CN=NCLCERTSERV
System.Net.Sockets Verbose: 0 : [3848] 000002D5 : 45 52 28 32 29 2C 43 4E-3D 77 69 74 34 2C 43 4E : ER(2),CN=wit4,CN
System.Net.Sockets Verbose: 0 : [3848] 000002E5 : 3D 43 44 50 2C 43 4E 3D-50 75 62 6C 69 63 25 32 : =CDP,CN=Public%2
System.Net.Sockets Verbose: 0 : [3848] 000002F5 : 30 4B 65 79 25 32 30 53-65 72 76 69 63 65 73 2C : 0Key%20Services,
System.Net.Sockets Verbose: 0 : [3848] 00000305 : 43 4E 3D 53 65 72 76 69-63 65 73 2C 44 43 3D 55 : CN=Services,DC=U
System.Net.Sockets Verbose: 0 : [3848] 00000315 : 6E 61 76 61 69 6C 61 62-6C 65 43 6F 6E 66 69 67 : navailableConfig
System.Net.Sockets Verbose: 0 : [3848] 00000325 : 44 4E 3F 63 65 72 74 69-66 69 63 61 74 65 52 65 : DN?certificateRe
System.Net.Sockets Verbose: 0 : [3848] 00000335 : 76 6F 63 61 74 69 6F 6E-4C 69 73 74 3F 62 61 73 : vocationList?bas
System.Net.Sockets Verbose: 0 : [3848] 00000345 : 65 3F 6F 62 6A 65 63 74-43 6C 61 73 73 3D 63 52 : e?objectClass=cR
System.Net.Sockets Verbose: 0 : [3848] 00000355 : 4C 44 69 73 74 72 69 62-75 74 69 6F 6E 50 6F 69 : LDistributionPoi
System.Net.Sockets Verbose: 0 : [3848] 00000365 : 6E 74 86 48 66 69 6C 65-3A 2F 2F 5C 5C 77 69 74 : nt.Hfile://\\wit
System.Net.Sockets Verbose: 0 : [3848] 00000375 : 34 2E 72 65 64 6D 6F 6E-64 2E 63 6F 72 70 2E 6D : 4.redmond.corp.m
System.Net.Sockets Verbose: 0 : [3848] 00000385 : 69 63 72 6F 73 6F 66 74-2E 63 6F 6D 5C 43 65 72 : icrosoft.com\Cer
System.Net.Sockets Verbose: 0 : [3848] 00000395 : 74 45 6E 72 6F 6C 6C 5C-4E 43 4C 43 45 52 54 53 : tEnroll\NCLCERTS
System.Net.Sockets Verbose: 0 : [3848] 000003A5 : 45 52 56 45 52 28 32 29-2E 63 72 6C 86 46 68 74 : ERVER(2).crl.Fht
System.Net.Sockets Verbose: 0 : [3848] 000003B5 : 74 70 3A 2F 2F 77 69 74-34 2E 72 65 64 6D 6F 6E : tp://wit4.redmon
System.Net.Sockets Verbose: 0 : [3848] 000003C5 : 64 2E 63 6F 72 70 2E 6D-69 63 72 6F 73 6F 66 74 : d.corp.microsoft
System.Net.Sockets Verbose: 0 : [3848] 000003D5 : 2E 63 6F 6D 2F 43 65 72-74 45 6E 72 6F 6C 6C 2F : .com/CertEnroll/
System.Net.Sockets Verbose: 0 : [3848] 000003E5 : 4E 43 4C 43 45 52 54 53-45 52 56 45 52 28 32 29 : NCLCERTSERVER(2)
System.Net.Sockets Verbose: 0 : [3848] 000003F5 : 2E 63 72 6C 30 81 FA 06-08 2B 06 01 05 05 07 01 : .crl0....+......
System.Net.Sockets Verbose: 0 : [3848] Exiting Socket#48285313::Receive()  -> 1545#1545
System.Net Information: 0 : [3848] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 185a20:daf80, targetName = localhost, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [3848] InitializeSecurityContext(In-Buffer length=1550, Out-Buffer length=204, returned code=ContinueNeeded).
System.Net.Sockets Verbose: 0 : [3848] Socket#48285313::Send()
System.Net.Sockets Verbose: 0 : [3848] Data from Socket#48285313::Send
System.Net.Sockets Verbose: 0 : [3848] 00000000 : 16 03 00 00 84 10 00 00-80 32 CB FC 3B 3B A6 67 : .........2..;;.g
System.Net.Sockets Verbose: 0 : [3848] 00000010 : BF F8 89 3F 40 CE 20 E4-79 92 4C 9C 8D 84 4C 77 : ...?@. .y.L...Lw
System.Net.Sockets Verbose: 0 : [3848] 00000020 : E6 BC 1D EA 59 95 F5 FA-E6 7B 35 40 68 EA 97 23 :
....Y....{5@h..#
System.Net.Sockets Verbose: 0 : [3848] 00000030 : 89 20 DD A5 E1 DD 2C 48-98 2F 2E C5 C3 4B 87 59 : . ....,H./...K.Y
System.Net.Sockets Verbose: 0 : [3848] 00000040 : C8 86 7F BA 85 CF BC B0-5C 26 1C E7 AA A6 C3 54 : ........\&.....T
System.Net.Sockets Verbose: 0 : [3848] 00000050 : 55 59 AE B3 3B 04 24 40-18 D5 B3 C8 57 02 83 E2 : UY..;.$@....W...
System.Net.Sockets Verbose: 0 : [3848] 00000060 : 46 2E 2B EA 1B C1 8E 6D-4A 3C E5 C7 5E 2E 47 24 : F.+....mJ<..^.G$
System.Net.Sockets Verbose: 0 : [3848] 00000070 : 13 DB 03 30 76 CB C4 19-FA D1 85 11 BD 5B AC A6 : ...0v........[..
System.Net.Sockets Verbose: 0 : [3848] 00000080 : 0D 60 9D E4 FB 6A BA 33-CB 14 03 00 00 01 01 16 : .`...j.3........
System.Net.Sockets Verbose: 0 : [3848] 00000090 : 03 00 00 38 B9 4C CA CF-CD C2 FF 20 61 43 8E 02 : ...8.L..... aC..
System.Net.Sockets Verbose: 0 : [3848] 000000A0 : 54 03 55 7D 6F 84 BE F9-B2 5D 44 31 1D FF B1 1F : T.U}o....]D1....
System.Net.Sockets Verbose: 0 : [3848] 000000B0 : 0F 45 04 81 ED 09 3A 2E-72 44 3E 37 B1 F6 CE 56 : .E....:.rD>7...V
System.Net.Sockets Verbose: 0 : [3848] 000000C0 : DB 67 11 8A E8 CE 35 23-DF 0B F5 3E             : .g....5#...>
System.Net.Sockets Verbose: 0 : [3848] Exiting Socket#48285313::Send()  -> 204#204
System.Net.Sockets Verbose: 0 : [3848] Socket#48285313::Receive()
System.Net.Sockets Verbose: 0 : [3848] Data from Socket#48285313::Receive
System.Net.Sockets Verbose: 0 : [3848] 00000000 : 14 03 00 00 01                                  : .....
System.Net.Sockets Verbose: 0 : [3848] Exiting Socket#48285313::Receive()  -> 5#5
System.Net.Sockets Verbose: 0 : [3848] Socket#48285313::Receive()
System.Net.Sockets Verbose: 0 : [3848] Data from Socket#48285313::Receive
System.Net.Sockets Verbose: 0 : [3848] 00000005 : 01                                              : .
System.Net.Sockets Verbose: 0 : [3848] Exiting Socket#48285313::Receive()  -> 1#1
System.Net Information: 0 : [3848] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 185a20:daf80, targetName = localhost, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [3848] InitializeSecurityContext(In-Buffer length=6, Out-Buffer length=0, returned code=ContinueNeeded).
System.Net.Sockets Verbose: 0 : [3848] Socket#48285313::Receive()
System.Net.Sockets Verbose: 0 : [3848] Data from Socket#48285313::Receive
System.Net.Sockets Verbose: 0 : [3848] 00000000 : 16 03 00 00 38                                  : ....8
System.Net.Sockets Verbose: 0 : [3848] Exiting Socket#48285313::Receive()  -> 5#5
System.Net.Sockets Verbose: 0 : [3848] Socket#48285313::Receive()
System.Net.Sockets Verbose: 0 : [3848] Data from Socket#48285313::Receive
System.Net.Sockets Verbose: 0 : [3848] 00000005 : 51 35 A8 FC 48 38 37 66-51 6D A3 A7 0C 10 95 93 : Q5..H87fQm......
System.Net.Sockets Verbose: 0 : [3848] 00000015 : 6B FF 8A 41 00 38 42 F4-B8 AA 0F C9 DB 71 16 46 : k..A.8B......q.F
System.Net.Sockets Verbose: 0 : [3848] 00000025 : 67 CA 04 20 F4 EF EB 31-EE BB C7 AD E8 7E B5 76 : g.. ...1.....~.v
System.Net.Sockets Verbose: 0 : [3848] 00000035 : 8D 63 96 99 96 63 53 3C-                        : .c...cS<
System.Net.Sockets Verbose: 0 : [3848] Exiting Socket#48285313::Receive()  -> 56#56
System.Net Information: 0 : [3848] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 185a20:daf80, targetName = localhost, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [3848] InitializeSecurityContext(In-Buffer length=61, Out-Buffer length=0, returned code=OK).
System.Net Information: 0 : [3848] Remote certificate: [Version]
  V3

[Subject]
  CN=dgortilt.redmond.corp.microsoft.com
  Simple Name: dgortilt.redmond.corp.microsoft.com
  DNS Name: dgortilt.redmond.corp.microsoft.com

[Issuer]
  CN=NCLCERTSERVER, DC=redmond, DC=corp, DC=microsoft, DC=com
  Simple Name: NCLCERTSERVER
  DNS Name: NCLCERTSERVER

[Serial Number]
  3474D06D00020000012B

[Not Before]
  8/20/2005 5:30:25 PM

[Not After]
  8/20/2006 5:40:25 PM

[Thumbprint]
  19E34D7F445E49ACBD5EEECFF606F10197098F16

[Signature Algorithm]
  sha1RSA(1.2.840.113549.1.1.5)

[Public Key]
  Algorithm: RSA
  Length: 1024
  Key Blob: 30 81 89 02 81 81 00 cf e2 93 87 af 2a 19 56 47 cf e0 2f e3 13 b8 4f b7 20 c7 fa 1a 55 5f b0 3e 6e b6 4b 9b dc 50 1f 7e a7 5a 13 49 6d 8b 4a b1 27 50 91 47 51 6a 8d 21 d1 dc 6c c2 ad 1d 38 e2 20 8a 1a 75 24 f3 8f 51 e9 bd e7 f9 fe 8d 11 c9 3a 9e 88 b4 d0 a3 a9 c7 a6 7b c0 91 d9 1d 10 ae 00 38 c6 a9 8e 27 52 a5 48 5d dd df 4b bf f8 d0 43 ae 43 11 0f 0d a5 5e 2c a6 0a 37 9c ef 9b 5a 30 fc d3 8a 54 55 33 02 03 01 ....
System.Net Information: 0 : [3848] SecureChannel#34948909 - Remote certificate has errors:
System.Net Information: 0 : [3848] SecureChannel#34948909 -  Certificate name mismatch.
System.Net Information: 0 : [3848] SecureChannel#34948909 - Remote certificate was verified as valid by the user.
System.Net.Sockets Verbose: 0 : [3848] Socket#48285313::Send()
System.Net.Sockets Verbose: 0 : [3848] Data from Socket#48285313::Send
System.Net.Sockets Verbose: 0 : [3848] 00000000 : 17 03 00 00 67 79 FD 10-7F A4 92 38 C5 80 38 EE : ....gy.....8..8.
System.Net.Sockets Verbose: 0 : [3848] 00000010 : 3A 6C 66 B7 0C CC C7 29-0C EC 3C 3B 88 D0 BE 94 : :lf....)..<;....
System.Net.Sockets Verbose: 0 : [3848] 00000020 : 6E C6 0D 7D B4 12 29 BD-ED 86 05 8C 17 CA 1B 9D : n..}..).........
System.Net.Sockets Verbose: 0 : [3848] 00000030 : D6 1A D7 65 D9 77 AD 5A-7C 17 E7 1A 78 69 90 B0 : ...e.w.Z|...xi..
System.Net.Sockets Verbose: 0 : [3848] 00000040 : DB 68 89 D8 F3 56 C2 D4-F8 52 CF EB 4B 1E 38 4B : .h...V...R..K.8K
System.Net.Sockets Verbose: 0 : [3848] 00000050 : D4 0D AA 5A 5C A4 C4 15-E8 AF F2 79 93 E0 06 37 : ...Z\......y...7
System.Net.Sockets Verbose: 0 : [3848] 00000060 : 10 CA 42 A9 49 8C B2 AC-33 85 68 80             : ..B.I...3.h.
System.Net.Sockets Verbose: 0 : [3848] Exiting Socket#48285313::Send()  -> 108#108
System.Net Information: 0 : [3848] ConnectStream#18796293 - Sending headers
{
Host: localhost
Connection: Keep-Alive
}.

You can see clearly that

1) The Remote Certificate is being clearly presented in the log file.
2) Any errors in the remote certificate are logged.
3) In this case we are returning true for NAME MISMATCH if the server is local or intranet [Please see the
remore certificate validation callback code]
4) The fact that we accepted the certificate is also logged.
5) Then at the sockets level you can see encrypted data being sent
6) At the System.Net level (application level) you can see the decrypted data.

You can see now how easy it is to debug SSL apps using the system.Net tracing.
Let me know your comments

Durgaprasad Gorti
Test Lead
System.Net
Microsoft

 

 


 

 

 

Lets say that you are invoking a web service from another web service. Both are on the same box. You might be making authenticated
or unauthenticated calls and perhaps you are setting the KeepAlive = false.
Intermittently, (under load) you might get "Only one usage of each socket address (protocol/network address/port) is normally permitted (typically under load)."
You might be wondering why are you getting a *SOCKET* Exception...

Here is the scoop
1. When you make authenticated calls, the client is closing connections. And when you are making authenticated calls
repeatedly to the same server, you are making and closing connections repeatedly
2. The same might happen when you are making regular http [un authenticated] calls but setting keep-alive = false.

When a connection is closed, on the side that is closing the connection the 5 tuple
{ Protocol, Local IP, Local Port, Remote IP, Remote Port} goes into a TIME_WAIT state for 240 seconds by default.
In this case, the protocol is fixed - TCP
the local IP, remote IP and remote PORT are also typically fixed. So the variable is the local port.
What happens is that when you don't bind a port in the range 1024-5000 is used.
So roughly you have 4000 ports. If you use all of them in 4 minutes - meaning roughly you
make 16 web service calls per second for 4 minutes you will exhaust all the ports. That is the cause of this exception.

OK now how can this be fixed?
1. One of the ways is to increase the dynamic port range. The max by default is 5000. You can set this up to 65534.
HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPort is the key to use.

2. The second thing you can do is once the connection does get into an TIME_WAIT state you can reduce the time it is
in that state, Default is 4 monutes, but you can set this to 30 seconds
HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\TCPTimedWaitDelay is the key to use.
Set this to 30 seconds

3. If you don't have access to registry you can do this through code.
In System.Net 2.0 we addeed what is called a BindIPEndPOintDelegate
What this does is to give you a chance to choose the local end point for the
connection that is being made.
See http://blogs.msdn.com/malarch/archive/2005/09/13/466664.aspx
for more info.

Using this, the following sample code attempts to choose between 5001 and 65534
and wrap around when we reach 65534.
Req.ServicePoint.BindIPEndPointDelegate
 = new BindIPEndPoint(BindIPEndPointCallback);
public static IPEndPoint BindIPEndPointCallback(ServicePoint servicePoint,
     IPEndPoint remoteEndPoint,
     int retryCount)
{
  int port = Interlocked.Increment(ref m_LastBindPortUsed); //increment
  Interlocked.CompareExchange(ref m_LastBindPortUsed, 5001, 65534);
  if(remoteEndPoint.AddressFamily == AddressFamily.InterNetwork) {
 return new IPEndPoint(IPAddress.Any,port);
  }
  else {
 return new IPEndPoint(IPAddress.IPv6Any,port);
  }
}

Durgaprasad Gorti
Durgaprasad.Gorti@microsoft.com.dontspam
Test Lead
System.Net

 

 

 

I presented System.Net tips and tricks. I did the lunch session on Tuesday and Wednesday.
Lunch sessions are particularly difficult, you only have 45 minutes - You can't present
lengthy topics.
I chose to talk about the System.Net Tracing, Negotiate Stream, SSL Stream, Email with Linked Resources and
then some kerberos information.

Please let me know what you would like to see in the next conference I might speak in. What are the topics of
interest?

If you were at the PDC and attened my session, thanks. Let me know your feedback and your comments and suggestions.

In the next two weeks, I will post all the info I talked about in the PDC.

Durgaprasad Gorti
Test Lead,
System.Net
Microsoft.

If you have questions on the System.Net [managed network programming api's]
please post your questions to
http://forums.microsoft.com/msdn/ShowForum.aspx?ForumID=40

I monitor this forum and so does all of my team. This is your best bet to get your
questions answered. There might be other public news groups, but we are not monitoring those
actively. Please use the forum above.

A few tips for getting your questions answered

  1. Always tell us the Operating System, Service Pack
  2. Always .NET Framework version, Service Pack
  3. If you can tell us the Exact version number of the System.dll it would be great.
  4. Make your question easy to understand. Use specific examples like
    "Machine A does X and Machine B does Y. I exepect Z but I am getting D"
  5. Post your code (snippet). It is so much easier to understand your issue if we can
    look at the code quickly

Good Luck and thanks for posting  your questions.

Durgaprasad Gorti

When using System.Net or Web Services, you might want to receive or send cookies, perhaps for session state
maintenance or in some rare situations for proxy authentication.
The first thing we are tempted to do is to access
WebResponse.Cookies collection. But then it returns no cookies. Why?
It turns out that unless you assign a CookieContainer to the WebRequest, you won't be able to get the
WebResponse.Cookies. It is not very intuitive. Why would I need to create a cookie container to get cookies?
Notice that you need to create a cookie container but then you get cookie collection from WebResponse.
Here is some sample code

CookieContainer CC = new CookieContainer();
HttpWebRequest Req = (HttpWebRequest) WebRequest.Create("
http://localhost/CookieTest/first.aspx");
Req.Proxy = null;
Req.UseDefaultCredentials = true;
//YOU MUST ASSIGN A COOKIE CONTAINER FOR THE REQUEST TO PULL THE COOKIES
Req.CookieContainer = CC;
Res = (HttpWebResponse)Req.GetResponse();
//DUMP THE COOKIES
Console.WriteLine("----- COOKIES -----");   
if(Res.Cookies != null &&  Res.Cookies.Count != 0)
{
    foreach(Cookie c in Res.Cookies)
    {
     Console.WriteLine("\t" + c.ToString());
    }
}
else
{
    Console.WriteLine("No Cookies");
|}

OK fine you might say - but what is the difference between CookieCollection and a CookieContainer?
They kind of look the same.  The difference is that the CookieCollection is the cookies obtained for the
SPECIFIC request.  CookieContainer is designed to be a store of all the cookies for one more requests.
To that extent, the CookieContainer is designed to be a hashtable of "domain - CookieCollection" pairs.

What this means is that for the next requests you create, you can safely assign the cookiecontainer from the previous
request. But then you might be wondering whether we send the cookies that we are not supposed to. Fear not.
The CookieContainer is designed to be safe.
When the second request is sent the we look at the URI and the path you are using and send only those cookies
that we can safely send as per the RFC.

Here is the complete sample for Cookies

using System;
using System.Threading;
using System.Net;
using System.Text;
using System.IO;


public class Test
{

 public static void Main(string[] args)
 {
  Stream s = null;
  StreamReader sr = null;
  HttpWebResponse Res = null;
  CookieContainer CC = new CookieContainer();
  try
  {
   //----------------------------------------------------
   //FIRST REQUEST
   //----------------------------------------------------     
   HttpWebRequest Req = (HttpWebRequest) WebRequest.Create("
http://localhost/CookieTest/first.aspx");
   Req.Proxy = null;
   Req.UseDefaultCredentials = true;   
   
   //YOU MUST ASSIGN A COOKIE CONTAINER FOR THE REQUEST TO PULL THE COOKIES
   Req.CookieContainer = CC;

   Res = (HttpWebResponse)Req.GetResponse();
  
   //DUMP THE COOKIES
   Console.WriteLine("----- COOKIES -----");   
   if(Res.Cookies != null &&  Res.Cookies.Count != 0)
   {
    foreach(Cookie c in Res.Cookies)
    {
     Console.WriteLine("\t" + c.ToString());
    }
   }
   else
   {
    Console.WriteLine("No Cookies present");    
   }
   
   
   s = Res.GetResponseStream();
   sr = new StreamReader(s, Encoding.ASCII);
   Console.WriteLine("----- RESPONSE -----");
   Console.WriteLine("\t" + sr.ReadToEnd());


   //----------------------------------------------------
   //SECOND REQUEST
   //----------------------------------------------------   

   Req = (HttpWebRequest) WebRequest.Create("http://localhost/CookieTest/second.aspx");
   Req.Proxy = null;   
   Req.UseDefaultCredentials = true;
   
   //TO TRANSFER COOKIES TO THE NEXT PAGE
   Req.CookieContainer = CC;

   Res = (HttpWebResponse)Req.GetResponse();
   
   //DUMP THE COOKIES
   Console.WriteLine("----- COOKIES -----");   
   if(Res.Cookies != null &&  Res.Cookies.Count != 0)
   {
    foreach(Cookie c in Res.Cookies)
    {
     Console.WriteLine("\t" + c.ToString());
    }
   }
   else
   {
    Console.WriteLine("No Cookies present");    
   }
   
   s = Res.GetResponseStream();
   sr = new StreamReader(s, Encoding.ASCII);
   Console.WriteLine("----- RESPONSE -----");
   Console.WriteLine("\t" + sr.ReadToEnd());
   
  }
  catch(Exception ex)
  {
   Console.WriteLine(ex);
  }
  finally
  {
   if(sr != null) sr.Close();
   if(s != null) s.Close();
  }
 }
}

Hope you find this useful. Let me know your comments
Also let me know what other topics you want me to blog about.

Just a note that I will be speaking at the PDC 2005 doing a Tips and Tricks
session on System.Net 2.0. I will be covering Tracing, X509 Certificates, Kerberos auth,
SMTP with embedded resources, etc. If there is a particular topic that if interest to you
let me know.

Durgaprasad Gorti
Test Lead
System.Net

 

 

 
Page view tracker