IanG on Tap

Ian Griffiths in Weblog Form (RSS 2.0)

Blog Navigation

April (2018)

(1 item)

August (2014)

(1 item)

July (2014)

(5 items)

April (2014)

(1 item)

March (2014)

(1 item)

January (2014)

(2 items)

November (2013)

(2 items)

July (2013)

(4 items)

April (2013)

(1 item)

February (2013)

(6 items)

September (2011)

(2 items)

November (2010)

(4 items)

September (2010)

(1 item)

August (2010)

(4 items)

July (2010)

(2 items)

September (2009)

(1 item)

June (2009)

(1 item)

April (2009)

(1 item)

November (2008)

(1 item)

October (2008)

(1 item)

September (2008)

(1 item)

July (2008)

(1 item)

June (2008)

(1 item)

May (2008)

(2 items)

April (2008)

(2 items)

March (2008)

(5 items)

January (2008)

(3 items)

December (2007)

(1 item)

November (2007)

(1 item)

October (2007)

(1 item)

September (2007)

(3 items)

August (2007)

(1 item)

July (2007)

(1 item)

June (2007)

(2 items)

May (2007)

(8 items)

April (2007)

(2 items)

March (2007)

(7 items)

February (2007)

(2 items)

January (2007)

(2 items)

November (2006)

(1 item)

October (2006)

(2 items)

September (2006)

(1 item)

June (2006)

(2 items)

May (2006)

(4 items)

April (2006)

(1 item)

March (2006)

(5 items)

January (2006)

(1 item)

December (2005)

(3 items)

November (2005)

(2 items)

October (2005)

(2 items)

September (2005)

(8 items)

August (2005)

(7 items)

June (2005)

(3 items)

May (2005)

(7 items)

April (2005)

(6 items)

March (2005)

(1 item)

February (2005)

(2 items)

January (2005)

(5 items)

December (2004)

(5 items)

November (2004)

(7 items)

October (2004)

(3 items)

September (2004)

(7 items)

August (2004)

(16 items)

July (2004)

(10 items)

June (2004)

(27 items)

May (2004)

(15 items)

April (2004)

(15 items)

March (2004)

(13 items)

February (2004)

(16 items)

January (2004)

(15 items)

Blog Home

RSS 2.0

Writing

Programming C# 5.0

Programming WPF

Other Sites

Interact Software

Azure Startup Script Admin Detection

Thursday 7 November, 2013, 11:39 AM

Since version 2.1 of the Azure SDK shipped, it has been possible to develop Azure applications without needing to run Visual Studio as an administrator. I used to run as a non-admin user back when Windows XP was still the current version of Windows. If you’ve ever tried that, you’ll know it required some determination—I really hate running anything elevated for everyday work. So the ability to debug Azure code without elevation was a very welcome change.

However, it caused a slight snag. When running the Azure Emulator Express (which is what enables local Azure web and worker role debugging without needing to run as an administrator), it will run all your roles’ startup scripts, including all the ones marked as executionContext="elevated". Of course, since the emulator isn’t running elevated, it can’t run the script elevated. So the script runs, but it doesn’t get the elevation it requests. This can cause these scripts to fail, preventing the role from starting.

For example, I have web role with a script that installs a particular certificate in the system’s trusted root CA store. Originally, it contained just this one line:

%windir%\system32\certutil.exe -addstore root %~dp0my-root-ca.cer

However, this caused a problem when I switched to the Express emulator—if you attempt to run that command non-elevated, it will fail, returning a non-zero return code. The Azure emulator detects that, so your role fails to start. (Rather unhelpfully, Visual Studio doesn’t tell you that—it just sits there claiming that it’s starting the roles until you give up and click Cancel.)

This particular work—adding the relevant certificate to my computer’s root CA list—only needs to be done once on any particular computer, so I don’t actually need this script to run when I’m debugging. I need to elevate when installing the cert, but since I only do that once, I don’t need to elevate every time I debug.

Ideally, there would be some sort of environment variable passed in to the script that would enable you to detect that you’re running in the emulator. Unfortunately, it doesn’t look like there’s any such thing. But we can instead fix this by detecting whether we’re elevated, and only attempting to install the certificate if we are.

Unfortunately, my first attempt at this didn’t work:

whoami /groups | find "S-1-16-12288" > nul
if "%errorlevel%"=="0" (
    %windir%\system32\certutil.exe -addstore root %~dp0my-root-ca.cer
)

That first line is the commonly recommended way to detect elevation in a .cmd file—you’ll find plenty of examples using this basic technique on the web. It relies on the Integrity Level mechanism that was introduced in Windows Vista. A token will always contain one of four SIDs indicating the integrity level at which it is running. That S-1-16-12288 SID is present when an admin user is running elevated. (The display name for that SID is “Mandatory Label\High Mandatory Level”.)

However, it turns out that Azure gives you more than you asked for. If your script requires elevation, apparently it runs at an even higher level: System. That’s the integrity level used by privileged system services, e.g. a Windows Service running as LocalService.

The script will be able to do anything that it would had it merely been running at the High integrity level, but the common test will mistakenly conclude that we don’t have administrative privileges. The upshot was that my script decided not to do anything, and so the certificate didn’t get installed.

This fixed it:

whoami /groups | find "S-1-16-12288" > nul
if not "%errorlevel%"=="0" (
    whoami /groups | find "S-1-16-16384" > nul 
)
if "%errorlevel%"=="0" ( 
    %windir%\system32\certutil.exe -addstore root %~dp0my-root-ca.cer
)

If the High integrity level label is not present, this then goes looking for the even higher System label, which has the SID S-1-16-16384 (or “Mandatory Label\System Mandatory Level” if you prefer display names). So this script will perform the work whether running with ordinary elevation or as system. This successfully installs the certificate when deploying to Azure, but skips that work when running non-elevated in Azure Emulator Express.

Now you might look at that and think that there’s a problem: when running non-elevated, the %errorlevel% variable will be non-zero by the time we reach the end of the script. And if you were calling this script from another script, then it would indeed appear to have failed. However, there’s a difference between a calling script inspecting the %errorlevel%, and the exit code eventually produced when cmd.exe exits. If a startup script terminates simply by reaching the end of the script, it doesn’t appear to matter what the %errorlevel% was at that point—from Azure’s perspective, that’s success. However, if a script just executes a single command that fails, cmd.exe appears to reflect that in its return code, and Azure sees it as failure.

That said, the document for Azure Startup scripts implies that this shouldn’t work:

“Startup tasks must end with an errorlevel (or exit code) of zero for the startup process to complete. If a startup task ends with a non-zero errorlevel, the role will not start.”

So to be on the safe side we should explicitly exit with a code of 0 when we decide not to do anything:

whoami /groups | find "S-1-16-12288" > nul
if not "%errorlevel%"=="0" (
    whoami /groups | find "S-1-16-16384" > nul
)
if not "%errorlevel%"=="0" (
    EXIT /B 0
)
%windir%\system32\certutil.exe -addstore root %~dp0my-root-ca.cer

Arguably this has another benefit: it makes it slightly clearer that we are deliberately exiting without producing an error if we don’t seem to be running elevated.

Copyright © 2002-2024, Interact Software Ltd. Content by Ian Griffiths. Please direct all Web site inquiries to webmaster@interact-sw.co.uk