October 2006 - Posts

One month after the start of the Daily Blogging Mission I'm happy to see I survived yet another month: "a post a day keeps the ... away". And again the comments and feedback have been great, so thanks everyone for the active participation. Furthermore, I'm proud to be re-elected as an MVP on Visual C#. Needless to say, the Daily Blogging Mission is part of my commitment for this MVP year. But the community world isn't only about acronyms of course: it's great to obtain the new string(new char[] {0x4D, 0x56, 0x50}) title, but the more important thing is to spread the word and help people in discovering new technologies and working with existing ones. Last but not least, I want to thank the people at Microsoft Belux to put me in the spotlight on www.msdn.be last month.

Some highlights of last month include another series of blogposts about Windows Workflow Foundation:

I hope you really enjoyed reading these articles as much as I did in writing it. Beside my personal interest in .NET FX 3.0 (and WF as one of the core pillars), the big stimulans to write these kind of lengthy tutorial fashioned posts is my thesis research activity at UGent on dynamic and generic workflows. This month I reached a next milestone in this research, dealing with dynamic workflow instrumentation and various performance-related tests. I can't tell very much about it yet, but stay tuned for regular updates.

Technically, there has been an upgrade of my blog to CommunityServer 2.1 (better late than never). Once more a decent job of Telligent with this release. And not to forget: Internet Explorer 7 has been released last month, so allow me to congratulate the IE team with this release. Check out the Channel 9 "Internet Explorer 7: The Browser. The Team. The Tour." series of posts.

Next month, blogging will continue on the same basis but with a slight difference: from November 5 to November 18 I'll be in Barcelona for Microsoft TechEd Europe. It's my goal to report about TechEd adventures during these two weeks, instead of the typical technical daily deferred posts. However, deferred posts are already online for the weekends, i.e. November 5, 11, 12 and 18. Nevertheless, lots of great stuff will be presented at TechEd and you'll hear from it through this blog as well as through the Virtual Side. During TechEd, I'll be on the Ask The Experts booth (schedule will be posted later on), so feel free to jump in for questions or a chat. And make sure to come and see me at Speaker Idol too for a 5 minutes presentation :-). For the first round at TechEd Developers, I'll be on stage on November 8th somewhere between 12:30 and 13:30. And for TechEd IT Forum my first round presentation will take place on November 14th in the timeslot from 19:00 to 20:00.

Thanks to everyone for reading my blog. More cool stuff soon!

Bart De Smet - 10/23/06 - 8:14 PM

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Tons of great stuff once more: LINQ, ADO.NET 3.0, multi-targeting and much more. Here's the link to download the VPC image: http://www.microsoft.com/downloads/details.aspx?FamilyId=C09B5A2D-EB6A-44B6-8BBD-3764A2FDA9CE&displaylang=en. Download in progress.

Enjoy and keep and eye on this blog; somewhere after TechEd Europe I'll most likely start to blog about these technologies.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Okay, so here's the code of the C# Quiz presented a couple of days ago:

using System;

public abstract class
Foo
{
   static
Foo()
   {
      Console.WriteLine(".cctor Foo"
);
   }

   public static string
Instance;

   public static void SetInstance(string
s)
   {
      Instance = s;
   }
}

public class
Bar : Foo
{
   static
Bar()
   {
      Console.WriteLine(".cctor Bar"
);
      SetInstance(
"Hello World!"
);
   }
}

public class
Demo
{
   public static void
Main()
   {
      Console.WriteLine(Bar.Instance ?? "Oops"
);
   }
}

System;

public abstract class
Foo
{
   static
Foo()
   {
      Console.WriteLine(".cctor Foo"
);
   }

   public static string
Instance;

   public static void SetInstance(string
s)
   {
      Instance = s;
   }
}

public class
Bar : Foo
{
   static
Bar()
   {
      Console.WriteLine(".cctor Bar"
);
      SetInstance(
"Hello World!"
);
   }
}

public class
Demo
{
   public static void
Main()
   {
      Console.WriteLine(Bar.Instance ?? "Oops"
);
   }
}

And here's the output:

.cctor Foo
Oops

Maybe you didn't expect this; that's the whole point of this kind of quizzes after all. A typical reasoning goes as follows:

Demo::Main contains Bar::Instance => Bar::.cctor => “.cctor Bar”
Bar::.cctor bevat Foo::SetInstance => Foo::.cctor => “.cctor Foo”

So, we'd expect:

.cctor Bar
.cctor Foo
Hello World!

