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.
Pingback: Azure FAQ: Can I write to the file system on Windows Azure? « Coding Out Loud
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
Not sure what the error signifies, but might be due to partial state of service installation. Here are a few tips on making sure that your service is shutdown and restarted in the dev environment: https://blog.codingoutloud.com/2011/08/21/four-tips-for-developing-windows-services-more-efficiently/
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
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.
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