Using the IIS web app manage task in Azure DevOps offers an easy and idempotent way of defining your web application on an IIS server. You can think of it as Application Infrastructure As Code. This task defines how the application should be setup/configured on an existing IIS server. It allows for a deployment to a new IIS server to seamlessly work.
What if you need to add a virtual directory as part of this setup? An upload folder for example located outside the web applications virtual directory.
Let’s take a look at three options, using the “Additional appcmd.exe commands” in the IIS web app manage task itself, using a second IIS web app manage task or a PowerShell task.
Of these options the “Additional appcmd.exe commands” immediately seems the way to go since it’s part of the setup task for the web app.
TL;DR – Use a second IIS web app manage task for most cases.
Using the “Additional appcmd.exe commands” in the IIS web app manage task itself
I really want to like this option. However, this option is too limiting to work for a scenario to add or change the virtual. The IIS web app manage task itself is idempotent*. It will add if an object doesn’t exist, change it if it does. * (maybe not fully idempotent…)
A naïve appcmd to use:
add vdir /app.name:”mywebsitename/mywebapp” /path:”/content” /physicalpath:"c:\somefolderpath”
This will work the first time but subsequent deploys will fail with duplicate collection element “/content”.
You could change it to be a set statement but that will fail if the virtual directory doesn’t exist.
The text area only allows for appcmd commands but these commands can be piped together and the appcmd has the /in parameter to pass items to the next command in the pipe. Since I’m trying to setup the release to be idempotent as much as possible this is looking a bit grim.
What about Pipes and /in for appcmd?
So, let’s see if the virtual exists and set it if it does.
list vdir /physicalpath:"c:\somefolderpath" /xml | set vdir /in /physicalpath:"c:\somefolderpath" /path:"/content"
Great, that works. However, what if the virtual doesn’t exist? This could be my own failing but it doesn’t appear possible to pipe this out in a way to add. It also doesn’t appear possible to if/else to do a set or an add with pipes and the appcmd.
What about piping to other functions? How about list piped to find with an or to add for the not found case.
list vdir /physicalPath:"c:\somefolderpath" | findstr "somefolderpath" || add vdir /app.name:"mywebsitename/mywebapp" /path:"/content" /physicalpath:"c:\somefolderpath"
You’ll get errors about invalid tokens. You can only use appcmd commands in this text area.
While one can pipe appcmd’s together and use the /in parameter for input to the next pipe, the inability (or at least my inability) to change the appcmd from an add if the virtual doesn’t exist to a set if it does makes this a no go.
What about Continue On Error and Run this task Even if a previous task failed combination?
Well, one could include add and set as two different lines in the text area and set Continue On Error for the task and a downstream task to run Even if a previous task failed. However, this has the downside of hiding real errors with this task. Plus, I can’t stand seeing the partially succeeded icon. It always make me think something is broken and must be addressed.
I even tried to NUL out the output to swallow the error.
add vdir /app.name:”mywebsitename/mywebapp” /path:”/content” /physicalpath:"c:\somefolderpath” > NUL
No go, errors. So, at the moment, I don’t see this as a viable option for adding or setting a virtual directory.
Using a second IIS web app manage task
You can have more than one IIS web app manage task. Let’s create a second one after the initial task to setup the web app.
On this task, select for the Configuration type, IIS Virtual Directory.
The parameters might be a bit confusing if you know you way around the appcmd but for the parameters as used in above examples:
Parent website name: mywebsitename
Virtual Path: /mywebapp/content
Physical Path: c:\somefolderpath
Since the task is smart enough to add or change we have achieved the desired result quickly and easily.
If you happen to be using YAML, below is the equivalent commands for the IIS Web App Manage Task:
- task: IISWebAppManagementOnMachineGroup@0
displayName: 'Manage IISVirtualDirectory - Add or Set content virtual directory'
Using a PowerShell task
I like PowerShell. I’m not the best at it but I use it often for automation tasks around DevOps. So how would we accomplish with a PowerShell task.
The code below was heavily inspired by the source for the IIS web app manage task.
c:\windows\system32\inetsrv\appcmd.exe list vdir /vdir.name:$vdirNameToFind
$vdir=c:\windows\system32\inetsrv\appcmd.exe list vdir /vdir.name:$vdirNameToFind
if($vdir -ne $null -and $vdir -like "*`"$vdirNameToFind`"*") # like needed as vdir name can match parent vdirs
Write-Host "virtual found, updating"
c:\windows\system32\inetsrv\appcmd.exe set vdir /vdir.name:$vdirNameToFind /path:"/content" /physicalPath:"c:\somefolderpath"
Write-Host "virtual not found, adding"
c:\windows\system32\inetsrv\appcmd.exe add vdir /app.name:"mywebsitename/mywebapp" /path:"/content" /physicalPath:"c:\somefolderpath"
The source from the IIS web app manage task has a far better flushed out solution so I recommend using the task but maybe one needs to do other work in the PowerShell task and having it all it scripted is desired.