Be prepared to be shocked (maybe): the Bar.Instance call in Demo::Main has nothing to do with Bar after all. It's just some kind of "syntactical sugar" introduced by C#. After all, Bar derives from Foo, isn't it? However, Bar.Instance is nothing more or less than Foo.Instance. Look at the IL to confirm this:

  IL_0000:  nop
  IL_0001:  ldsfld     string Foo::Instance
  IL_0006:  dup
  IL_0007:  brtrue.s   IL_000f
  IL_0009:  pop
  IL_000a:  ldstr      "Oops"
  IL_000f:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0014:  nop
  IL_0015:  ret

As the matter in fact, the Bar class is out of play. Its static constructor won't ever be called by the runtime when executing this peice of code. You can verify this by executing ildasm.exe /out:demo.il demo.exe, dropping the Bar portion from it, and re-ilasming the stuff using ilasm.exe demo.il. Everything will just work fine. Another experiment is to change IL_0001 into ldsfld string Bar::Instance and re-compile using ilasm. When executing the executable, you'll get a MissingMethodException thrown in your face.

In the CLI (I.8.9.5) one can read:

3. If marked BeforeFieldInit, then the type’s initializer method is executed at, or sometime before, first access to any static field defined for that type.
4. If not marked BeforeFieldInit then that type’s initializer method is executed at (i.e., is triggered by):
- First access to any static or instance field of that type, or
- First invocation of any static, instance, or virtual method of that type

The BeforeFieldInit portion is not relevant in here and refers to a metadata token on the class-level that allows semantic relaxation to increase performance by dropping timing guarantees for execution of the static constructor (as long as it happens before the first access of a static field) as mentioned in 3.

More important is that there's no reason to call the static constructor based on the points mentioned above (more specifically in 4 because BeforeFieldInit won't be set by the C# compiler upon compilation of the code fragment).

There are two solutions to the problem. Solution 1 consists of adding a static field to the Bar class and using it in the Main method. This will cause the runtime to invoke the type initializer (.cctor) in Bar (see CLI I.8.9.5 for the reasons):

using System;

public abstract class
Foo
{
   static
Foo()
   {
      Console.WriteLine(".cctor Foo"
);
   }

   public static string
Instance;

   public static void SetInstance(string
s)
   {
      Instance = s;
   }
}

public class
Bar : Foo
{
   internal static int
junk = 0;

   static
Bar()
   {
      Console.WriteLine(".cctor Bar"
);
      SetInstance(
"Hello World!"
);
   }
}

public class
Demo
{
   public static void
Main()
   {
      int
junk = Bar.junk;
      Console.WriteLine(Bar.Instance ?? "Oops"
);
   }
}

System;

public abstract class
Foo
{
   static
Foo()
   {
      Console.WriteLine(".cctor Foo"
);
   }

   public static string
Instance;

   public static void SetInstance(string
s)
   {
      Instance = s;
   }
}

public class
Bar : Foo
{
   internal static int
junk = 0;

   static
Bar()
   {
      Console.WriteLine(".cctor Bar"
);
      SetInstance(
"Hello World!"
);
   }
}

public class
Demo
{
   public static void
Main()
   {
      int
junk = Bar.junk;
      Console.WriteLine(Bar.Instance ?? "Oops"
);
   }
}

Solution 2 is more sexy and uses a RuntimeHelper to call the static constructor, as shown below:

using System;

public abstract class
Foo
{
   static
Foo()
   {
      Console.WriteLine(".cctor Foo"
);
   }

   public
static string Instance;

   public static void SetInstance(string
s)
   {
      Instance = s;
   }
}

public class
Bar : Foo
{
   static
Bar()
   {
      Console.WriteLine(".cctor Bar"
);
      SetInstance(
"Hello World!"
);
   }
}

public class
Demo
{
   public static void
Main()
   {
      System.Runtime.CompilerServices.
RuntimeHelpers.RunClassConstructor(typeof
(Bar).TypeHandle);
      Console.WriteLine(Bar.Instance ?? "Oops"
);
   }
}

System;

public abstract class
Foo
{
   static
Foo()
   {
      Console.WriteLine(".cctor Foo"
);
   }

   public
static string Instance;

   public static void SetInstance(string
s)
   {
      Instance = s;
   }
}

public class
Bar : Foo
{
   static
Bar()
   {
      Console.WriteLine(".cctor Bar"
);
      SetInstance(
"Hello World!"
);
   }
}

