Connecting the Windows Phone 8 Emulator to Web API Applications on a Local Computer

I've been playing around with Web API a lot recently, and I've found that it's a really powerful and elegant way to create internet-based applications. After writing several server-side Web API applications, I thought that it would be fun to write a Windows Phone 8 application that used Web API to communicate with one of my server-side applications.

I was using the Visual Studio 2013 Preview to write my Web API application, and by default Visual Studio 2012 and later use IIS Express for the development web server on http://localhost with a random port. With this in mind, I thought that it would be trivial to create a Windows Phone 8 application that would be able to send HTTP GET requests to http://localhost to download data. This seemed like an easy thing to do, but it turned out to be considerably more difficult than I had assumed, so I thought that I would dedicate a blog post to getting this scenario to work.

I should point out that I stumbled across the following article while I was getting my environment up and working, but that article had a few steps that didn't apply to my environment, and there were a few things that were required for my environment that were missing from the article:

http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj684580.aspx

In addition, there were a few things that I needed that might not apply to everyone, and I'll try to point those out as I go along.

System Requirements for Windows Phone SDK 8.0

First things first, you need to make sure that you have a physical computer that meets the system requirements for the Windows Phone 8 SDK that are posted at the following URL:

http://www.microsoft.com/en-us/download/details.aspx?id=35471

Here is an annotated copy of the system requirements list at the time that I wrote this blog:

  • Supported Operating Systems:
  • Operating System Type:
    • Windows 8 64-bit (x64) client versions
    • Note: You cannot use Windows 8 32-bit (x86) client versions
  • Hardware:
    • 6.5 GB of free hard disk space
    • 4 GB RAM
    • 64-bit (x64) CPU
  • Windows Phone 8 Emulator:
    • Windows 8 Pro edition or greater
    • Requires a processor that supports Second Level Address Translation (SLAT)
    • Note: The Windows Phone 8 Emulator runs in Hyper-V, so you cannot use a Hyper-V virtual machine for the host computer where you install the Windows Phone 8 SDK
  • Additional Notes:
    • If your computer meets the hardware and operating system requirements, but does not meet the requirements for the Windows Phone 8 Emulator, the Windows Phone SDK 8.0 will install and run. However, the Windows Phone 8 Emulator will not function and you will not be able to deploy or test apps on the Windows Phone 8 Emulator.

You Cannot use "Localhost" for Testing

The first time that I launched my Web API application and tried to connect to it from the Windows Phone 8 Emulator (WP8E), I used "http://localhost" as the domain where my Web API application was hosted. This was a silly mistake on my part; the WP8E runs in Hyper-V, and the WP8E believes that it is a separate computing device than your host computer, so using "http://localhost" in the WP8E meant that it was trying to browse to itself, not the host machine. (Duh.)

Your Host Computer Must Not Use IPSEC

My computer was joined to a domain, and our domain uses IP Security (IPSEC) for obvious reasons. That being said, the Windows Phone 8 Emulator is not going to use IPSEC to connect to the host machine, so I needed to find a way around IPSEC.

Our IT department allows us to make a domain-joined machine as a boundary machine to get past this problem, but I decided to set up a new, physical machine from scratch for my development testing.

Verify that the Windows Phone Emulator Internal Switch Exists

After you install the Windows Phone 8 SDK with the Windows Phone 8 Emulator (WP8E), you will have a new virtual machine in Hyper-V with a name like "Emulator WVGA 512MB"; this is the actual WP8E instance.

You should also have a new virtual switch named "Windows Phone Emulator Internal Switch" in the Hyper-V Virtual Switch Manager; WP8E will use this virtual switch to communicate with your host machine. (This virtual switch was missing on one of the systems where I was doing some testing, so I had to add it manually.)

Disable Windows Firewall on your Host Computer

