West-Wind Web Connection requires defining, in IIS, script-mapped extensions that execute without checking the existence of a file or folder; this post explains how to achieve that with a JavaScript script executing in Windows Script Host.

In most IIS installs, when creating a script-mapped handler, one gets this default setting: IIS: default 'resourceType' setting when defining a script-mapped handler

As this setting does not work for West-Wind Web Connection, when defining a handler manually, one just need to un-check the box at the top: IIS: 'resourceType' adjusted to 'Undefined'

However, when installing a Web application on a server programmatically, you need a programmatic way to do the same operation. We had to cope with this issue to install the FoxInCloud sample applications on the developer’s machine using InstallShield Professional 2015 (ISP).

Using ISP we can create applications within any site on the target machine (extent or new), such as the ‘Default Web Site’ mapped to 127.0.0.1:80, alias localhost: InstallShield Professional 2015: installing an application in the Default Web Site

However, when defining a handler – aka script-mapped extension – ISP no longer cares for the ‘check if file exists’ setting on IIS>6, thus defaulting to the dreaded ‘check that file exists’, not suitable for a West-Wind Web Connection application installation: InstallShield Professional 2015 sets resourceType to default

Fortunately, ISP supports ‘custom actions’ that installation author can define and decide to execute at any step in the installation process: ISP custom action pane

We decided to execute, just after IIS applications have installed successfully, the following JavaScript in the Windows Script Host context:

var junk
, adminManager = new ActiveXObject('Microsoft.ApplicationHost.WritableAdminManager')
, sitesSection = adminManager.GetAdminSection("system.applicationHost/sites", "MACHINE/WEBROOT/APPHOST")
, site = sitesSection.Collection.Item(0) /* alias "Default Web Site" */
, XMLparser = new ActiveXObject('MSXML.DOMDocument'), oXML
, fso = WScript.createObject('Scripting.FileSystemObject'), file, stream
, application, virtual
, handlers, handler, handlerChanged
, scriptProcessor
;

for (var i = 0; i < site.Collection.count; i++) {

  application = site.Collection.Item(i);
  if (application.Name === 'application' && application.Properties.Item('path').Value.length > 1){
    for (var j = 0; j < application.Collection.count; j++) {

      virtual = application.Collection.Item(j);
      if (virtual.Name === "virtualDirectory" && virtual.Properties.Item('path').Value.length == 1){

        /* Update web connection handlers in virtual's web.config*/
        file = virtual.Properties.Item('physicalPath').Value;
        file = file + (file.substr(file.length-1) === '\\' ? '' : '\\') + "web.config";
        if (fso.FileExists(file)){

          stream = fso.OpenTextFile(file, 1);
          if (!stream.AtEndOfStream && XMLparser.loadXML(stream.ReadAll())){

            handlers = XMLparser.documentElement;
            if (handlers){

              handlers = handlers.getElementsByTagName('system.webServer');
              if (handlers.length){

                handlers = handlers[0];
                handlers = handlers.getElementsByTagName('handlers');
                if (handlers.length){

                  handlers = handlers[0];
                  handlerChanged = false;
                  for (var k = 0; k < handlers.childNodes.length; k++) {
                    handler = handlers.childNodes[k];
                    scriptProcessor = handler.getAttribute("scriptProcessor");
                    scriptProcessor = scriptProcessor.substr(scriptProcessor.lastIndexOf('\\')+1).toLowerCase();
                    if (handler.getAttribute("resourceType") !== "Unspecified"
                     && (scriptProcessor === 'wc.dll' || scriptProcessor === 'webconnectionmodule.dll')){ /* for west-wind web connect */
                      handler.setAttribute("resourceType", "Unspecified");
                      handlerChanged = true;
                    }
                  };
                  if (handlerChanged){
                    stream = fso.OpenTextFile(file, 2);
                    stream.Write(XMLparser.xml);
WScript.Echo(file);
                  }
                }
              }
            }
          }
        }
      }
    }
  }
};

Decoding:

  • adminManager.GetAdminSection("system.applicationHost/sites", "MACHINE/WEBROOT/APPHOST") reads in "C:\Windows\System32\inetsrv\config\applicationHost.config" the following section:
<site name="Default Web Site" id="1" serverAutoStart="true">
    <application path="/" applicationPool="West Wind Web Connection">
        <virtualDirectory path="/" physicalPath="%SystemDrive%\inetpub\wwwroot" />
        <virtualDirectory path="/awScripts" physicalPath="C:\Program Files\VFP9\Tools\AB\AW\Scripts\" userName="" />
        <virtualDirectory path="/blog" physicalPath="C:\aDossier\3718 FoxLANWeb\Interne\Web\Site\blog" />
        <virtualDirectoryDefaults userName="ThierryNivelet" password="[...]" />
    </application>
    <application path="/tutoTest" applicationPool="FoxInCloud apps">
        <virtualDirectory path="/" physicalPath="C:\Program Files\VFP9\Tools\AB\AW\Samples\FIC\FicTuto\site\" />
    </application>
    <application path="/demotest" applicationPool="FoxInCloud apps">
        <virtualDirectory path="/" physicalPath="C:\Program Files\VFP9\Tools\AB\AW\Samples\FIC\FicDemo\site" />
    </application>
    <application path="/ttdoc" applicationPool="West Wind Web Connection">
        <virtualDirectory path="/" physicalPath="C:\aDossier\3718 FoxLANWeb\Interne\Web\dokuwiki" />
    </application>
    <application path="/mpn/bin" applicationPool="West Wind Web Connection">
        <virtualDirectory path="/" physicalPath="C:\aDossier\3681_MachPro_IntuiCat\Interne\Site\bin" />
    </application>
    <application path="/ctb" applicationPool="FoxInCloud apps">
        <virtualDirectory path="/" physicalPath="C:\aDossier\3725 BioSilWeb\Interne\Compo\CTB" />
    </application>
    <application path="/cal" applicationPool="FoxInCloud apps">
        <virtualDirectory path="/" physicalPath="C:\aDossier\3721 CAL\Client\Recu\BioVetoWeb\Site\CAL" />
    </application>
    <application path="/tt" applicationPool="FoxInCloud apps">
        <virtualDirectory path="/" physicalPath="C:\aDossier\3718 FoxLANWeb\Interne\Web\Site" />
    </application>
    <application path="/ip" applicationPool="FoxInCloud apps">
        <virtualDirectory path="/" physicalPath="C:\aDossier\3724 MCTG\Interne\Compo" />
    </application>
    <application path="/asl" applicationPool="FoxInCloud apps">
        <virtualDirectory path="/" physicalPath="C:\aDossier\3728 JPB\Client\Recu\asl_Web\Site" />
    </application>
    <application path="/fttTest" applicationPool="FoxInCloud apps">
        <virtualDirectory path="/" physicalPath="C:\Program Files\VFP9\Tools\AB\AW\Samples\Tastrade\Adapted\Site\fttTest" />
    </application>
    <application path="/wconnect" applicationPool="West Wind Web Connection">
        <virtualDirectory path="/" physicalPath="C:\inetpub\wwwroot\wconnect" />
    </application>
    <bindings>
        <binding protocol="http" bindingInformation="*:80:" />
        <binding protocol="net.tcp" bindingInformation="808:*" />
        <binding protocol="net.pipe" bindingInformation="*" />
        <binding protocol="net.msmq" bindingInformation="localhost" />
        <binding protocol="msmq.formatname" bindingInformation="localhost" />
    </bindings>
    <logFile period="MaxSize" truncateSize="4294967295" enabled="true" />
