2
votes

I want to create virtual directory foo underneath the following existing IIS structure using Wix.

//Default website/mywebapp/editor

In the above snippet, 'mywebapp' is a web applicatoin and editor is a non-virtual folder inside that. My new virtual directory 'foo' needs to be created inside that non-virtual folder 'editor'

I am using IIS 7 and Wix 3.5.

Can the above be done using tags or do i need to write a custom action to do that?

Thanks in advance

2

2 Answers

0
votes

It's a good question.

I don't have direct experience but... when faced with a similar challenge - install an ISAPI as an IIS Extension onto a particular virtual directory using WiX - I resorted to using custom actions implemented in Javascript, and in one case, VBScript. I found that WiX had some of the things I needed, but finding the correct information was difficult, and not all IIS admin functions are exposed via WiX. Also, not all IIS Admin things are exposed to Javascript, believe it or not. The WMI interface in one case requires a VBArray. !!

Also, within the custom actions, rather than solely relying on the IIS WMI (programming) interfaces, the code sometimes invokes APPCMD.exe to do the actual work. If you pre-req IIS7 then you will have this. Creating a vdir or app with appcmd would be really simple (appcmd add app or appcmd add vdir). The hardest part, for me, was wrapping the necessary supporting Javascript and WiX code around it. Here's how I did it.

In the main product.wxs file:

<InstallExecuteSequence>
   ...
  <!-- configure extension if we need it -->
  <Custom Action="CA.AddExtension" After="InstallFiles">NOT Installed AND &amp;F.Binary = 3</Custom>
  ...
</InstallExecuteSequence>

and then there's a separate customactions.wxs file:

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Fragment>
    <Binary Id="B.JavaScript" SourceFile="CustomActions.js" />
    <Binary Id="B.VBScript" SourceFile="MoreCustomActions.vbs" />

    <CustomAction Id="CA.EnumerateWebSites"
                  BinaryKey="B.JavaScript"
                  JScriptCall="EnumerateWebSites_CA"
                  Execute="immediate"
                  Return="check" />

    <CustomAction Id="CA.AddExtension"
                  BinaryKey="B.VBScript"
                  VBScriptCall="AddExtension_CA"
                  Execute="immediate"
                  Return="check" />

     ....

And then the javascript looked like this:

function RunAppCmd(command, deleteOutput) {
    deleteOutput = deleteOutput || false;
    LogMessage("RunAppCmd("+command+") ENTER");
    var shell = new ActiveXObject("WScript.Shell");
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    var tmpdir = fso.GetSpecialFolder(SpecialFolders.TemporaryFolder);
    var tmpFileName = fso.BuildPath(tmpdir, fso.GetTempName());
    var windir = fso.GetSpecialFolder(SpecialFolders.WindowsFolder);
    var appcmd = fso.BuildPath(windir,"system32\\inetsrv\\appcmd.exe") + " " + command;

    LogMessage("shell.Run("+appcmd+")");

    // use cmd.exe to redirect the output
    var rc = shell.Run("%comspec% /c " + appcmd + "> " + tmpFileName, WindowStyle.Hidden, true);
    LogMessage("shell.Run rc = "  + rc);

    if (deleteOutput) {
        fso.DeleteFile(tmpFileName);
    }
    return {
        rc : rc,
        outputfile : (deleteOutput) ? null : tmpFileName
    };
}



// GetWebSites_Appcmd()
//
// Gets website info using Appcmd.exe, only on IIS7+ .
//
// This fn always returns site state info with each record.
//
function GetWebSites_Appcmd() {
    var ParseOneLine = function(oneLine) {
        // split the string: capture quoted strings, or a string surrounded
        // by parens, or lastly, tokens separated by spaces,
        var tokens = oneLine.match(/"[^"]+"|\(.+\)|[^ ]+/g);

        // split the 3rd string: it is a set of properties separated by colons
        var props = tokens[2].slice(1,-1);
        var t2 = props.match(/\w+:.+?(?=,\w+:|$)/g);
        var bindingsString = t2[1];
        //say(bindingsString);
        var ix1 = bindingsString.indexOf(':');
        var t3 = bindingsString.substring(ix1+1).split(',');

        var bindings = {};
        for (var i=0; i<t3.length; i++) {
            var split = t3[i].split('/');
            var obj = {};
            if (split[0] == "net.tcp") {
                var p2 = split[1].split(':');
                obj.port = p2[0];
            }
            else if (split[0] == "net.pipe") {
                var p3 = split[1].split(':');
                obj.other = p3[0];
            }
            else if (split[0] == "http") {
                var p4 = split[1].split(':');
                obj.ip = p4[0];
                if (p4[1]) {
                    obj.port = p4[1];
                }
                obj.hostname = "";
            }
            else {
                var p5 = split[1].split(':');
                obj.hostname = p5[0];
                if (p5[1]) {
                    obj.port = p5[1];
                }
            }
            bindings[split[0]] = obj;
        }

        // return the object describing the website
        return {
            id          : t2[0].split(':')[1],
            name        : "W3SVC/" + t2[0].split(':')[1],
            description : tokens[1].slice(1,-1),
            bindings    : bindings,
            state       : t2[2].split(':')[1] // started or not
        };
    };

    LogMessage("GetWebSites_Appcmd() ENTER");

    var r = RunAppCmd("list sites");
    if (r.rc !== 0) {
        // 0x80004005 == E_FAIL
        throw new Exception("ApplicationException", "exec appcmd.exe returned nonzero rc ("+r.rc+")", 0x80004005);
    }

    var fso = new ActiveXObject("Scripting.FileSystemObject");
    var textStream = fso.OpenTextFile(r.outputfile, OpenMode.ForReading);
    var sites = [];

    // Read from the file and parse the results.
    while (!textStream.AtEndOfStream) {
        var oneLine = textStream.ReadLine();
        var line = ParseOneLine(oneLine);
        LogMessage("  site: " + line.name);
        sites.push(line);
    }
    textStream.Close();
    fso.DeleteFile(r.outputfile);

    LogMessage("GetWebSites_Appcmd() EXIT");

    return sites;
}

Maybe you'll find this useful.

0
votes

Finally i found a easy solution to this. came to know that we can use relative path for the alias attribute of webvirtualdir element. So to create virtual folder foo i did the following

  1. Referred the Default website using website element

    <iis:WebSite  Id="My.Site" Description="Default Website">
      <iis:WebAddress Id="My.Web.Address" Port="12300"/>
    </iis:WebSite>
    
  2. added webvirtualdir element with alias set to mywebapp/editor/foo (webapp/subfolder/virtualdir)

    <Directory Id="TARGETDIR" Name="SourceDir">
     <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLLOCATION" Name="IISDemo">
          <Component Id="IIS.Component" Guid="{6FAD9EC7-D2B0-4471-A657-C8AF5F6F707F}" KeyPath="yes">
            <iis:WebVirtualDir Id="My.VirtualDir" Alias="mywebapp/editor/foo" Directory="INSTALLLOCATION" WebSite="My.Site">              
            </iis:WebVirtualDir>
          </Component>
        </Directory>
      </Directory>
    </Directory>
    

never expected that the solution is that simple. But meanwhile i have written a custom action using system.directoryservices to achieve the same. But this one is simpler and neat. Might be useful for someone who faces the same scenario. Thanks