Since the Hyper-V machine for the Windows Phone 8 Emulator (WP8E) and IIS Express will be communicating over your network, you will need to make sure that the Windows Firewall is not blocking the communication. I attempted to add an exception for IIS Express to the Windows Firewall, but that did not seem to have any effect - I had to actually disable the Windows Firewall on my development machine to get my environment working. (Note that it is entirely possible that I needed to configure something else in my Windows Firewall settings in order to allow my environment to work without disabling Windows Firewall, but I couldn't find it, and it was far easier in the short term to just disable Windows Firewall for the time being.)

Verify the Internal IP Address of your Windows Phone 8 Emulator

It's entirely possible that the Windows Phone 8 Emulator (WP8E) always uses a 169.254.nnn.nnn IP address on the "Windows Phone Emulator Internal Switch", but I needed to make sure which IP address range to use when configuring IIS Express.

The way that I chose to do this was to drop the following code inside my WP8E application and I stepped through it in a debugger to tell me the IP addresses that WP8E had assigned for each network interface:

var hostnames = Windows.Networking.Connectivity.NetworkInformation.GetHostNames();
foreach (var hn in hostnames)
{
    if (hn.IPInformation != null)
    {
        string ipAddress = hn.DisplayName;
    }
}

I had two IP addresses for the WP8E: one in the 192.168.nnn.nnn IP address range, and another the 169.254.nnn.nnn IP address range. Once I verified that one of my IP addresses was in the 169.254.nnn.nnn range, I was able to pick an IP address in that range for IIS Express.

Add an Internal IP Address to IIS Express for your Web API Application

Once you have verified the IP address range that the Windows Phone 8 Emulator (WP8E) is using, you can either pick a random IP address within that range to use with IIS Express, or you can use the default IP address for the Windows Phone Emulator Internal Switch. To verify the default IP address, you would need to open a command prompt and run ipconfig, then look for the Internal Ethernet Port Windows Phone Emulator Internal Switch:

C:\>ipconfig

Windows IP Configuration

Ethernet adapter vEthernet (Internal Ethernet Port Windows Phone Emulator
Internal Switch
):

   Connection-specific DNS Suffix . :
   Link-local IPv6 Address . . . . . : fe80::81d3:c1c7:307b:d732%11
   IPv4 Address. . . . . . . . . . . : 169.254.80.80
   Subnet Mask . . . . . . . . . . . : 255.255.0.0
   Default Gateway . . . . . . . . . :

Ethernet adapter vEthernet (Intel(R) 82579LM Gigabit Network Connection
Virtual Switch):

   Connection-specific DNS Suffix . :
   Link-local IPv6 Address . . . . . : fe80::3569:3387:bb3f:583b%8
   IPv4 Address. . . . . . . . . . . : 192.168.1.72
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : 192.168.1.1

C:\>

If you decide to use a custom IP address in the range that your Windows Phone 8 Emulator is using, you would need to enter that IP address in the IPv4 TCP/IP settings for the Internal Ethernet Port Windows Phone Emulator Internal Switch:

(Click the image to expand it.)

Once you have the IP address that you intend to use, you will need to add that to your IIS Express settings. There are two easy ways to do this, both of which require administrative privileges on your system:

Method #1: Use AppCmd from an elevated command prompt

Open an elevated command prompt session by right-clicking the Command Prompt icon and choosing Run as administrator, then enter the following commands:

cd "%ProgramFiles%\IIS Express"

appcmd.exe set config -section:system.applicationHost/sites /+"[name='WebApplication1'].bindings.[protocol='http',bindingInformation='169.254.21.12:80:']" /commit:apphost

Where WebApplication1 is the name of your Web API application and 169.254.21.12 is the IP address that you chose for your testing.

Method #2: Manually edit ApplicationHost.config

Open Windows Notepad as an administrator, and open the "ApplicationHost.config" file for IIS Express for editing. By default this file should be located at "%UserProfile%\Documents\IISExpress\config\ApplicationHost.config".

Locate the code for the website of your Web API application; this should resemble something like the following, where WebApplication1 is the name of your Web API application:

<site name="WebApplication1" id="1" serverAutoStart="true">
    <application path="/">
        <virtualDirectory path="/" physicalPath="%IIS_SITES_HOME%\WebApplication1" />
    </application>
    <bindings>
        <binding protocol="http" bindingInformation=":54321:localhost" />
    </bindings>
</site>

Copy the existing binding and paste it below the original entry, then change the binding to resemble the following example, where 169.254.21.12 is the IP address that you chose for your testing:

<site name="WebApplication1" id="1" serverAutoStart="true">
    <application path="/">
        <virtualDirectory path="/" physicalPath="%IIS_SITES_HOME%\WebApplication1" />
    </application>
    <bindings>
        <binding protocol="http" bindingInformation=":54321:localhost" />
        <binding protocol="http" bindingInformation="169.254.21.12:80:" />
    </bindings>
</site>

Save the file and close Windows Notepad.

Specify the Internal IP Address of your Web API Application in your Windows Phone 8 Application

Once you have configured IIS Express to use an internal IP address, you need to specify that IP address for your Windows Phone 8 application. The following example shows what this might look like:

public void LoadData()
{
    if (this.IsDataLoaded == false)
    {
        this.Items.Clear();
        WebClient webClient = new WebClient();
        webClient.Headers["Accept"] = "application/json";
        webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webClient_DownloadStringCompleted);
        webClient.DownloadStringAsync(new Uri(@"http://169.254.21.12/api/TodoList"));
    }
}