public class
Demo
{
   public static void
Main()
   {
      System.Runtime.CompilerServices.
RuntimeHelpers.RunClassConstructor(typeof
(Bar).TypeHandle);
      Console.WriteLine(Bar.Instance ?? "Oops"
);
   }
}

RuntimeHelpers.RunClassConstructor does ask the runtime to call the static constructor anyway, regardless of the reasons specified in the CLI. This technique is employed by the Delphi .NET compilers to ensure deterministic unit initialization order.

The conclusion? Static constructors (or type initializers or .cctors, whatever name you like to give it) are tricky things. Don't get fooled by them.

kick it on DotNetKicks.com

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

A little nice trick (on Windows XP and later) to navigate more quickly through folders is the use of pushd and popd. The names should ring the stack bell almost immediately. The commands are nothing more and nothing less than the cd command with stack support:

This one is on my demo tips-n-tricks list for quite a while and causes a bit of noise in the audience (depending on the geek level). Others include:

  • =rand() in Word to generate junk text
  • CTRL+click multiple items in the taskbar, right click and choose some option to arrange the windows
  • TAB completion in cmd.exe

amongst many others.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Introduction

Assume you're writing some application that really - I mean really really - needs administrative privileges (does it? you must be kidding). So, you want it to run evelated as an administrator. How to tackle this requirement in a Windows Vista and UAC world where even an administrator is locked down on the machine?

This post shows you how to create an application manifest for your .NET application (I'll be using C# for that purpose) that tells Vista to run the application evelated.

The application

Creating the Windows Forms app

Create a simple Windows Forms application called UacDemo. On Form1.cs add a single label called label1, and add the following line to the Form_Load event handler:

label1.Text = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) ? "Yup" : "Nope";

new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) ? "Yup" : "Nope";

This time I'm assuming you're an administrator on the machine. Launch the application and see how "Nope" stares you in the face telling you're not as powerful as you thought you were:

Creating a manifest

In order to make the application support elevation, we need to define a manifest for the application. It's just a simple XML file with the following in it:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<
assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"

   <
assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="UacDemo" type="win32"
/>
      <
trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"
>
      <
security
>
         <
requestedPrivileges
>
            <
requestedExecutionLevel level="requireAdministrator"
/> 
         </
requestedPrivileges
>
      </
security
>
   </
trustInfo
>
</
assembly>

Add a file called UacDemo.exe.manifest to the project and copy/paste the XML chunk in there.

Adding the manifest to the executable

The last thing to do is to embed the manifest in the executable as a Win32Resource. An embedded resource is VS2005 can't be used for this. It's a little more complex than this. With the SDK, a tool called mt.exe ships that can be used to manage manifests in executables (for gurus, perform a dumpbin on the .exe file before and after the execution of mt.exe to see the application manifest being copied to the file). We'll be invoking this tool as a post-build step by going to the project properties, tab Build Events and pasting the following in Post-build even command line:

"$(DevEnvDir)..\..\SDK\v2.0\bin\mt.exe" -manifest "$(ProjectDir)$(TargetName).exe.manifest"  –outputresource:"$(TargetDir)$(TargetFileName)";#1

This looks as follows:

The test

Compile the project and go to the bin\Debug folder in Windows Explorer. Notice the application is now displayed with a security shield icon on it:

Let's try to run it. This should bring up the following prompt:

Now we are elevated and we finally see the happy "Yup" word:

Conclusion

So, now you now how to make your app UAC ready when it needs administrative rights and privileges. However, it's always better to avoid this level of rights and privileges. As usual: think before you do!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Time for another C# Quiz. Thanks to R.L. to provide me with the problem via mail.

Question: What will the following code print on the screen?

Answer: Will appear on October 30 at 6:00 PM (GMT+1) on this blog.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Introduction

End of October 2006, the PowerShell release is coming closer day after day. Time for a series of lightning posts with small yet useful tips for developers targeting PowerShell with custom providers, cmdlets, etc. After reading this series, there are no excuses left for not taking on the PowerShell challenge :-).

In this first post, you'll learn how to parameterize cmdlets in a structed way.

Code

Assume you have a cmdlet called Bar that processes Foo objects:

class Foo
{
   private int
id;

   public int
Id
   {
      get { return
id; }
      set { id = value
; }
   }

   private string
name;

   public string
Name
   {
      get { return
name; }
      set { name = value
; }
   }

   //
   // Other properties and class members omitted.
   //
}

Assume our cmdlet is declared as follows:

[Cmdlet("Process", "Foo", DefaultParameterSetName = "FooId")]
public class StopProcCommand :
PSCmdlet
{
}

