CTICoder

A random spillage of programming (and other) thoughts

  • Welcome!

    Enjoy the random thoughts, but only if you really want to.
  • a

  • Archives

Archive for August 11th, 2008

MsBuild: Custom Task: Drive Mapper

Posted by Shaun McDonnell on August 11, 2008

For those not familiar with MsBuild, it is Microsoft’s Build Engine that comes with the .NET 2.0 Framework.  It is a highly flexible and highly reusable framework that makes it possible to build almost any type of software and deploy any type of product.  Personally, I think it is an excellent tool.

One common task that I have wanted to perform in MsBuild is drive mapping.  If I could have a Custom MsBuild Task that would map a drive letter to a UNC path, I could deploy my application to anywhere within the network (testing, staging, and even production).  So, recently, I set out to create that Custom MsBuild Task and here are the results and the code.

First off, in order to create an Custom MsBuild Task, you have to inherit from Microsoft.Build.Utilities.Task and then implement the bool Execute() member method required by the base class.

In order to use the Win32 functions that map and unmap drives.  We’re going to have to use Platform Invoke (P/Invoke).  The three platform methods that I use are (click on the function will take you to its documentation):

Take a look the implementations of the above methods in C#

[DllImport("mpr.dll")] public static extern int WNetAddConnection2A ( [MarshalAs(UnmanagedType.LPArray)] NETRESOURCEA[] lpNetResource, [MarshalAs(UnmanagedType.LPStr)] string lpPassword, [MarshalAs(UnmanagedType.LPStr)] string UserName, int dwFlags ); [StructLayout(LayoutKind.Sequential)] public struct NETRESOURCEA { public int dwScope; public int dwType; public int dwDisplayType; public int dwUsage; [MarshalAs(UnmanagedType.LPStr)] public string lpLocalName; [MarshalAs(UnmanagedType.LPStr)] public string lpRemoteName; [MarshalAs(UnmanagedType.LPStr)] public string lpComment; [MarshalAs(UnmanagedType.LPStr)] public string lpProvider; public override string ToString() { string str = "LocalName: " + lpLocalName + " RemoteName: " + lpRemoteName + " Comment: " + lpComment + " lpProvider: " + lpProvider; return (str); } } [DllImport("mpr.dll")] public static extern int WNetCancelConnection2A ( [MarshalAs(UnmanagedType.LPStr)] string lpName, int dwFlags, bool fForce ); [DllImport("Kernel32.dll")] public static extern int FormatMessage ( int dwFlags, IntPtr lpSource, int dwMessageID, int dwLanguageID, StringBuilder lpBuffer, int nSize, IntPtr arguments );

 

Together, all of these methods will do the following:

  • Map a Drive
  • Unmap a Drive
  • Display an Error Message returned by the operating system if the map fails.

Now that we have all of these methods in place, we need to create properties to be used within the MsBuild Xml file for our task.  Here is the code for those properties:

[Required] public bool RemoveIfExists { get { return _RemoveIfExists; } set { _RemoveIfExists = value; } } [Required] public string Path { get { return _Path; } set { _Path = value; } } [Required] public string DriveLetter { get { return _DriveLetter; } set { _DriveLetter = value; } }

Then, we need to have some methods that will make calling the external methods above a little bit smoother:

protected int MapDrive() { NETRESOURCEA[] resources = new NETRESOURCEA[1]; resources[0] = new NETRESOURCEA(); resources[0].dwType = 1; int dwFlags = 1; resources[0].lpLocalName = _DriveLetter; resources[0].lpRemoteName = _Path; resources[0].lpProvider = null; int ret = WNetAddConnection2A(resources, null, null, dwFlags); return ret; } protected int UnmapDrive() { int ret = WNetCancelConnection2A(_DriveLetter, 0, true); return ret; }

Lastly, we need to implement the execute method because it is the method that will be executed by MsBuild:

public override bool Execute() { int ret = MapDrive(); int ret3 = 0; bool success = false; StringBuilder message = new StringBuilder(500); int messageId = FormatMessage(4096, IntPtr.Zero, ret, 0, message, 500, IntPtr.Zero); Log.LogMessage("Format Message Result: {0}", new object[] { ret }); Log.LogMessage("Mapping Result: {0}", new object[] { message.ToString() }); if (ret == 85 && RemoveIfExists) { int ret2 = UnmapDrive(); StringBuilder unmapMessage = new StringBuilder(500); FormatMessage(4096, IntPtr.Zero, ret2, 0, unmapMessage, 500, IntPtr.Zero); Log.LogMessage("Format Message Result: {0}", new object[] { ret2 }); Log.LogMessage("Unmapping Result: {0}", new object[] { unmapMessage.ToString() }); ret3 = MapDrive(); StringBuilder mapMessage = new StringBuilder(500); int mapMessageId = FormatMessage(4096, IntPtr.Zero, ret3, 0, mapMessage, 500, IntPtr.Zero); Log.LogMessage("Format Message Result: {0}", new object[] { ret3 }); Log.LogMessage("Second Mapping Result: {0}", new object[] { mapMessage.ToString() }); success = !(ret3 > 85); } else { success = !(ret > 0); } return success; }

Here’s a look at all of the code combined:

using System; using System.Collections.Generic; using System.Text; using Microsoft.Build.Utilities; using Microsoft.Build.Framework; using System.Runtime.InteropServices; namespace Cti.MsBuild.Tasks { [StructLayout(LayoutKind.Sequential)] public struct NETRESOURCEA { public int dwScope; public int dwType; public int dwDisplayType; public int dwUsage; [MarshalAs(UnmanagedType.LPStr)] public string lpLocalName; [MarshalAs(UnmanagedType.LPStr)] public string lpRemoteName; [MarshalAs(UnmanagedType.LPStr)] public string lpComment; [MarshalAs(UnmanagedType.LPStr)] public string lpProvider; public override string ToString() { string str = "LocalName: " + lpLocalName + " RemoteName: " + lpRemoteName + " Comment: " + lpComment + " lpProvider: " + lpProvider; return (str); } } public class MapDriveTask : Microsoft.Build.Utilities.Task { protected string _Path = String.Empty; protected string _DriveLetter = String.Empty; protected bool _RemoveIfExists = false; [DllImport("mpr.dll")] public static extern int WNetAddConnection2A ( [MarshalAs(UnmanagedType.LPArray)] NETRESOURCEA[] lpNetResource, [MarshalAs(UnmanagedType.LPStr)] string lpPassword, [MarshalAs(UnmanagedType.LPStr)] string UserName, int dwFlags ); [DllImport("mpr.dll")] public static extern int WNetCancelConnection2A ( [MarshalAs(UnmanagedType.LPStr)] string lpName, int dwFlags, bool fForce ); [DllImport("Kernel32.dll")] public static extern int FormatMessage ( int dwFlags, IntPtr lpSource, int dwMessageID, int dwLanguageID, StringBuilder lpBuffer, int nSize, IntPtr arguments ); [Required] public bool RemoveIfExists { get { return _RemoveIfExists; } set { _RemoveIfExists = value; } } [Required] public string Path { get { return _Path; } set { _Path = value; } } [Required] public string DriveLetter { get { return _DriveLetter; } set { _DriveLetter = value; } } protected int MapDrive() { NETRESOURCEA[] resources = new NETRESOURCEA[1]; resources[0] = new NETRESOURCEA(); resources[0].dwType = 1; int dwFlags = 1; resources[0].lpLocalName = _DriveLetter; resources[0].lpRemoteName = _Path; resources[0].lpProvider = null; int ret = WNetAddConnection2A(resources, null, null, dwFlags); return ret; } protected int UnmapDrive() { int ret = WNetCancelConnection2A(_DriveLetter, 0, true); return ret; } public override bool Execute() { int ret = MapDrive(); int ret3 = 0; bool success = false; StringBuilder message = new StringBuilder(500); int messageId = FormatMessage(4096, IntPtr.Zero, ret, 0, message, 500, IntPtr.Zero); Log.LogMessage("Format Message Result: {0}", new object[] { ret }); Log.LogMessage("Mapping Result: {0}", new object[] { message.ToString() }); if (ret == 85 && RemoveIfExists) { int ret2 = UnmapDrive(); StringBuilder unmapMessage = new StringBuilder(500); FormatMessage(4096, IntPtr.Zero, ret2, 0, unmapMessage, 500, IntPtr.Zero); Log.LogMessage("Format Message Result: {0}", new object[] { ret2 }); Log.LogMessage("Unmapping Result: {0}", new object[] { unmapMessage.ToString() }); ret3 = MapDrive(); StringBuilder mapMessage = new StringBuilder(500); int mapMessageId = FormatMessage(4096, IntPtr.Zero, ret3, 0, mapMessage, 500, IntPtr.Zero); Log.LogMessage("Format Message Result: {0}", new object[] { ret3 }); Log.LogMessage("Second Mapping Result: {0}", new object[] { mapMessage.ToString() }); success = !(ret3 > 85); } else { success = !(ret > 0); } return success; } } }

 

If you compile this code, you can then use this task in your MsBuild Xml file like so:

<?xml version="1.0" encoding="utf-8" ?> <Project DefaultTargets="MyTarget" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <UsingTask AssemblyFile="Cti.MsBuild.Tasks.dll" TaskName="Cti.MsBuild.Tasks.MapDriveTask" /> <Target Name="MyTarget"> <MapDriveTask Path="\\coke\Projects" DriveLetter="Z:" RemoveIfExists="true" /> </Target> </Project>

There you have it.  MsBuild is going to make life a whole alot easier when it comes to building, compiling, and deploying.

Posted in MsBuild | 1 Comment »

Annoying <Script> Tag Bug When Using Client-Side Web Services

Posted by Shaun McDonnell on August 11, 2008

This one took me hours to figure out and there is no rhyme or reason to it.  Yet, I have been able to reproduce it consistently so I thought I would share the details here.

Microsoft .NET 3.5 introduced a new Attribute called [ScriptService] that we can put at the top of our WCF Services or our ASMX Web Services.  The addition of this attribute to a service allows for that service to be called from the client-side of a web application using javascript.

 [ScriptService] public class Service : System.Web.Services.WebService 

Behind the scenes, .NET creates some javascript and it is automatically embedded in your code.  You can actually see the javascript it creates by just adding a ‘/js’ to the end of the ASMX url like this:

http://services.customers.ctiusa.com/cisco/ipphoneservice.asmx/js

If you want to use the client-side accessible web service from a pure HTML page (or something else that isn’t .NET) you can actually make a direct reference to that javascript path like this:

 <script language="javascript" src="http://services.customers.ctiusa.com/cisco/ipphoneservice.asmx/js" type="text/javascript">

However, this won’t work and the browser won’t be able to find the javascript reference.  Why?  Because it doesn’t like ‘/>’ for ending the script tag.  It wants the full ‘ ‘ like this:

 <script language="javascript" src="http://services.customers.ctiusa.com/cisco/ipphoneservice.asmx/js" type="text/javascript">  

Once you do that, you will be able to access your web service through pure asynchronous javascript calls.

-Shaun

 

Posted in .NET, Bugs, Tips | 1 Comment »