Where 169.254.21.12 is the IP address that you chose for your testing.

Configure Proxy Settings in your Internet Options to Bypass the Proxy for your Internal IP Address

It is essential that you configure your proxy settings so that the IP address of your Web API application will be considered an internal network address; otherwise all of the requests from the Windows Phone 8 Emulator (WP8E) will attempt to locate your Web API application on the Internet, which will fail.

The WP8E will use the proxy settings from your Windows Internet options, which are the same settings that are used by Internet Explorer. This means that you can either set your proxy settings through the Windows Control Panel by using the Internet Options feature, or you can use Internet Explorer's Tools -> Internet Options menus.

Once you have the Internet Options dialog open, click the Connections tab, then click the LAN settings button.

There are a few ways that you can specify your IP address as internal:

Method #1: Specify your proxy server, then click the Advanced button and add your internal IP address to the list of exceptions:

Method #2: If you do not need actual Internet access during your testing, you can specify "localhost" as the proxy server, then click the Advanced button and add your internal IP address to the list of exceptions:

Method #3: Specify your internal IP address as the proxy server; it isn't really a proxy server, of course, but this will keep the requests internal.

Launch IIS Express as an Administrator

In order for IIS Express to register a binding with HTTP.SYS on an IP address other than 127.0.0.1, you need to run IIS Express using an elevated session. There are two easy ways to do this:

Method #1: Launch IIS Express from Visual Studio as an administrator

Start Visual Studio as an administrator by right-clicking the Visual Studio icon and choosing Run as administrator. Once Visual Studio has opened, you can open your Web API project and hit F5 to launch IIS Express.

Method #2: Launch IIS Express from an elevated command prompt

Open an elevated command prompt session by right-clicking the Command Prompt icon and choosing Run as administrator, then enter the following commands:

cd "%ProgramFiles%\IIS Express"

iisexpress.exe /site:WebApplication1

Where WebApplication1 is the name of your Web API application.

In Closing

As I pointed out in my opening statements for this blog, getting the Windows Phone 8 Emulator to communicate with a Web API application on the same computer was not as easy as I would have thought, but all of the steps made sense once I had all of the disparate technologies working together.

Have fun! ;-]


Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/

Microsoft IIS 8.0 Express Beta is Released!

