How to make startup tasks idempotent?
Asked Answered
N

5

8

I have a number of startup tasks in batch files. In particular I call IIS's appcmd.exe to configure IIS. Startup tasks in Azure are supposed to idempotent (ie, able to be run repeatedly with the same results), in case the role is restarted for some reason. Unfortunately many of my IIS configuration commands will fail the second time around, eg because they delete a configuration node the first time which is then not present on subsequent runs.

My question is, how do I make these startup tasks idempotent? Is there a way to make appcmd.exe not throw errors? Is there a way to make the shell catch the errors? Is there a way to make the Azure framework ignore the errors?

Here's an example of my startup tasks. This is all contained in a command file, configiis.cmd.

@REM Enable IIS compression for application/json MIME type
%windir%\system32\inetsrv\appcmd.exe set config -section:system.webServer/httpCompression /+"dynamicTypes.[mimeType='application/json',enabled='True']" /commit:apphost
%windir%\system32\inetsrv\appcmd.exe set config -section:system.webServer/httpCompression /+"dynamicTypes.[mimeType='application/json; charset=utf-8',enabled='True']" /commit:apphost

@REM Set IIS to automatically start AppPools
%windir%\system32\inetsrv\appcmd.exe set config -section:applicationPools -applicationPoolDefaults.startMode:AlwaysRunning /commit:apphost

@REM Set IIS to not shut down idle AppPools
%windir%\system32\inetsrv\appcmd set config -section:applicationPools -applicationPoolDefaults.processModel.idleTimeout:00:00:00 /commit:apphost

@REM But don't automatically start the AppPools that we don't use, and do shut them down when idle
%windir%\system32\inetsrv\appcmd.exe set config  -section:system.applicationHost/applicationPools "/[name='Classic .NET AppPool'].startMode:OnDemand" "/[name='Classic .NET AppPool'].autoStart:False" "/[name='Classic .NET AppPool'].processModel.idleTimeout:00:01:00" /commit:apphost
%windir%\system32\inetsrv\appcmd.exe set config  -section:system.applicationHost/applicationPools "/[name='ASP.NET v4.0'].startMode:OnDemand" "/[name='ASP.NET v4.0'].autoStart:False" "/[name='ASP.NET v4.0'].processModel.idleTimeout:00:01:00" /commit:apphost
%windir%\system32\inetsrv\appcmd.exe set config  -section:system.applicationHost/applicationPools "/[name='ASP.NET v4.0 Classic'].startMode:OnDemand" "/[name='ASP.NET v4.0 Classic'].autoStart:False" "/[name='ASP.NET v4.0 Classic'].processModel.idleTimeout:00:01:00" /commit:apphost


@REM remove IIS response headers
%windir%\system32\inetsrv\appcmd.exe set config /section:httpProtocol /-customHeaders.[name='X-Powered-By']
Neurology answered 4/8, 2012 at 17:48 Comment(2)
Pretty sure that the lines that are supposed to stop the unused AppPools from automatically starting will not work. Instead of using 'Classic .NET AppPool' etc. as the name you need to use Clr2ClassicAppPool etc.Docent
Actually those names work fine, but it did need to be quoted a little differently. I've updated the code above, just in case somebody looks at it later.Neurology
S
4

Aside from @Syntaxc4's answer: Consider the use of a breadcrumb (file) locally. In your script, check for existence of a known file (that you create). If it doesn't exist, go through your startup script, also creating a breadcrumb file. Next time the vm starts up, it would again check for existence of the breadcrumb file and, if it exists, exit the cmd file. If the breadcrumb file disappears, this typically means your vm has been reconstituted somewhere else (either a new instance or a respawned instance maybe on different hardware) and IIS configuration would be needed.

Superannuated answered 4/8, 2012 at 19:7 Comment(2)
Seems like a good idea. Any idea how to implement that in a .cmd script? I'm sure I can figure it out eventually, but it sounds like you might've done something like this before.Neurology
In case anyone reads this in the future, I added the code to implement this in another answer.Neurology
R
3

You would have to check to see if the config setting is present before attempting to delete it (add conditional logic). This could be achieved by:

'appcmd.exe list config -details'

Capturing a return value would give you something to compare against, be it length of output or an actual value.

Rightist answered 4/8, 2012 at 18:10 Comment(0)
D
3

MSDN now contains an excellent guide for doing this by handling error codes from APPCMD.

http://msdn.microsoft.com/en-us/library/windowsazure/hh974418.aspx

Basically after any appcmd operation, you can do the following:

IF %ERRORLEVEL% EQU 183 DO VERIFY > NUL

and ignore any acceptable error code.

Docent answered 17/10, 2012 at 0:11 Comment(2)
Very nice. Seems like that might be the "right" way to do it. Too bad the error handling is so verbose.Neurology
It appears to be an error in the MSDN article - 'DO' is excessive since the syntax fot the 'IF' command is: 'IF [/I] string1 compare-op string2 command'. And the DO keyword applies only to the 'FOR' command. So the correct command should look like: 'IF %ERRORLEVEL% EQU 183 VERIFY > NUL'. This one worked for me while the original broke the script preventing role from starting at all.Cassock
N
2

Based on David Makogon's suggestion, I added the following to the top of each of my .cmd files. This seems to do the trick. It will create a flag file (what David called a breadcrumb file) in the same directory as the executing script, then check for it on subsequent runs.

@REM A file to flag that this script has already run
@REM because if we run it twice, it errors out and prevents the Azure role from starting properly
@REM %~n0 expands to the name of the currently executing file, without the extension
SET FLAGFILE=c:\%~n0-flag.txt

IF EXIST "%FLAGFILE%" (
  ECHO %FLAGFILE% exists, exiting startup script
  exit /B
) ELSE (
  date /t > %FLAGFILE%
)
Neurology answered 6/8, 2012 at 17:39 Comment(2)
You should put the %ComputerName% in the flag file name, too! ..would be useful!Pewter
Why would that be useful?Neurology
L
0

I highly recommend using the /config:* /xml on the end of your list command. For more information on how I made iis idempotent please look at: https://github.com/opscode-cookbooks/iis

Chef is one of multiple configuration management platforms and i'm only suggesting looking at it for the code (in ruby) that does idempotent via listing the current settings and comparing them to the settings being requested to change.

Linkboy answered 27/3, 2015 at 17:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.