Luis Guerrero EN

Archive for April, 2011

Run elevated task at any point of the Azure Rol lifetime.

by on Apr.30, 2011, under .NET, Azure, wcf

There are two mostly common used for Windows Azure, starting a new 100% .NET project on Windows Azure, or migrate a legacy project to the cloud. Talking from this point legacy project could be the most difficult project of all, because can use COM components during him lifetime. As you may know all the Windows Azure roles are clean machine so it’s means that you need to install manually all your component COM. Normally this issue is solved by placing on the startup node of the Role configuration file, a .cmd file with all the registration of the COM components.

But what happens if during role’s lifetime you need to execute a task elevated? You can’t do that because the hosting process of the worker role and web role is not executing with elevated rights. So we have to find a place where we can execute task with elevated rights, and this places is the roll’s startup node. So what we have to do is have a sentinel program up and running at roll’s startup and make this sentinel program to accept requests to execute process.

So this is exactly what we are going to do, using WCF to open a Windows pipe to enable communication between two different process in the same machine, enabling the process to send message with the necessary information to run this elevated task.

Let’s do it!

Service definition.

Since all we need is a WCF service be exposed on NetNamedPipeBinding, first of all we have to define a service contract with the operation contract we need. In this case we only need one operation, ExecuteTask.

[ServiceContract(Namespace = "http://azure.plainconcepts.com/schemas/04/2011/azure/executionhost")]
public interface IExecutionHost
{
    [OperationContract]
    void ExecuteTask(ProcessTask host);
}

Once we define the service contract now is the time to create the service itself, who is responsible to finally execute the tasks.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class ExecutionHostService : IExecutionHost
{
    public void ExecuteTask(ProcessTask host)
    {
        Process process = new Process();
        process.StartInfo = host.StartInfo;
        process.Start();
    }
}

When we have done this, the next step is host the server itself on the sentinel process that will handle the requests, as we said before we’re going to use the NetNamedPipeBinding.

public class ExecutionHostServiceManager
{
    public ExecutionHostServiceManager()
    {
        service = new ExecutionHostService();
        ServiceHost host = new ServiceHost(service);

        string address = "net.pipe://PlainConcepts/Azure/ExecutionHost";
        NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
        host.AddServiceEndpoint(typeof(IExecutionHost), binding, address);

        // Add a mex endpoint
        long maxBufferPoolSize = binding.MaxBufferPoolSize;

        int maxBufferSize = binding.MaxBufferSize;

        int maxConnections = binding.MaxConnections;

        long maxReceivedMessageSize =
            binding.MaxReceivedMessageSize;

        NetNamedPipeSecurity security = binding.Security;

        string scheme = binding.Scheme;

        XmlDictionaryReaderQuotas readerQuotas =
            binding.ReaderQuotas;

        BindingElementCollection bCollection = binding.CreateBindingElements();

        HostNameComparisonMode hostNameComparisonMode =
            binding.HostNameComparisonMode;

        bool TransactionFlow = binding.TransactionFlow;

        TransactionProtocol transactionProtocol =
            binding.TransactionProtocol;

        EnvelopeVersion envelopeVersion =
            binding.EnvelopeVersion;

        TransferMode transferMode =
            binding.TransferMode;
        host.Open();
    }

    private ExecutionHostService service;
}

And that is!

Now we have to put this together in a console application and wait forever.

class Program
{
    static void Main(string[] args)
    {
        new ExecutionHostServiceManager();
        Thread.Sleep(Timeout.Infinite);
    }
}

Making call to the service

We now have the service contract, the service definition and the hosting process, now it’s time to define the client and make call to the servicer. Since we’re using WCF we need to define a class that will handle the request to the pipe. To accomplish this it’s necessary to inherit from ClientBase<T>, and T need to the service contract of the service.

public class ExecutionHostClient : ClientBase<IExecutionHost>
{
    static ExecutionHostClient()
    {
        string address = "net.pipe://PlainConcepts/Azure/ExecutionHost";
        NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
        binding.CloseTimeout = TimeSpan.MaxValue;
        binding.ReceiveTimeout = TimeSpan.MaxValue;
        binding.SendTimeout = TimeSpan.MaxValue;
        EndpointAddress endpoint = new EndpointAddress(address);
        client = new ExecutionHostClient(binding, endpoint);
    }

    public ExecutionHostClient(Binding binding, EndpointAddress remoteAddress) :
        base(binding, remoteAddress)
    {
    }

    public void ExecuteTask(ProcessTask task)
    {
        Channel.ExecuteTask(task);
    }

    public static void ExecuteRemoteTask(ProcessTask task)
    {
        client.ExecuteTask(task);
    }

    private static ExecutionHostClient client;
}

It’s important to create this proxy with the same configuration of the server, because we’re not using the default Visual Studio code generated proxy, and it’s our responsibility to create the binding and the address of the endpoint.

Invoking services

In this example we are registering COM components by calling the regsvc32.exe in code. We looking for dll files on a known folder of our Windows Azure Solution and invoke the service to make the elevated call.

public class RegisterComHelper
{
    public RegisterComHelper()
    {

    }

    public void Register()
    {
        // hay que buscar la localizacion en el servidor de azure de donde estan los ensamblados
        // como no sabemos donde estan los ficheros tenemos que buscar el modulo 
        // Habitania.RegisterCom.dll que es especifico para este ejemplo
        // así nos aseguramos que estamos buscando la dll correcta
        Process current = Process.GetCurrentProcess();
        var found = (from p in current.Modules.Cast<ProcessModule>().ToList()
                     where p.ModuleName == "PlainConcepts.Azure.WorkerRoleDemo.dll"
                     select p).FirstOrDefault();

        if (found != null)
        {
            // a partir de la locacion del modulo cargada por el proceso 
            // somos capaces de encontrar la informacion del directorio y buscar
            // la carpeta dlls que contiene la lista de dlls que queremos registar
            string directoryLocation = Path.GetDirectoryName(found.FileName);

            string dllPath = Path.Combine(directoryLocation, "V3COM30");

            string[] files = Directory.GetFiles(dllPath);

            foreach (var item in files)
            {
                if (item.EndsWith(".dll"))
                    RegisterComObject(item);
            }

            dllPath = Path.Combine(directoryLocation, "V3COM");

            files = Directory.GetFiles(dllPath);

            foreach (var item in files)
            {
                if (item.EndsWith(".dll"))
                    RegisterComObject(item);
            }
        }
    }

    private void RegisterComObject(string filePath)
    {
        ProcessStartInfo info = new ProcessStartInfo();
        info.FileName = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.System),
            "regsvr32.exe");
        info.Arguments = string.Format("/i {0}", filePath);
        info.UseShellExecute = false;

        ExecutionHostClient.ExecuteRemoteTask(new ProcessTask()
        {
            StartInfo = info
        });
    }
}

This way of invoking task on Windows Azure seems a little bit complicated, but once is done can be super flexible and permit to extend this demo with custom task and background tasks.

The complete source code can be downloaded from here.

Luis Guererro.

87 Comments :, , , , , more...

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!