Automated Deployment - MSBuild.Community.Tasks - WebDirectoryCreate / WebDirectorySettings Example

From my previous post of WebDirectoryCreate I have an example script of how you would use MSBuild Community Tasks to Set up Virtual Directories and/or do updates to the current virtual directory.

The below is an example of how you can create or update virtual directories.  This is what you would add to the MSBuild.proj file.

 

<PropertyGroup>
  <!-- Virtual Directory Items-->
  <EnableVirtualDirectorySetup>True</EnableVirtualDirectorySetup>
  <VirtualDirectoryApplicationPoolName>ASPNET2</VirtualDirectoryApplicationPoolName>
  <!-- Main App Virtual Directory -->
  <VirtualDirectoryPath></VirtualDirectoryPath>
  <VirtualDirectoryName>ZVirDir</VirtualDirectoryName>
  <VirtualDirectoryFullName>$(VirtualDirectoryPath)$(VirtualDirectoryName)</VirtualDirectoryFullName>
  <VirtualDirectoryDefaultDoc>default.aspx</VirtualDirectoryDefaultDoc>
  <EnableVirtualDirectoryAnonymousAccess>true</EnableVirtualDirectoryAnonymousAccess>
</PropertyGroup>
 
<Choose>
  <When Condition=" '$(Environment)'=='DEV' ">
    <PropertyGroup>
      <WebAppServer>serverDev</WebAppServer>
      <!-- Main App Virtual Directory -->
      <VirtualDirectorySetupServerName>$(WebAppServer)</VirtualDirectorySetupServerName>
      <VirtualDirectoryHostHeader>OrgDev</VirtualDirectoryHostHeader>
      <VirtualDirectoryPath>dude/</VirtualDirectoryPath>
      <VirtualDirectoryFullName>$(VirtualDirectoryPath)$(VirtualDirectoryName)</VirtualDirectoryFullName>
      <VirtualDirectoryPhysicalPath>E:\Dev\$(VirtualDirectoryName)</VirtualDirectoryPhysicalPath>
    </PropertyGroup>
  </When>
  <When Condition=" '$(Environment)'=='TEST' ">
    <PropertyGroup>
      <WebAppServer>serverTest</WebAppServer>
      <!-- Main App Virtual Directory -->
      <VirtualDirectorySetupServerName>$(WebAppServer)</VirtualDirectorySetupServerName>
      <VirtualDirectoryHostHeader>test.com</VirtualDirectoryHostHeader>
      <VirtualDirectoryPhysicalPath>E:\Code\$(VirtualDirectoryName)</VirtualDirectoryPhysicalPath>
    </PropertyGroup>
  </When>
</Choose>
 
