Azure
Run elevated task at any point of the Azure Rol lifetime.
by Guerrerotook 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.
Azure worker role throw a FileLoadException on load
by Guerrerotook on May.11, 2010, under Azure, Debugging
I’m working now on a Smooth Streaming Silverlight player for the world cup and we using Azure to host some services for the player. Recently I started the development of all those services on a Web project and when I’m trying to run the project inside the Azure Simulation Environment the web role throw me a FileLoadException.
[runtime] Role entrypoint could not be created: System.IO.FileLoadException: Could not load file or assembly 'Microsoft.Silverlight.MediaPlayer.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a10dc7bdece43c5c' or one of its dependencies. The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047) File name: 'Microsoft.Silverlight.MediaPlayer.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a10dc7bdece43c5c' ---> System.IO.FileLoadException: Could not load file or assembly 'file:///C:\Plain\Microsoft.SmoothPlayer\Source\Azure\Microsoft.Silverlight.MediaPlayer.Online\bin\Debug\Microsoft.Silverlight.MediaPlayer.Online.csx\roles\Microsoft.Silverlight.MediaPlayer.Web\approot\bin\Microsoft.Silverlight.MediaPlayer.Web.dll' or one of its dependencies. The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047) File name: 'file:///C:\Plain\Microsoft.SmoothPlayer\Source\Azure\Microsoft.Silverlight.MediaPlayer.Online\bin\Debug\Microsoft.Silverlight.MediaPlayer.Online.csx\roles\Microsoft.Silverlight.MediaPlayer.Web\approot\bin\Microsoft.Silverlight.MediaPlayer.Web.dll' WRN: Assembly binding logging is turned OFF. To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1. Note: There is some performance penalty associated with assembly bind failure logging. To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog]. at System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) at System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) at System.Reflection.Assembly.LoadFrom(String assemblyFile) at Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.CreateRoleEntryPoint(RoleType roleTypeEnum) at Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.InitializeRoleInternal(RoleType roleTypeEnum)
I double check that my local assembly Microsoft.Silverlight.MediaPlayer.Web.dll is there and have enough permission to read and load it. Also I enabled Fusion Log to catch the assembly fail load but I can’t find any references to my assembly… so I decided it’s time to WinDBG.
I attach to the process WaWebHost.exe (Microsoft Windows Azure Web Host), loaded all symbols and sos. The first command I wrote was !threads and I found this:
0:000> !threads
ThreadCount: 6
UnstartedThread: 0
BackgroundThread: 5
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
PreEmptive Lock
ID OSID ThreadOBJ State GC GC Alloc Context Domain Count APT Exception
XXXX 1 1ba0 000000000014c4a0 2008220 Enabled 00000000053a1c10:00000000053a1fd0 0000000002f182a0 1 Ukn System.IO.PathTooLongException (0000000005305a80)
XXXX 2 294 00000000000e46d0 b220 Enabled 00000000053d09f8:00000000053d1fd0 0000000002f17990 0 MTA (Finalizer)
XXXX 3 13b0 00000000022f9350 a802220 Enabled 0000000000000000:0000000000000000 0000000002f17990 0 MTA (Threadpool Completion Port)
XXXX 4 1a80 0000000002fbc080 80a220 Enabled 0000000000000000:0000000000000000 0000000002f17990 0 MTA (Threadpool Completion Port)
XXXX 5 1ae0 0000000002fbe650 1220 Enabled 0000000000000000:0000000000000000 0000000002f17990 0 Ukn
0 6 7e4 0000000002fe22c0 1020 Enabled 00000000053c69b0:00000000053c7fd0 0000000002f182a0 0 Ukn System.IO.FileLoadException (00000000053a7ca8)
As you can see threre are two applications domain inside this process and one of them (0000000002f182a0) have to exception in thread #1 and #6.
The exception in Thread #6 is familiar but the other thread has a System.IO.PathTooLongException.
0:001> !pe 0000000005305a80 Exception object: 0000000005305a80 Exception type: System.IO.PathTooLongException Message: The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters. InnerException: <none> StackTrace (generated): SP IP Function 00000000031BD360 000007FEED2D5A8F mscorlib_ni!System.IO.Path.NormalizePathFast(System.String, Boolean)+0xd0f 00000000031BD420 000007FEED239CA6 mscorlib_ni!System.IO.File.Exists(System.String)+0x96 StackTraceString: <none> HResult: 800700ce The current thread is unmanaged
So it’s seem that my path it’s too long to be loaded by the runtime!
Hope this help!
Luis Guerrero.