The DefaultParameterSetName property will be explained in a minute.

There are many ways to provide Foo objects to the cmdlet. One way is to pass the objects (e.g. obtained through some get-foo cmdlet) directly via the pipeline:

private Foo[] foos;

[
Parameter(
   ParameterSetName =
"InputObject"
,
   Mandatory =
true
,
   ValueFromPipeline =
true

)]
public
Foo[] InputObject
{
   get { return
processNames; }
   set { processNames = value
; }
}

Another way is to allow users to specify the names of the Foo objects that have to be processed:

private string[] names;

[
Parameter(
   ParameterSetName =
"FooName"
,
   Mandatory =
true
,
   ValueFromPipelineByPropertyName =
true
,
   ValueFromPipeline =
true
)]
public string
[] Name
{
   get { return
names; }
   set { names = value
; }
}

Or what about the identifiers:

private int[] ids;

[Parameter
(
   ParameterSetName =
"FooId"
,
   Mandatory =
true
,
   ValueFromPipelineByPropertyName =
true
,
   ValueFromPipeline =
true
)]
public int
[] Id
{
   get { return
ids; }
   set { ids = value
; }
}

All these parameters have one thing in common: the use of a ParameterSetName property. The idea is simple: each "parameter set" represents a different way to pass parameters to the cmdlet. On invocation, such a parameter set is chosen (e.g. by invoking process-foo -Name ... or implicitly by using the pipeline). Afterwards, you can find out about the parameter set being used by calling the ParameterSetName property of the cmdlet itself:

protected override void ProcessRecord()
{
   switch
(this.ParameterSetName)
   {
      case "InputObject":
         // Use foos
         break
;
      case "FooName"
:
          
// Use names
         break;
      case "FooId"
:
         // Use ids
         break
;
      default
:
         throw new ArgumentException("Invalid parameter set."
);
   }
}

This way you can allow different types of invocations of a cmdlet, clearly distinguishing different parameter sets. The DefaultParameterSet is used whenever the PowerShell shell cannot disambiguate a parameter that is set based on the command-line input (a fall-back mechanism that is).

Enjoy!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Introduction

Did I tell you already how great Windows Vista is? I guess so. Today, I'm showing you yet another piece of Windows Vista API goodness: ShutdownBlockReasonCreate. The goal of this function (defined in user32.dll) is to block system shutdown with a friendly message. This is handy if your application is doing a critical operation, such as writing to a cd or dvd. But I guess you can find other good reasons too. Check out the Application Shutdown Changes in Windows Vista article on MSDN too.

The code

Window messages

In order to understand what's happening, you need to know a little bit about window messages. Basically, the OS sends messages to an application to tell it about mouse moves, etc. In the winuser.h file (that comes with the Windows SDK) you can find all sorts of window events, prefixed by WM_. Two of these messages are WM_QUERYENDSESSION and WM_ENDSESSION. A few definitions:

The WM_QUERYENDSESSION message is sent when the user chooses to end the session or when an application calls one of the system shutdown functions. If any application returns zero, the session is not ended. The system stops sending WM_QUERYENDSESSION messages as soon as one application returns zero.

The WM_ENDSESSION message is sent to an application after the system processes the results of the WM_QUERYENDSESSION message. The WM_ENDSESSION message informs the application whether the session is ending.

The former window message is interesting to us because it allows to avoid shutdown: if any application returns zero, the session is not ended. In the past, this would lead to a situation where Windows asks to kill the applications ("not responding"). In Windows Vista, one can supply shutdown blocking reasons which are displayed by Windows Vista is a user-friendly way.

Capturing window messages