<Target Name="AppVirtualDirectoryDeploy" Condition="$(EnableVirtualDirectorySetup) =='true'">
 
    <!-- Virtual Directory Setup -->
    <Message Text="" />
    <Message Text="" />
    <Message Text="**********************************************" />
    <Message Text="Virtual Directory/Web.Config Setup - $(Environment)" />
    <Message Text="**********************************************" />
    <Message Text="" />
 
 
    <AppPoolCreate
        Condition="$(Environment) !='LOCAL' AND $(VirtualDirectoryApplicationPoolName) !=''"
        ServerName="$(VirtualDirectorySetupServerName)"
        ApplicationPoolName="$(VirtualDirectoryApplicationPoolName)"
        ContinueOnError="true"
        />
 
    <!-- Virtual Directory Setup - Web Deploy -->
 
    <Message Text="" />
    <Message Text="" />
    <Message Text="**********************************************" />
    <Message Text="Virtual Directory Creation for $(VirtualDirectoryFullName) - $(Environment)" />
    <Message Text="**********************************************" />
    <Message Text="" />
 
    <Message
        Condition="$(Interactive) != 'true'"
        Text="|| Skipping - Interactive setup must be enabled ||"
        />
 
    <Prompt
        Condition="$(Interactive) == 'true'"
        Text="Create virtual directory (leave blank for No)?"
        >
      <Output TaskParameter="UserInput" PropertyName="CreateWebDirectory"/>
    </Prompt>
 
    <!--<WebDirectoryDelete
      Condition="$(CreateWebDirectory) !='' AND $(CreateWebDirectory) !='N'"
      ServerName="$(VirtualDirectorySetupServerName)"
      HostHeaderName="$(VirtualDirectoryHostHeader)"
      VirtualDirectoryName="$(VirtualDirectoryFullName)"
      ContinueOnError="true"
        />-->
 
    <WebDirectoryCreate
        Condition="$(CreateWebDirectory) !='' AND $(CreateWebDirectory) !='N'"
        ServerName="$(VirtualDirectorySetupServerName)"
        HostHeaderName="$(VirtualDirectoryHostHeader)"
        VirtualDirectoryName="$(VirtualDirectoryFullName)"
        VirtualDirectoryPhysicalPath="$(VirtualDirectoryPhysicalPath)"
        AccessRead="True"
        AuthAnonymous="$(EnableVirtualDirectoryAnonymousAccess)"
        AnonymousPasswordSync="False"
        AuthNtlm="True"
        EnableDefaultDoc="True"
        ContinueOnError="true"
        />
 
 
    <Message Text="" />
    <Message Text="" />
    <Message Text="**********************************************" />
    <Message Text="Virtual Directory Updates for $(VirtualDirectoryFullName) - $(Environment)" />
    <Message Text="**********************************************" />
    <Message Text="" />
 
    <WebDirectoryScriptMap
        ServerName="$(VirtualDirectorySetupServerName)"
        HostHeaderName="$(VirtualDirectoryHostHeader)"
        VirtualDirectoryName="$(VirtualDirectoryFullName)"
        Extension=".svc"
        MapToAspNet="True"
        VerifyFileExists="False"
        Verbs="GET,POST,HEAD,DEBUG"
        />
 
    <WebDirectorySetting
        Condition="$(VirtualDirectoryDefaultDoc) !=''"
        ServerName="$(VirtualDirectorySetupServerName)"
        HostHeaderName="$(VirtualDirectoryHostHeader)"
        VirtualDirectoryName="$(VirtualDirectoryFullName)"
        SettingName="DefaultDoc"
        SettingValue="$(VirtualDirectoryDefaultDoc)"
        />
 
    <WebDirectorySetting
        ServerName="$(VirtualDirectorySetupServerName)"
        HostHeaderName="$(VirtualDirectoryHostHeader)"
        VirtualDirectoryName="$(VirtualDirectoryFullName)"
        SettingName="AppFriendlyName"
        SettingValue="$(VirtualDirectoryName)"
        />
 
    <WebDirectorySetting
        Condition="$(Environment) !='LOCAL' AND $(VirtualDirectoryApplicationPoolName) !=''"
        ServerName="$(VirtualDirectorySetupServerName)"
        HostHeaderName="$(VirtualDirectoryHostHeader)"
        VirtualDirectoryName="$(VirtualDirectoryFullName)"
        SettingName="AppPoolId"
        SettingValue="$(VirtualDirectoryApplicationPoolName)"
        ContinueOnError="true"
        />
 
    <Message Text="" />
    <Message Text="" />
    <Message Text="**********************************************" />
    <Message Text="Virtual Directory Anonymous Authorization Account for $(VirtualDirectoryFullName) - $(Environment)" />
    <Message Text="**********************************************" />
    <Message Text="" />
 
    <Message
        Condition="$(Interactive) != 'true' OR $(EnableVirtualDirectoryAnonymousAccess) !='true'"
        Text="|| Skipping - Anonymous authentication and interactive setup must be enabled ||"
        />
 
    <Prompt
        Condition="$(EnableVirtualDirectoryAnonymousAccess) =='true' AND $(Interactive) == 'true'"
        Text="What is the Domain\UserName for the anonymous account (leave blank for inherited/current)?"
        >
      <Output TaskParameter="UserInput" PropertyName="VirtualDirectoryAnonymousUserName"/>
    </Prompt>
    <Message Text="" />
    <Prompt
        Condition="$(VirtualDirectoryAnonymousUserName)!=''"
        Text="Password:"
        >
      <Output TaskParameter="UserInput" PropertyName="VirtualDirectoryAnonymousPassword"/>
    </Prompt>
 
    <WebDirectorySetting
        Condition="$(VirtualDirectoryAnonymousUserName)!=''"
        ServerName="$(VirtualDirectorySetupServerName)"
        HostHeaderName="$(VirtualDirectoryHostHeader)"
        VirtualDirectoryName="$(VirtualDirectoryFullName)"
        SettingName="AnonymousUserName"
        SettingValue="$(VirtualDirectoryAnonymousUserName)"
        />
 
    <WebDirectorySetting
        Condition="$(VirtualDirectoryAnonymousUserName)!=''"
        ServerName="$(VirtualDirectorySetupServerName)"
        HostHeaderName="$(VirtualDirectoryHostHeader)"
        VirtualDirectoryName="$(VirtualDirectoryFullName)"
        SettingName="AnonymousPasswordSync"
        SettingValue="false"
        />
 
    <WebDirectorySetting
        Condition="$(VirtualDirectoryAnonymousUserName)!=''"
        ServerName="$(VirtualDirectorySetupServerName)"
        HostHeaderName="$(VirtualDirectoryHostHeader)"
        VirtualDirectoryName="$(VirtualDirectoryFullName)"
        SettingName="AnonymousUserPass"
        SettingValue="$(VirtualDirectoryAnonymousPassword)"
        />
 
