Search code examples
azureazure-cloud-services

Azure Cloud Service Package - Cant find Physical/Virtual Path on hosted VSTS build server


I am using a hosted build agent on VSTS, to build and deploy a Cloud Service.

When packaging the Cloud Service, cspack.exe complains that it cannot find the physical directory for a virtual path.

....
  <WebRole name="MySite" vmsize="Small">
    <Sites>
      <Site name="Web">
        <VirtualDirectory name="media" physicalDirectory="Z:\MySiteMedia" />
        <Bindings>
....

ServiceDefinition.csdef: Error CloudServices079 : Cannot find the physical directory 'Z:\MySiteMedia' for virtual path Web/media/.

It is true that the physical directory does not exist on the build server, but it exists on the server that the Cloud Service is going to be deployed to. It's an Azure File Share.

Is there a workaround?

Update

It is possible to create a virtual drive on the build agent, which has the required drive letter and folder. The drive could be pointing to another local drive. For intance, Z:\ could be pointing at C:\

Example:

net use Z: \\localhost\c$\MySiteMedia

This fixes the build issue, however, it doesnt solve the actual problem.

After some digging, I found out that cspack does not work as I originally thought. When adding a

<VirtualDirectory>

to the ServiceDefition, it takes the contents of that folder and packages it in the cspkg file. Then it creates a new folder, which it places in the siteroot folder, and then creates a virtual directory to that new folder.

Example:

enter image description here

Becomes

enter image description here

Which means that the Azure File Share on Z:\ will not be used.

The workaround is to create add the virtual directory to IIS after the role has been deployed, by using a RoleEntryPoint.


Solution

  • I solved the problem by creating a RoleEntryPoint, that runs initialization code for my role. This happens after the role has been deployed. The code can be placed in any folder in the role itself.

    Microsoft.Web.Administration is used to add the virtual directory to IIS. I had some problems using Microsoft.Web.Administration from NuGet, because it references a CORE project, which gives a runtime exception when deployed to the Cloud Service:

    (System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime, Version=4.1.0.0, Culture=neutral..)

    So I referenced the Microsoft.Web.Administration.dll from C:\Windows\System32\inetsrv\ - to get the DLL you need to install IIS on your development machine.

    The code adds the virtual directory to the first site on IIS. This works for me, if you have several sites, you would need to modify it according to your needs.

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using Microsoft.Web.Administration;
    using Microsoft.WindowsAzure.ServiceRuntime;
    using Newtonsoft.Json;
    
    namespace MyNamespace.Azure
    {
        public class WebRole : RoleEntryPoint
        {
            public override bool OnStart()
            {
                try
                {
                    // Add initialization code here
                    var serverManager = new ServerManager();
                    var site = serverManager.Sites.First();
                    var application = site.Applications.First();
    
                    Trace.WriteLine($"First site: {site.Name} ");
                    Trace.WriteLine($"First application path for site {site.Name}: {application.Path}");
    
                    var vDirs = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(@"Azure\VirtualDirectories.json"));
    
                    var vDirsToDelete = new List<VirtualDirectory>();
                    foreach (var vDir in vDirs)
                    {
                        var virtualDirectories = application.VirtualDirectories.Where(x => x.Path == vDir.Key).ToList();
                        vDirsToDelete.AddRange(virtualDirectories);
                    }
    
                    if (vDirsToDelete.Any())
                    {
                        foreach (var vDir in vDirsToDelete)
                        {
                            Trace.WriteLine($"Removing existing media virtual directory");
                            application.VirtualDirectories.Remove(vDir);
                        }
                    }
    
                    foreach (var vDir in vDirs)
                    {
                        Trace.WriteLine($"Adding virtual directory. Address: {vDir.Key}, PhysicalPath: {vDir.Value}");
                        application.VirtualDirectories.Add(vDir.Key, vDir.Value);
                    }
    
                    serverManager.CommitChanges();
    
                }
                catch (Exception e)
                {
                    Trace.WriteLine("Exception during OnStart: " + e);
                    // Take other action as needed.
                }
    
                return base.OnStart();
            }
        }
    }
    

    VirtualDirectories.json:

    {
      "media": "Z:\\MySiteMedia"
    }