First I need to show you how to capture window messages in a Windows Forms app. Assume a one-form Windows Forms application and go to Form1.cs. In there (or more generally, in the app's main window), override the WndProc method:

private bool blocked = false;

protected override void WndProc(ref Message aMessage)
{
   const int
WM_QUERYENDSESSION = 0x0011;
   const int
WM_ENDSESSION = 0x0016;

   if
(blocked && (aMessage.Msg == WM_QUERYENDSESSION || aMessage.Msg == WM_ENDSESSION))
      return
;

   base.WndProc(ref
aMessage);
}

bool blocked = false;

protected
override void WndProc(ref Message aMessage)
{
   const int
WM_QUERYENDSESSION = 0x0011;
   const int
WM_ENDSESSION = 0x0016;

   if
(blocked && (aMessage.Msg == WM_QUERYENDSESSION || aMessage.Msg == WM_ENDSESSION))
      return
;

   base.WndProc(ref
aMessage);
}

Don't forget the base call; otherwise Win32Exceptions will occur and the form won't even be created.

What this code does is blocking message forwarding (by the base call) when the WM_QUERYENDSESSION (or WM_ENDSESSION) message is received and the blocking is turned on (by means of a variable blocked). Let's see when we turn this variable on now.

A shutdown blocking reason

Add the following controls to the form of our demo application:

  • TextBox txtBox1 - used to supply a shutdown reason
  • Button button1 with caption "Block" - used to turn on shutdown blocking
  • Button button1 with caption "Unblock" - used to turn off shutdown blocking

The result looks like this:

On to the code:

private void button1_Click(object sender, EventArgs e)
{
   if (ShutdownBlockReasonCreate(this
.Handle, textBox1.Text))
   {
      blocked =
true
;
      MessageBox.Show("Shutdown blocking succeeded"
);
   }
   else
      MessageBox.Show("Shutdown blocking failed"
);
}

private void button2_Click(object sender, EventArgs
e)
{
   if (ShutdownBlockReasonDestroy(this
.Handle))
   {
      blocked =
false
;
      MessageBox.Show("Shutdown unblocking succeeded"
);
   }
   else
      MessageBox.Show("Shutdown unblocking failed"
);
}

void button1_Click(object sender, EventArgs e)
{
   if (ShutdownBlockReasonCreate(this
.Handle, textBox1.Text))
   {
      blocked =
true
;
      MessageBox.Show("Shutdown blocking succeeded"
);
   }
   else
      MessageBox.Show("Shutdown blocking failed"
);
}

private void button2_Click(object sender, EventArgs
e)
{
   if (ShutdownBlockReasonDestroy(this
.Handle))
   {
      blocked =
false
;
      MessageBox.Show("Shutdown unblocking succeeded"
);
   }
   else
      MessageBox.Show("Shutdown unblocking failed"
);
}

Two Windows Vista API functions are called:

[DllImport("user32.dll")]
public extern static bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string
pwszReason);

[
DllImport("user32.dll"
)]
public extern static bool ShutdownBlockReasonDestroy(IntPtr hWnd);

DllImport("user32.dll")]
public extern static bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string
pwszReason);

[
DllImport("user32.dll"
)]
public extern static bool ShutdownBlockReasonDestroy(IntPtr hWnd);

The first one is used to supply a friendly shutdown blocking message. The second one is used to destroy it again. The first parameter to both functions is the window handle of the main window.

Tip: Don't forget to set the marshal type of the pwszReason parameter, otherwise you'll get Chinese-looking characters.

The demo

Compile the application in a Release build mode. Now run it, type a shutdown reason, and click Block:

Now try to log off the machine. The screen will go dark and the shutdown blocking reason will be displayed (click to enlarge):

(Don't worry about the SysFader thing, that's an issue related to Nvidia stuff on my machine.)

If you click Unblock again and then try to log off, the app won't block the operation anymore.

Conclusion

Yet another great Windows Vista feature, but use it with care. Only if your app can't afford to be shut down in the middle of some critical operation, you should consider to use this API. In fact, the ShutdownBlockReason* set of API functions is not required to get this to work. You can just choose not to process the WM_QUERYENDSESSION window message and things will be blocked. However, a user-friendly message yields a better end-user experience and that's where ShutdownBlockReasonCreate comes into play.

Have fun (once more)!

kick it on DotNetKicks.com

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Although the site is online for quite a while, I'd like to point fellow-geeks from Belgium and Luxemburg (free bus transfer to Brussels available) to it: http://www.readyforanewday.be. If you're not attending TechEd, this might be something for you. Attend the launch event for 2007 Office System, Windows Vista, Exchange 2007 and exciting Windows Live stuff on November 9th at Brussels (Heyzel). The closing keynote speaker is Bill Gates. With over 5000 attendees registered already, act now as seats are limited!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Windows Vista has support for symbolic links, and of course the API does too. Support for hard links has been there since the Windows 2000 days and won't be covered here.

Here's a piece of sample code:

using System;
using
System.Runtime.InteropServices;
using
System.IO;