</Target>

MSBuild.Community.Tasks - WebDirectoryCreate / WebDirectorySetting / WebDirectoryDelete

Recently I submitted a patch for the MSBuild.Community.Tasks because there were certain tasks that were not working for us.  I decided to blog about this because there are others that indicated they were having the same issues. 

In IIS, you are able to specify more than one website with the same ports.  By default, the WebDirectoryCreate task will always loop through and choose the last website with that port.  If you do have more than one website with the same ports, you need to distinguish differences between the two by either the host header name or by having different IP addresses for the two on the same server.  So I added a HostHeaderName property to the WebBase.cs where you can put in either an IP address or a Host header and it will search for that as a string based on the entire web site identification.

 

This is where the search across the different websites in IIS to find the one that has the site header.

/// <summary>
/// Verify that the IIS Website exists if it has been specified.
/// </summary>
/// <param name="site">DirectoryEntry that meets the IISWebServer schema</param>
/// <returns>True if a site is found when specified. True if no site has been specified.</returns>
private bool VerifySiteHostHeaderExists(DirectoryEntry site)
{
    bool siteHeaderFound = false;
 
    if (HostHeaderName == null)
    {
        siteHeaderFound = true;
    }
    else
    {
        //get the serverBinding information
        foreach (object serverBinding in (PropertyValueCollection)site.Properties["ServerBindings"])
        {
            //find the IIS Website that has been specified by the user
            if (((string)serverBinding).ToLower().IndexOf(HostHeaderName.ToLower()) != -1)
            {
                siteHeaderFound = true;
                break;
            }
        }
    }
 
    return siteHeaderFound;
}

 

Second, if you specify more than one identity for a website, it would break the functionality of the create and delete tasks.  This is due to the site.Properties["ServerBindings"].Value.ToString() being a String when there was only one item and returning as an Object() if there was more than one.  This is what the code looked like below.  The offending code is in bold below.

private bool VerifyServerPortExists(DirectoryEntry site)
{
    string serverBindings = site.Properties["ServerBindings"].Value.ToString(); //offending line of code
    string[] serverBindingsArray = serverBindings.Split(':');
 
 
    if (mServerPort == Convert.ToInt32(serverBindingsArray[1])) //This is where the error originates
    {
        mServerInstance = site.Name;
        mIISServerPath = string.Format("IIS://{0}/W3SVC/{1}/Root", mServerName, mServerInstance);
        mIISApplicationPath = string.Format("/LM/W3SVC/{0}/Root", mServerInstance);
        mIISAppPoolPath = string.Format("IIS://{0}/W3SVC/AppPools", mServerName);
        return true;
 
    }
    return false;
}

 

This is what it looks like now.  Notice that I moved the setting of the IIS primitives out of the verification area (although it is still being called by the VerifyIISRoot).