Earlier today the IIS Express team released the IIS 8.0 Express Beta, and there are some great new features in this release! Here are just a few of the highlights:

64-bit Support
IIS 8.0 Express now fully supports 64-bit application development. When you install IIS Express on a 64-bit system, you actually get both 32-bit and 64-bit versions of IIS 8.0 Express installed, which allows you to use the version that matches your project's needs.
Customizable Home Directory
The default home directory for IIS Express is "%UserProfile%\Documents\IISExpress", but with IIS 8.0 Express you can start the iisexpress.exe process with the "/userhome" parameter to specify the home directory for your projects; this makes it easier for you to use IIS 8.0 Express with multiple development applications.
AppCmd Support for Multiple ApplicationHost.config Files
As a complement to allowing users to customize their IIS Express home directory, IIS 8.0 Express contains a new version of AppCmd.exe that supports a new "/AppHostConfig" parameter, which makes it possible to use AppCmd.exe to edit multiple ApplicationHost.config files. By default AppCmd.exe for IIS 7 or IIS 7.5 Express will only edit the ApplicationHost.config file in your "%WinDir%\System32\InetSrv\Config" or "%UserProfile%\Documents\IISExpress" folder, but the AppCmd.exe command-line utility that ships with IIS 8.0 Express allows you to edit ApplicationHost.config files anywhere on your system.

You can read more about this release at the following URL:

http://learn.iis.net/page.aspx/1266/iis-80-express-beta-readme/


Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/

Storing IIS 7.5 WebDAV Properties in NTFS Alternate Data Streams

Two months ago Microsoft published an update for the WebDAV module that shipped with IIS 7.5 in Windows 7 and Windows Server 2008 R2, and this update is documented in the Microsoft Knowledge Base article ID 2593591:

FIX: A hotfix is available that enables WebDAV to store the properties of file resources by using NTFS alternate data streams in IIS 7.5

This update enables administrators to configure the IIS 7.5 WebDAV module to store WebDAV-based properties in NTFS alternate data streams instead of properties.dav files. By way of explanation, WebDAV has two HTTP methods - PROPFIND and PROPPATCH - which enable WebDAV clients to store custom properties on a WebDAV server. These properties may contain anything that makes sense to the WebDAV client. For example, if you were creating a WebDAV client that stored Microsoft Office documents on a WebDAV server, you could store metadata in WebDAV properties for each document, like the author's name, document abstract, etc.

By default, the IIS 7.5 WebDAV module stores properties in system files in each folder of a website that are called properties.dav. These files are essentially text-based INI files that contain the encoded WebDAV properties for the various files in each folder. In contrast, the WebDAV functionality in IIS 6 had used NTFS alternate data streams to store WebDAV properties, which are described in the following Microsoft TechNet article:

The NTFS File System

After we shipped IIS 6, we received a lot of complaints from customers who were losing their WebDAV properties when they were copying their website files between NTFS and FAT file systems. This was expected behavior - NTFS alternate data streams will be removed when you copy files from NTFS to FAT. To remedy this situation, in IIS 7.0 we decided to switch to using INI-based functionality in order to prevent losing custom WebDAV properties when files are copied between disparate file systems.

When we were designing IIS 7.5, we wanted to add optional support for storing WebDAV properties in NTFS alternate data streams, and we wanted to do so because NTFS alternate data streams might perform faster when you are working with larger websites; however, we ran out of time to implement that functionality before we shipped Windows 7 and Windows Server 2008 R2. That being said, we still wanted to implement the feature, and the update that I listed at the beginning of this blog contains the functionality that is required to enable storing WebDAV properties in NTFS alternate data streams.

Enabling Alternate Data Streams for WebDAV Properties

The above information is good news for anyone who is storing large quantities of WebDAV properties, so your next logical question might be: "How do I enable NTFS alternate data streams for WebDAV properties ?"