</site>
  • if (application.Name === 'application' && application.Properties.Item('path').Value.length > 1){ identifies the <application path="..." applicationPool="..."> nodes
  • for (var j = 0; j < application.Collection.count; j++) { and if (virtual.Name === "virtualDirectory" && virtual.Properties.Item('path').Value.length == 1){ identify the virtual directories at the applications’ root

Then comes the tricky part… Each application’s settings are stored in a different place, in a file named web.config stored in the virtual directory’s physical folder. Altering the application settings requires modifying this file, which Microsoft.ApplicationHost.WritableAdminManager is no help for.

So we need to read and write the XML in this file, and rather than parsing the XML ourselves, we prefer rely on the standard MSXML.DOMDocument activeX component:

  file = virtual.Properties.Item('physicalPath').Value;
  file = file + (file.substr(file.length-1) === '\\' ? '' : '\\') + "web.config";
  if (fso.FileExists(file)){

    stream = fso.OpenTextFile(file, 1);
    if (!stream.AtEndOfStream && XMLparser.loadXML(stream.ReadAll())){

Then we navigate through the XML nodes down to the script-mapped extension handler defined on some west-wind web connect dll and set its resourceType attribute to Undefined (case sensitive like everything in XML) if its value is different:

  for (var k = 0; k < handlers.childNodes.length; k++) {
    handler = handlers.childNodes[k];
    scriptProcessor = handler.getAttribute("scriptProcessor");
    scriptProcessor = scriptProcessor.substr(scriptProcessor.lastIndexOf('\\')+1).toLowerCase();
    if (handler.getAttribute("resourceType") !== "Unspecified"
     && (scriptProcessor === 'wc.dll' || scriptProcessor === 'webconnectionmodule.dll')){ /* for west-wind web connect */
      handler.setAttribute("resourceType", "Unspecified");
      handlerChanged = true;
    }
  };

Finally we write the result XML back into the web.config file:

  if (handlerChanged){
    stream = fso.OpenTextFile(file, 2);
    stream.Write(XMLparser.xml);
  }

Et voilà!

Update: here is the equivalent code for VFP:

local adminManager as Microsoft.ApplicationHost.WritableAdminManager;
, sitesSection, iApp;
, site;
, isapiCgiRestrictionSection;
, isapiCgiRestrictionCollection, iISAPI;
, XMLparser, oXML;
, fso, file, stream;
, app, virtual, iVirtual;
, handlers, handler, handlerChanged, iHand;
, scriptProcessor, scriptModule;
, scriptModuleEntry, scriptModuleListed, scriptModuleAllowed, isapiCgiRestrictionSectionChanged;
, true, false

adminManager = CreateObject('Microsoft.ApplicationHost.WritableAdminManager')
adminManager.CommitPath = "MACHINE/WEBROOT/APPHOST"

sitesSection = adminManager.GetAdminSection("system.applicationHost/sites", "MACHINE/WEBROOT/APPHOST")
site = sitesSection.Collection.Item(0) && alias "Default Web Site"
isapiCgiRestrictionSection = adminManager.GetAdminSection("system.webServer/security/isapiCgiRestriction", "MACHINE/WEBROOT/APPHOST")
isapiCgiRestrictionCollection = isapiCgiRestrictionSection.Collection
XMLparser = CreateObject('MSXML.DOMDocument')
fso = createObject('Scripting.FileSystemObject')
true  = .T.
false = .F.

for iApp = 0 to site.Collection.Count-1
  app = site.Collection.Item(m.iApp)

  if app.Name == 'application' and Lenc(app.Properties.Item('path').Value) > 1
    for iVirtual = 0 to app.Collection.count-1
      virtual = app.Collection.Item(iVirtual)

      if (virtual.Name == "virtualDirectory" and virtual.Properties.Item('path').Value == '/')
        * Update web connection handlers in virtual's web.config
        file = virtual.Properties.Item('physicalPath').Value
        file = file + iif(substrc(file, Lenc(file)-1) == '\', '', '\') + "web.config"
        if (fso.FileExists(file))
          stream = fso.OpenTextFile(file, 1)

          if (!stream.AtEndOfStream and XMLparser.loadXML(stream.ReadAll()))
            handlers = XMLparser.documentElement

            if vartype(handlers) == 'O'
              handlers = handlers.getElementsByTagName('system.webServer')

              if (!empty(handlers.length))
                handlers = handlers.Item[0]
                handlers = handlers.getElementsByTagName('handlers')

                if !empty(handlers.length)
                  handlers = handlers.Item[0]
                  handlerChanged = false
                  for iHand = 0 to handlers.childNodes.length - 1
                    handler = handlers.childNodes.Item[m.iHand]
                    scriptProcessor = handler.getAttribute("scriptProcessor")
                    scriptModule = Lower(Substrc(scriptProcessor, Ratc('\', scriptProcessor)+1))
                    if (! handler.getAttribute("resourceType") == "Unspecified";
                     and (scriptModule == 'wc.dll' or scriptModule == 'webconnectionmodule.dll')) && west-wind web connect
                      handler.setAttribute("resourceType", "Unspecified")
                      handlerChanged = true
                    endif

                    && make sure script processor is allowed && https://www.iis.net/configreference/system.webserver/security/isapicgirestriction
                    scriptModuleListed = false
                    scriptModuleAllowed = false
&& ? scriptProcessor
                    for iISAPI = 0 to isapiCgiRestrictionCollection.Count-1
                      scriptModuleEntry = isapiCgiRestrictionCollection.Item(m.iISAPI)
&& ? scriptModuleEntry.Properties.Item('path').Value
                      if (scriptModuleEntry.Name == 'add' and Lower(scriptModuleEntry.Properties.Item('path').Value) == Lower(scriptProcessor))
                        scriptModuleListed = true
                        scriptModuleAllowed = scriptModuleEntry.Properties.Item('allowed').Value == true
                        exit
                      endif
                    endfor
                    if !scriptModuleAllowed
                      if !scriptModuleListed
                        scriptModuleEntry = isapiCgiRestrictionCollection.CreateNewElement('add')
                        scriptModuleEntry.Properties.Item('path').Value = scriptProcessor
                      endif
                      scriptModuleEntry.Properties.Item('allowed').Value = true
                      scriptModuleEntry.Properties.Item('groupId').Value = handler.getAttribute('name')
                      scriptModuleEntry.Properties.Item('description').Value = 'FoxInCloud Application'
                      if(!scriptModuleListed)
                        isapiCgiRestrictionCollection.addElement(scriptModuleEntry)
                      endif
                      isapiCgiRestrictionSectionChanged = true
                    endif
                  endfor
                  if (handlerChanged)
                    stream.Close()
                    stream = fso.OpenTextFile(file, 2)
                    stream.Write(XMLparser.xml)
                    stream = fso.OpenTextFile(file, 1)
                  endif
                endif
              endif
            endif
            stream.Close()
          endif
        endif
      endif
    endfor
  endif
endfor

if isapiCgiRestrictionSectionChanged
  adminManager.CommitChanges()
endif