3

My Azure Web Role needs to be able to delete temporary local files stored in a sub-folder of App_Data. I want to use ICACLS in an Azure elevated startup task to allow IIS to do this as follows:

ICACLS App_Data /grant "IIS_IUSRS":(OI)(CI)F

However, my startup task executes in:

E:\approot\bin

Whereas the root folder where the web application actually ends up in and is executed from seems to be:

E:\sitesroot\0

I’m reluctant to hardcode this path in case Microsoft changes this. Is there a way to get this path from within the startup task or can I rely on this destination?

To test this in an ASPX I add:

Label1.Text = "MapPath: " + Server.MapPath("~/");
Label2.Text = "RoleRoot: " + Environment.GetEnvironmentVariable("RoleRoot");

When I run this on a deployed instance, I get:

MapPath: E:\sitesroot\0\ RoleRoot:

i.e. RoleRoot is empty.

So how can I get the result of Server.MapPath("~/"); in a startup task?

David Makogon
  • 69,407
  • 21
  • 141
  • 189
Mister Cook
  • 1,552
  • 1
  • 13
  • 26

4 Answers4

3

Instead of using icacls from a command prompt, use a Powershell script, as Powershell allows you to enumerate web sites in IIS and get the physical folder path.

Unfortunately, you cannot set a Powershell script as a startup task. So you'll have to create a regular CMD or BAT file, and then in this file ask Powershell to execute your script.

The CMD file:

PowerShell -ExecutionPolicy Unrestricted .\Setup.ps1 >> %TEMP%\Setup-DebugLog.txt 2>&1
exit /B 0

The first line executes the Powershell script and saves all output to the file Setup-DebugLog.txt. The second line makes sure that the CMD file returns an OK back to Windows Azure, so Azure knows that everything went well in the startup script.

Here is the Powershell script that I use to set folder permissions on the App_Data folder:

Import-Module WebAdministration
cd IIS:\Sites
$dir = Get-ChildItem
$timeout = 0
while ($dir -eq $NULL -and $timeout -lt 11)
{
    "IIS Site not ready. Waiting for 2 seconds..."
    [System.Threading.Thread]::Sleep(2000)
    $timeout++
    $dir = Get-ChildItem
}

if ($dir -eq $NULL)
{
    "IIS Site still not ready. Aborting."
}
else
{
    "IIS site ready."
    Set-Location $dir.physicalPath
    "Location is $($dir.physicalPath)"
    $acl = (Get-Item App_Data).GetAccessControl("Access")
    $rule = New-Object System.Security.AccessControl.FileSystemAccessRule("Network Service", "Modify, Write", "ContainerInherit, ObjectInherit", "None", "Allow")
    $acl.AddAccessRule($rule)
    Set-Acl App_Data $acl
    "Permission added."
}

It starts by importing the WebAdministration module, that allows Powershell to interact with IIS. Then it tries to enumerate the web sites. But, here's an interesting challenge. Because, a startup task runs before your web site has been deployed to IIS. So you need to set your startup task to run as a background task, and then wait until the site has been deployed. So the code tries to enumerate the web sites, and if no web site is found, it waits for 2 seconds and then tries again. It does this for 20 seconds total and then times out. If the web site is ready, then the physical path is found and a FileSystemAccessRule is added to the ACL of the App_Data folder. You can modify the permissions added to suit your needs. Note that this script expects there to be only 1 site in IIS. If you deploy more than 1 web site, this script might not work and you'll have to adapt the code.

For completeness, here is the XML you should add to your ServiceDefinition.csdef:

<Startup>
  <Task commandLine="Setup.cmd" executionContext="elevated" taskType="background" />
</Startup>

Both Setup.cmd and Setup.ps1 should be placed in the root of your web site. If you would like them to be placed in a subfolder of your web site, you can do that, but then you'll have to update the commandLine attribute in the Task element to "Subfolder\Setup.cmd", and you'll need to update the path to the Powershell file in the CMD file:

PowerShell -ExecutionPolicy Unrestricted .\Subfolder\Setup.ps1 >> %TEMP%\Setup-DebugLog.txt 2>&1

Also, note that there is some differences in calling the Powershell script between Powershell v1 and v2. If I recall correctly, Windows 2008 on Windows Azure runs Powershell v1, and Windows 2008 R2 and newer runs Powershell v2. So, if you're using Windows 2008 on Azure, you'll need to change the line that calls the Powershell script, as there is some difference in setting the execution policy.

René
  • 9,880
  • 4
  • 43
  • 49
3

Why not get it relatively? The drive that both sitesroot and approot are deployed to seems to toggle between E:\ and F:\ but I think it's a safer bet for now to assume they will be deployed as sibling folders.

Here's a startup task we have that copies files from sitesroot folders:

xcopy "../../sitesroot/0/bin" "../../sitesroot/1/bin" /y /i /S
xcopy "../../sitesroot/0/bin" "../../sitesroot/2/bin" /y /i /S
xcopy "../../sitesroot/0/bin" "../../sitesroot/3/bin" /y /i /S
xcopy "../../sitesroot/0/bin" "../../sitesroot/4/bin" /y /i /S
xcopy "../../sitesroot/0/bin" "../../sitesroot/5/bin" /y /i /S
Nariman
  • 6,368
  • 1
  • 35
  • 50
  • Best answer - I successfully used relative paths to access sitesroot/N from a startup task. Had permission issues getting Powershell script working so I'm using a vanilla CMD file, oldskool :) – Dunc Oct 29 '14 at 11:11
2

There is an environment variables called %ROLEROOT% which gets the path the your application.

string appRoot = Environment.GetEnvironmentVariable("RoleRoot");

appRoot = Path.Combine(appRoot + @"\", @"approot\");

Read more about this here.

Matthew Manela
  • 16,572
  • 3
  • 64
  • 66
1

No, I don't think there's a way from a startup task to get the root of your web site(s) in a web role.

user94559
  • 59,196
  • 6
  • 103
  • 103