Actually, it's really simple. In the KB article that I listed in the beginning of this blog, I documented two methods that show you how to enable storing WebDAV properties in NTFS alternate data streams:

  1. By modifying your applicationHost.config file
  2. By using AppCmd.exe

For the sake of completeness, I will repost some of the information here. ;-)

Method #1: Modifying your applicationHost.config file

You can enable storing WebDAV properties in alternate data streams for the simple property provider by adding a "useAlternateDataStreams" attribute to the property provider’s registration settings in your applicationHost.config file, which is highlighted in the following global configuration snippet:

<webdav>
  <globalSettings>
    <propertyStores>
      <add name="webdav_simple_prop"
        image="%windir%\system32\inetsrv\webdav_simple_prop.dll"
        image32="%windir%\syswow64\inetsrv\webdav_simple_prop.dll"
        useAlternateDataStreams="true" />
    </propertyStores>
    <lockStores>
      <add name="webdav_simple_lock"
        image="%windir%\system32\inetsrv\webdav_simple_lock.dll"
        image32="%windir%\syswow64\inetsrv\webdav_simple_lock.dll" />
    </lockStores>
  </globalSettings>
  <authoring>
    <locks enabled="true" lockStore="webdav_simple_lock" />
    <properties>
      <clear />
      <add xmlNamespace="*" propertyStore="webdav_simple_prop" />
    </properties>
  </authoring>
  <authoringRules />
</webdav>

Once you have enabled the feature, you have to restart IIS in order for it to take effect.

Method #2: Using AppCmd.exe

I wrote the following batch file for the KB article, which uses AppCmd.exe to enable the NTFS alternate data streams functionality, and it automatically restarts IIS for you:

pushd "%SystemRoot%\System32\Inetsrv"

iisreset /stop

appcmd.exe set config -section:system.webServer/webdav/globalSettings -propertyStores.[name='webdav_simple_prop'].useAlternateDataStreams:true /commit:apphost

iisreset /start

popd

Migrating IIS 7 WebDAV Properties into Alternate Data Streams

Once you've enabled storing WebDAV properties in alternate data streams, you are presented with a new challenge: "How do I migrate my existing WebDAV properties?"

Here's the situation, once you have enabled the alternate data streams feature, the WebDAV property provider is going to ignore any properties that have already been set in properties.dav files. With this in mind, I wrote a script that will migrate all of the WebDAV properties from all of the properties.dav files in a website into their corresponding per-file NTFS alternate data streams.

To use the following script, you will need to update the folder path in the third line of the script with the path to your website. Once you have done that, you can run the script to migrate your existing WebDAV properties.

NOTE: You need to run this script as an administrator!

Option Explicit

Dim arrFolderTree, intFolderCount

arrFolderTree = BuildFolderTree("C:\inetpub\wwwroot")

For intFolderCount = 1 To UBound(arrFolderTree)
  MigratePropertiesToADS arrFolderTree(intFolderCount)
Next

