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.