Writing to Azure Local Storage from a Windows Service

As you may know, Windows Azure roles do not generally write freely to the file system. Instead of hard-coding a path into our code, we declare in our service model that we plan to write data to disk, and we supply it with a logical name. We can declare multiple such logical names. Windows Azure uses these named locations to provide us managed local, writeable folders which it calls Local Storage.

To specificy your intent to use a Local Storage location, you add an entry to ServiceDefinition.csdef under the specific role from which you plan to access it. For example:

<LocalStorage name="LocalTempFolder" sizeInMB="11"
      cleanOnRoleRecycle="true" />

You can read more about the details over in Neil Mackenzie’s post, but the main thing you need to do is call a method to access the full path to the read/write folder associated with the name you provided (e.g., “LocalTempFolder” in the config snippet above). The method call looks like this:

LocalResource localResource =
      RoleEnvironment.GetLocalResource("LocalTempFolder");
var pathToReadWriteFolder = localResource.RootPath;
var pathToFileName = pathToReadWriteFolder + "foo.txt";

Now you can use “the usual” classes to write and read these files. But calling RoleEnvironment.GetLocalResource only works from within the safe confines of a your Role code – as in a Worker Role or Web Role – you know, the process that inherits from (and completes) the RoleEntryPoint abstract class. What happens if I am inside of a Windows Service?

Your Windows Service Has Super Powers

Well… your Windows Service does not exactly have Super Powers, but it does have powers and abilities far above those of ordinary Roles. This is due to the differences in their security contexts. Your Windows Service runs as the very powerful LocalSystem account, while your Roles run as a lower priviledge user. Due to this, your Windows Service can do things your Role can’t, such as write to the file system generally, access Active Directory commands, and more.

[Your Startup Tasks might also have more powers than your Roles, if you configure them to run with elevated privileges using executionContext=”elevated” as in:
<Task commandLine=”startuptask.cmd” executionContext=”elevated” />
See also David Aiken’s post on running a startup task as a specific user.]

However, there are some things your Windows Service can’t do, but that your Role can: access RoleEnvironment!

Problem Querying Local Storage from a Windows Service

Inside of a Windows Service (which is outside of the Role environment), the RoleEnvironment object is not populated. So, for example, you cannot call

RoleEnvironment.GetLocalResource("LocalTempFolder")

and expect to get a useful result back. Rather, an exception will be raised.

But here’s a trick: it turns out that calling RoleEnvironment.GetLocalResource returns the location of the folder, but it is just the location of a folder on disk at this point – this folder can be accessed by any process that knows about it. So how about if your Web  or Worker Role could let the Windows Service know where its storage location happens to be? (As an aside, we have a good idea where they might ultimately end up on disk in practice (see last section of this post) – but of course subject to variability and change – but it is useful if you want to poke around on your local machine or through Remote Desktop to an instance in the cloud.)

The Trick: Pass the Local Storage location into your Windows Service

If you are deploying a Windows Service along with your Role, you will need to install the Windows Service and you will need to start the Windows Service. A reasonable way to install your Windows Service is to use the handy InstallUtil.exe program that is included with .NET. Here is how you might invoke it:

%windir%\microsoft.net\framework\v4.0.30319\installutil.exe
      MyWindowsService.exe

Now the Windows Service is installed, but not running; you still need to start it. Here is a reasonable way to start it:

net start MyWindowsServiceName

Typically, both the InstallUtil and net start commands would be issued (probably in a .bat or .cmd file) from a Startup Task. But there is another way to start an installed Windows Service which allows some additional control over it, such as the ability to pass it arguments. This is done with a few lines of code from within the OnStart method of your Role, such as in the following code snippet which uses the .NET ServiceController class to get the job done:

var windowsServiceController =
      new System.ServiceProcess.ServiceController
            ("MyWindowsServiceName");
System.Diagnostics.Debug.Assert(
      windowsServiceController.Status ==  
      windowsServiceControllerStatus.Stopped);
windowsServiceController.Start();

Putting together both acquiring the Local Storage location and starting the Windows Service, your code might look like the following:

string[] args = { 
      RoleEnvironment.GetLocalResource
            ("LocalTempFolder").RootPath
      }
var windowsServiceController =
      new System.ServiceProcess.ServiceController  
            ("MyWindowsServiceName");
System.Diagnostics.Debug.Assert(
      windowsServiceController.Status == 
      windowsServiceControllerStatus.Stopped);
// pass in Local Storage location
windowsServiceController.Start(args);

Within your Windows Service’s OnStart method you will need to pick up the arguments passed in, which at that point has nothing specific to Azure. Your code might look like the following:

protected override void OnStart(string[] args)
{
   var myTempFolderPath = args[0];
   // ...
}

That oughta do it! Please let me know in the comments if you find this useful.

6 thoughts on “Writing to Azure Local Storage from a Windows Service

  1. Pingback: Azure FAQ: Can I write to the file system on Windows Azure? « Coding Out Loud

  2. jnana

    var windowsServiceController =
    new System.ServiceProcess.ServiceController
    (“MyWindowsServiceName”);

    throws exception but it works in azure emulator

    System.InvalidOperationException: Cannot open MyWindowsServiceNameservice on computer ‘.’. —> System.ComponentModel.Win32Exception: Access is denied — End of inner exception stack trace — at System.ServiceProcess.ServiceController.GetServiceHandle(Int32 desiredAccess) at System.ServiceProcess.ServiceController.Start(String[] args) at LifeLineWorkerRole.WorkerRole.Run() in

    Reply
  3. jnana

    Hi,
    Actually I want to pass some argument from worker role to windows service
    I have used following command to install windows service in cmd files

    REM The following directory is for .NET 2.0
    set DOTNETFX2=%SystemRoot%\Microsoft.NET\Framework\v4.0.30319
    set PATH=%PATH%;%DOTNETFX2%

    net stop ReverseGeocoding > ReverGeocoding.txt
    InstallUtil /u Services.exe > Uninstallserice.txt
    InstallUtil /i Services.exe > InstallService.txt

    I want to start it in worker role

    var windowsServiceController =
    new System.ServiceProcess.ServiceController
    (“ReverseGeocoding “);
    if (windowsServiceController.Status == ServiceControllerStatus.Stopped)
    {
    // pass in Local Storage location
    windowsServiceController.Start(args);
    }
    It works in local but shows following exception in azure

    System.InvalidOperationException: Cannot open ReverseGeocoding service on computer ‘.’. —> System.ComponentModel.Win32Exception: Access is denied — End of inner exception stack trace — at System.ServiceProcess.ServiceController.GetServiceHandle(Int32 desiredAccess) at System.ServiceProcess.ServiceController.Start(String[] args) at

    Reply
    1. Bill Wilder Post author

      Two ideas for you @jnana:

      1. Is your startup task running elevated, as in (I’m leaving off the angle brackets so WordPress does not get angry):

      Task commandLine=”startuptask.cmd” executionContext=”elevated”

      2. Can you RDP into the machine and start the service manually? Specifically, try running the .cmd file manually.

      Reply
      1. jnana

        Hi
        1. yes startuptask is running on elevated
        2. We can do RDP and start the service. But we want to start service programatically, as it described in this article

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.