Sub MigratePropertiesToADS(strFolderPath)
  On Error Resume Next
  
  ' Declare all our variables
  Dim objTempFSO, objTempFolder
  Dim objTempPropertiesFile, objTempAlternateDataStream
  Dim strTempLine, strTempObjectName, blnTempOpenStream
  Const strTempPropertiesDAV = "\properties.dav"
  Const strTempAlternateDataStream = ":properties.dav:$DATA"

  ' Create a file system object.
  Set objTempFSO = WScript.CreateObject("Scripting.FileSystemObject")

  ' Flag the function as having a closed output stream.
  blnTempOpenStream = False

  ' Retrieve a folder object for the path.
  Set objTempFolder = objTempFSO.GetFolder(strFolderPath)

  ' Check for a properties.dav file in the current folder.
  If objTempFSO.FileExists(objTempFolder.Path & strTempPropertiesDAV) Then
    ' Open the properties.dav file for the current folder.
    Set objTempPropertiesFile = objTempFSO.OpenTextFile(objTempFolder.Path & _
      strTempPropertiesDAV,1,False,-1)
    ' Loop through the properties.dav file.
    Do While Not objTempPropertiesFile.AtEndOfStream
      ' Retrieve a line from the properties.dav file.
      strTempLine = Trim(objTempPropertiesFile.ReadLine)
      ' Check if it's a section heading.
      If Left(strTempLine,1) = "[" And Right(strTempLine,1) = "]" Then
        ' Parse the name of the object (file/folder).
        strTempObjectName = Replace(Trim(Mid(strTempLine,2,Len(strTempLine)-2)),"/","\")
        ' Strip off a backslash from the parent folder.
        If Len(strTempObjectName) = 1 Then strTempObjectName = ""
        ' Check to see if the file/folder exists.
        If objTempFSO.FileExists(objTempFolder.Path & _
             strTempObjectName) Or objTempFSO.FolderExists(objTempFolder.Path & _
             strTempObjectName) Then
          ' Create a file object for the alternate data stream.
          Set objTempAlternateDataStream = objTempFSO.CreateTextFile(objTempFolder.Path & _
             strTempObjectName & _
             strTempAlternateDataStream,True,-1)
          ' Write the WebDAV section header.
          objTempAlternateDataStream.WriteLine "[WebDAV]"
          ' Flag the function as having an open output stream.
          blnTempOpenStream = True
        Else
          ' Flag the function as having a closed output stream.
          blnTempOpenStream = False
        End If
      Else
        ' Check if we have an open output stream.
        If blnTempOpenStream = True Then
          ' Output a property.
          objTempAlternateDataStream.WriteLine strTempLine
        End If
      End If
    Loop
    ' Close the properties.dav file.
    objTempPropertiesFile.Close
  End If
  Set objTempFSO = Nothing
End Sub

Function BuildFolderTree(strTempBaseFolder)
  On Error Resume Next

  ' Declare all our variables
  Dim objTempFSO
  Dim objTempFolder
  Dim objTempSubFolder
  Dim lngTempFolderCount
  Dim lngTempBaseCount

  ' Create our file system object.
  Set objTempFSO = WScript.CreateObject("Scripting.FileSystemObject")
     
  ' Define the initial values for our folder counters.
  lngTempFolderCount = 1
  lngTempBaseCount = 0
  
  ' Dimension an array to hold the folder names.
  ReDim strTempFolders(1)
  
  ' Store the root folder in our array.
  strTempFolders(lngTempFolderCount) = strTempBaseFolder
    
  ' Loop while we still have folders to process.
  While lngTempFolderCount <> lngTempBaseCount
    ' Set up a folder object to a base folder.
    Set objTempFolder = objTempFSO.GetFolder(strTempFolders(lngTempBaseCount+1))
    ' Loop through the collection of subfolders for the base folder.
    For Each objTempSubFolder In objTempFolder.SubFolders
      ' Increment our folder count.
      lngTempFolderCount = lngTempFolderCount + 1
      ' Increase our array size
      ReDim Preserve strTempFolders(lngTempFolderCount)
      ' Store the folder name in our array.
      strTempFolders(lngTempFolderCount) = objTempSubFolder.Path
    Next
    ' Increment the base folder counter.
    lngTempBaseCount = lngTempBaseCount + 1
  Wend

  ' Return the array of folder names.
  BuildFolderTree = strTempFolders

End Function

In Closing

I have a couple final notes for you to consider:

  • Enabling NTFS alternate data streams is a global WebDAV setting; you cannot do this on a per-site basis.
  • As with IIS 6, once you enable storing WebDAV properties in NTFS alternate data streams, you will lose your WebDAV properties if you copy your files between NTFS and FAT file systems.

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/

How to add <clear/> or <remove/> Elements through Scripting

I had a question recently where someone was trying to add <clear /> or <remove /> elements to a collection in their IIS 7 configuration settings. With that in mind, for today's blog I thought that I would discuss a couple of ways to add <clear /> and <remove /> elements by using two specific scripting methods: AppCmd and VBScript.

It should be noted that you can also use JavaScript or PowerShell, but I'm not covering those because the syntax for those is available elsewhere. (JavaScript syntax is available in the Configuration Editor in IIS Manager, and the PowerShell syntax is available through the Web Server (IIS) Administration Cmdlet Reference.) You can also use Managed-Code, and the syntax for that is also available in the Configuration Editor in IIS Manager; but compiled code isn't scripting, is it? :-)