namespace
mklink
{
   class
Program
   {
      [
DllImport("kernel32.dll"
)]
      static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int
dwFlags);

      static int SYMLINK_FLAG_DIRECTORY = 1;

      static void Main(string
[] args)
      {
         //
         // Symbolic file link bar.txt <<===>> foo.txt
         //
         string symF = "bar.txt"
;
         string targetF = "foo.txt"
;

         Console.WriteLine(">echo \"Hello World\" > {0}"
, targetF);
         StreamWriter sw = File
.CreateText(targetF);
         sw.WriteLine(
"Hello World"
);
         sw.Close();
         Console
.WriteLine();

         Console.WriteLine(">mklink {0} {1}"
, symF, targetF);
         if
(CreateSymbolicLink(symF, targetF, 0))
            Console.WriteLine("symbolic link created for {0} <<===>> {1}"
, symF, targetF);
         Console
.WriteLine();

         Console.WriteLine(">type {0}"
, targetF);
         Console.WriteLine(File
.ReadAllText(targetF));
         Console
.WriteLine();

         //
         // Symbolic directory link bar <<===>> foo
         //
         string symD = "bar"
;
         string targetD = "foo"
;

         Console.WriteLine(">mkdir {0}"
, targetD);
         Directory
.CreateDirectory(targetD);
         Console
.WriteLine();

         Console.WriteLine(">echo \"Hello World\" > {0}\\demo.txt"
, targetD);
         StreamWriter sw2 = File.CreateText(targetD + \\demo.txt
);
         sw2.WriteLine(
"Hello World"
);
         sw2.Close();
         Console
.WriteLine();

         Console.WriteLine(">mklink /d {0} {1}"
, symD, targetD);
         if
(CreateSymbolicLink(symD, targetD, SYMLINK_FLAG_DIRECTORY))
            Console.WriteLine("symbolic link created for {0} <<===>> {1}"
, symD, targetD);
         Console
.WriteLine();

         Console
.WriteLine(">dir {0}", targetD);
         foreach (string f in Directory
.GetFiles(targetD))
            Console
.WriteLine(f);
      }
   }
}

System;
using
System.Runtime.InteropServices;
using
System.IO;

namespace
mklink
{
   class
Program
   {
      [
DllImport("kernel32.dll"
)]
      static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int
dwFlags);

      static int SYMLINK_FLAG_DIRECTORY = 1;

      static void Main(string
[] args)
      {
         //
         // Symbolic file link bar.txt <<===>> foo.txt
         //
         string symF = "bar.txt"
;
         string targetF = "foo.txt"
;

         Console.WriteLine(">echo \"Hello World\" > {0}"
, targetF);
         StreamWriter sw = File
.CreateText(targetF);
         sw.WriteLine(
"Hello World"
);
         sw.Close();
         Console
.WriteLine();

         Console.WriteLine(">mklink {0} {1}"
, symF, targetF);
         if
(CreateSymbolicLink(symF, targetF, 0))
            Console.WriteLine("symbolic link created for {0} <<===>> {1}"
, symF, targetF);
         Console
.WriteLine();

         Console.WriteLine(">type {0}"
, targetF);
         Console.WriteLine(File
.ReadAllText(targetF));
         Console
.WriteLine();

         //
         // Symbolic directory link bar <<===>> foo
         //
         string symD = "bar"
;
         string targetD = "foo"
;

         Console.WriteLine(">mkdir {0}"
, targetD);
         Directory
.CreateDirectory(targetD);
         Console
.WriteLine();

         Console.WriteLine(">echo \"Hello World\" > {0}\\demo.txt"
, targetD);
         StreamWriter sw2 = File.CreateText(targetD + \\demo.txt
);
         sw2.WriteLine(
"Hello World"
);
         sw2.Close();
         Console
.WriteLine();

         Console.WriteLine(">mklink /d {0} {1}"
, symD, targetD);
         if
(CreateSymbolicLink(symD, targetD, SYMLINK_FLAG_DIRECTORY))
            Console.WriteLine("symbolic link created for {0} <<===>> {1}"
, symD, targetD);
         Console
.WriteLine();

         Console
.WriteLine(">dir {0}", targetD);
         foreach (string f in Directory
.GetFiles(targetD))
            Console
.WriteLine(f);
      }
   }
}

The code is pretty self-explanatory. Compile it and execute it in an empty folder. The following directory structure will be created:

C:.
│   foo.txt

└───bar
        demo.txt

Next, symbolic links are created, yielding the following result:

C:.
│   bar.txt
│   foo.txt

├───bar
│       demo.txt

└───foo
        demo.txt

Indicated in red are the symbolic links: one for a file and another one for a directory. Below, the dir output and the Windows Explorer view are displayed:

Check out the mklink command in Vista too:

Enjoy!

kick it on DotNetKicks.com

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

More Posts Next page »