Here's the scenario, IIS makes it possible to modify the contents of an inherited collection in two ways:

  • You can clear the contents of an inherited configuration section, as illustrated by the following configuration excerpt:
    <configuration>
       <system.webServer>
          <defaultDocument enabled="true">
             <files>
                <clear />
             </files>
          </defaultDocument>
       </system.webServer>
    </configuration>
  • You can remove an item from an inherited collection, as illustrated by the following configuration excerpt:
    <configuration>
       <system.webServer>
          <defaultDocument enabled="true">
             <files>
                <remove value="index.html" />
             </files>
          </defaultDocument>
       </system.webServer>
    </configuration>

With that in mind, let's look at scripting those settings.

Using AppCmd

AppCmd.exe is a great utility that ships with IIS 7, which allows editing the configuration settings for IIS from a command line. This also allows you to create batch scripts that automate large numbers of configuration changes. For example, the following batch file enables ASP session state, sets the maximum number of ASP sessions to 1000, and then sets the session time-out to 10 minutes for the Default Web Site:

appcmd.exe set config "Default Web Site" -section:system.webServer/asp /session.allowSessionState:"True" /commit:apphost

appcmd.exe set config "Default Web Site" -section:system.webServer/asp /session.max:"1000" /commit:apphost

appcmd.exe set config "Default Web Site" -section:system.webServer/asp

I'm a big fan of IIS 7's AppCmd.exe, but unfortunately it has two rather large limitations:

  • AppCmd.exe does not directly support clearing the contents of a configuration section. (But there's a workaround that I list below.)
  • AppCmd.exe does not support removing an item from a collection.

These limitations have caused me some grief from time to time, because I often want to script the modification of collections, and I would love to remove items or clear a collection.

How to add a <clear /> element using AppCmd:

Although it's kind of a hack, there is a way to force AppCmd.exe to add a <clear /> element.

Here's what you need to do in order to clear the list of default documents for the Default Web Site:

  1. Create an XML file like the following and save it as "CLEAR.XML":
    <?xml version="1.0" encoding="UTF-8"?>
    <appcmd>
        <CONFIG CONFIG.SECTION="system.webServer/defaultDocument" path="MACHINE/WEBROOT/APPHOST" overrideMode="Allow" locked="false">
            <system.webServer-defaultDocument  enabled="true">
                <files>
                    <clear />
                </files>
            </system.webServer-defaultDocument>
        </CONFIG>
    </appcmd>
  2. Run the following command:
    appcmd.exe set config /in "Default Web Site" < CLEAR.xml

Unfortunately this technique does not work for <remove /> elements. :-( But that being said, you can add a <remove /> element through VBScript; for more information, see the Using VBScript section.

Using VBScript

Fortunately, VBScript doesn't have AppCmd.exe's limitations, so you can add both <clear /> and <remove /> elements.

How to add a <clear /> element in VBScript:

The following steps will clear the list of default documents for the Default Web Site:

  1. Save the following VBScript code as "clear.vbs":
    Set adminManager = WScript.CreateObject("Microsoft.ApplicationHost.WritableAdminManager")
    adminManager.CommitPath = "MACHINE/WEBROOT/APPHOST/Default Web Site"
    Set defaultDocumentSection = adminManager.GetAdminSection("system.webServer/defaultDocument", _
      "MACHINE/WEBROOT/APPHOST/Default Web Site")
    Set filesCollection = defaultDocumentSection.ChildElements.Item("files").Collection
    filesCollection.Clear
    adminManager.CommitChanges
  2. Run the VBscript code by double-clicking the "clear.vbs" file.

How to add a <remove /> element in VBScript:

The following steps will remove a single item from the list of default documents for the Default Web Site:

  1. Save the following VBScript code as "remove.vbs":
    Set adminManager = WScript.CreateObject("Microsoft.ApplicationHost.WritableAdminManager")
    adminManager.CommitPath = "MACHINE/WEBROOT/APPHOST/Default Web Site"
    Set defaultDocumentSection = adminManager.GetAdminSection("system.webServer/defaultDocument", _
      "MACHINE/WEBROOT/APPHOST/Default Web Site")
    Set filesCollection = defaultDocumentSection.ChildElements.Item("files").Collection
    addElementPos = FindElement(filesCollection, "add", Array("value", "index.html"))
    If (addElementPos = -1) Then
       WScript.Echo "Element not found!"
       WScript.Quit
    End If
    filesCollection.DeleteElement(addElementPos)
    adminManager.CommitChanges
    
    Function FindElement(collection, elementTagName, valuesToMatch)
       For i = 0 To CInt(collection.Count) - 1
          Set element = collection.Item(i)
          If element.Name = elementTagName Then
             matches = True
             For iVal = 0 To UBound(valuesToMatch) Step 2
                Set property = element.GetPropertyByName(valuesToMatch(iVal))
                value = property.Value
                If Not IsNull(value) Then
                   value = CStr(value)
                End If
                If Not value = CStr(valuesToMatch(iVal + 1)) Then
                   matches = False
                   Exit For
                End If
             Next
             If matches Then
                Exit For
             End If
          End If
       Next
       If matches Then
          FindElement = i
       Else
          FindElement = -1
       End If
    End Function
  2. Run the VBscript code by double-clicking the "remove.vbs" file.

More Information

For more information about scripting and IIS configuration settings, see the following:


Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/

AppCmd 80070057 errors when configuring site-level settings

I had an interesting question from a coworker who was trying to use AppCmd to set the site-level SSL options for an FTP site. This should have been straightforward, and the syntax that he gave me looked correct:

appcmd.exe set config -section:system.applicationHost/sites -[name='Default FTP Site'].ftpServer.security.ssl.controlChannelPolicy:SslAllow /commit:apphost

That being said, whenever he or I ran the command we received the following cryptic error from AppCmd:

Failed to process input: The parameter 'Site'].ftpServer.security.ssl.controlChannelPolicy='SslAllow'' must begin with a / or - (HRESULT=80070057).

The HRESULT=80070057 code can mean either "One or more arguments are invalid" or "The parameter is incorrect", which seemed wrong to me because the arguments looked correct. Based on the error message referring to the word 'Site', I retried the command using the site ID instead of the site name:

appcmd.exe set config -section:system.applicationHost/sites -[id='4'].ftpServer.security.ssl.controlChannelPolicy:SslAllow /commit:apphost

This worked as expected, so I knew that somehow the problem was with the site name.

I searched around and I found a forum post on IIS.NET where Anil Ruia had stated that when the site name has a space in it you should enclose the entire parameter statement in quotes. Armed with that knowledge, I tried the following command:

appcmd.exe set config -section:system.applicationHost/sites "-[name='Default FTP Site'].ftpServer.security.ssl.controlChannelPolicy:SslAllow" /commit:apphost

This fixed the problem and the command worked as I would have originally expected.

By the way, in general you should be able use the following command to get the FTP syntax listing for an area:

appcmd.exe set config -section:system.applicationHost/sites -? | find /i "ftp"

This wouldn't have helped my coworker identify the problem with the "name" parameter, but it would have helped by giving him the syntax for using the "id" parameter.


Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/