How I got .Net 4.5 RC Running in a Windows Azure WebRole

UPDATE (28 Sep 2012):  Now Windows Azure supports .Net 4.5 officially and the dev tools are fixed with the release of the Azure SDK 1.8 this post largely deprecated.

However, if you get the following message when you try to deploy for the first time after upgrading your local build;

The feature named NetFx45 that is required by the uploaded package is not available in the OS * chosen for the deployment.

You’ll need to upgrade your CSFG with the osFamily from 2 to 3;

<code ServiceConfiguration ... osFamily="3"/>

UPDATE (7 Sep 2012): I’ve since wrapped this in a plugin and I’m now using it for .Net 4.5 RTM (includes MVC web apps, naturally) with the Azure SDK and Tools for Visual Studio 1.7 SP1 

Initially I was very excited when I saw this post which seemed to suggest that the 1.7 release of the Azure SDK would now allow me to build solutions in .Net 4.5 and Visual Studio 2012 RC.

Initially I was very excited when I saw this post which seemed to suggest that the 1.7 release of the Azure SDK would now allow me to build solutions in .Net 4.5 and Visual Studio 2012 RC.

Unfortunately, like many other announcements it was very misleading. It does NOT work out of the box. If you look carefully at the pictures, this post, like many others very subtly chooses [.Net Framework 4.0] and NOT 4.5 as the title would suggest. The comments on this post suggest there may be workarounds but I couldn’t find a single explanation for “power users” of how to “work around the blockers”. “How hard could these workarounds be to find?”, I asked myself. Well it turns out, that although the solution isn’t that complicated, the effort involved in hunting down the stoppers was way more of a time-sink than I’d have liked. If you’d like to save yourself days of pain, read on.

Blocker #1 – No .Net 4.5 Runtime on the Azure Images

So this one is pretty easy to solve once you know how to build a Windows Azure Startup Task but I chose to build mine in the end using the semi-official/semi-documented plugins feature. I started with an Azure Plugins Library as a base but wrote my own in the end. Startup Tasks have to be idempotent and error free. Any deviation from that and your role will hang seemingly indefinitely as it tries to recover running the scripts over and over again. a task like installing the .Net runtime for instance isn’t something you want to repeat endlessly because you returned a non-zero error code from your command script. By default, plugins for the 1.7 Azure SDK go here;

%ProgramFiles%\Microsoft SDKs\Windows Azure\.NET SDK\2012-06\bin\plugins

I used a junction (mklink /j) to my developer source directory under which is under git control to develop my plugin, which looks like this;

image

I’d read Magnus’ post about upgrading the Framework on the Azure instance and although he said he’d had trouble getting the web installer to run I didn’t fancy putting the whole 50MB installer into my .cspkg and putting it into blob storage seemed liked overkill, so the file you see above (although named ‘full’) is actually the web installer.

The csplugin simply looks like this;

<?xmlversion="1.0" ?>
<RoleModule
  xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"
  namespace="Axxiant.Net45RC">
  <Startup>
    <TaskcommandLine="baseLineUpgrade.cmd"executionContext="elevated"taskType="simple" />
  </Startup>
  <ConfigurationSettings>
  </ConfigurationSettings>
  <Endpoints>
  </Endpoints>
  <Certificates>
  </Certificates>
</RoleModule>

It runs as a “Simple” task so that it’s synchronous and the role won’t continue to setup until this task completes successfully (returns ERRORLEVEL==0 from baseLineUpgrade.cmd). Here’s that cmd file influenced by various posts in the comment fields;

@echo off
REM http://www.magnusmartensson.com/post/2012/04/02/howto_put_net45_beta_and_aspnetmvc4_beta_on_windowsazure.aspx
REM http://blog.smarx.com/posts/windows-azure-startup-tasks-tips-tricks-and-gotchas
REM http://www.davidaiken.com/2011/01/19/running-azure-startup-tasks-as-a-real-user/ 
:start
echo :start****************************************  >> baseLineUpgrade.txt
time /t >> baseLineUpgrade.txt
echo **********************************************  >> baseLineUpgrade.txt
echo REM Install .Net 4.5 RC : WARNING - this does take several minutes of startup time and then reboots >> baseLineUpgrade.txt

:jobs

:dotNetFx_check
echo :dotNetFxCheck >> baseLineUpgrade.txt
reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319\SKUs\.NETFramework,Version=v4.5" >> baseLineUpgrade.txt
if %ERRORLEVEL%x==0x goto dotNetFx45_installed
echo Need to install NetFX45RC >> baseLineUpgrade.txt

echo dotNetFxRunning=%dotNetFxRUNNING% >> baseLineUpgrade.txt
if  dotNetFx_RUNNINGx==truex goto skip_dotNetFx45
set dotNetFx_RUNNING=true
echo set dotNetFx_RUNNING=true >> baseLineUpgrade.txt

echo REM Change the location of App Data for running 32 bit install tasks on Win64 (downloading installers like WebPI have trouble with this otherwise) >> baseLineUpgrade.txt
@echo on
md "%~dp0appdata"
reg add "hku\.default\software\microsoft\windows\currentversion\explorer\user shell folders" /v "Local AppData" /t REG_EXPAND_SZ /d "%~dp0appdata" /f  >> baseLineUpgrade.txt
@echo off

echo REM Run FULL setup if we have it, otherwise WEB setup - see flags /passive vs /q >> baseLineUpgrade.txt
REM Could also use WebPI Command Line tool http://msdn.microsoft.com/en-us/library/windowsazure/gg433059.aspx
if     exist .\dotNetFx45_Full_x86_x64.exe echo Running FULL installer... >> baseLineUpgrade.txt
if not exist .\dotNetFx45_Full_x86_x64.exe echo Running WEB  installer... >> baseLineUpgrade.txt
if     exist .\dotNetFx45_Full_x86_x64.exe start /wait .\dotNetFx45_Full_x86_x64.exe /q /serialdownload /log "%~dp0appdatadotNetFx45_setup.log"
if not exist .\dotNetFx45_Full_x86_x64.exe start /wait .\dotNetFx45_Full_setup.exe /q /serialdownload /log "%~dp0appdatadotNetFx45_setup.log"

echo REM Restore Local AppData >> baseLineUpgrade.txt
reg add "hku\.default\software\microsoft\windows\currentversion\explorer\user shell folders" /v "Local AppData" /t REG_EXPAND_SZ /d %%USERPROFILE%%\AppData\Local /f  >> baseLineUpgrade.txt

REM no need to set dotNetFx_RUNNING=false
goto doesntAppearToNeedReboot
echo REBOOT ***************************************************  >> baseLineUpgrade.txt
REM shutdown /r /t 0 >> baseLineUpgrade.txt
:doesntAppearToNeedReboot

:dotNetFx45_installed
echo :dotNetFx45_installed >> baseLineUpgrade.txt
:skip_dotNetFx45
echo :skip_dotNetFx45 >> baseLineUpgrade.txt

:next_job

:exit
echo :exit >> baseLineUpgrade.txt

Notice there’s plenty of logging (which you need) and the script should defend itself both against being executed more than once and unnecessarily.

You’ll need add a “Cloud Project” to your solution, for which you need to have installed WindowsAzureTools.vs110 for Visual Studio on top of the 1.7 SDK.

To include this plugin, add an Import section to your ServiceDefinition.csdef as follows;

<?xmlversion="1.0"encoding="utf-8"?>
<ServiceDefinitionname="MyProjDotCom"xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"
schemaVersion="2012-05.1.7">
 
  <WebRolename="MyProj.www"vmsize="ExtraSmall"enableNativeCodeExecution="true">
       <!-- omitted stuff -->
    <Imports>
       <!--omitted stuff -->
      <ImportmoduleName="NetFX45RC" />
    </Imports>
       <!-- omitted stuff –>
  </WebRole>
</ServiceDefinition>

Blocker #2 – Visual Studio Refuses to Build a Cloud Project that contains .Net v4.5 Code

So now you have created your cloud project, add in your web project and build and you’ll get this;

Windows Azure Cloud Service projects currently support roles that run on .NET Framework version 3.5 and 4.  Please set the Target Framework property in the project settings for project ‘MyProj.www’ to .NET Framework 3.5 or .NET Framework 4.

Thanks a lot VS! Well after much deliberation, I decided to fix this by patching the targets in the 1.7 SDK;

c:\> notepad %ProgramFiles(x86)%\MSBuild\Microsoft\VisualStudio\v11.0\Windows Azure Tools\1.7\Microsoft.WindowsAzure.targets

And edited line 1784 from this;

  Condition="$(_RoleTargetFramework) == 'v3.5' Or $(_RoleTargetFramework.StartsWith('v4.0'))">True</_IsValidRoleTargetFramework>

to this;

Condition="$(_RoleTargetFramework) == 'v3.5' Or $(_RoleTargetFramework.StartsWith('v4'))">True</_IsValidRoleTargetFramework>

OK. Now it will build and package. The trouble is, it creates a broken package. And you find that your role just won’t start. It turns out this is for two reasons; Firstly, Visual Studio packages up a waIISHost.exe.config file with a missing version number whereas you actually want one that looks like this;

<?xmlversion="1.0"encoding="utf-8"?>
<configuration>
  <startupuseLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntimeversion="v4.0"sku=".NETFramework,Version=v4.5" />
  </startup>
  <runtime>
    <NetFx40_LegacySecurityPolicyenabled="false" />
  </runtime>
</configuration>

It also packs a RoleModel.xml which contains an incorrect version number pointing to v3.5. Both of these cause the role to fail. It should look like this;

      <EntryPoint>
        <NetFxEntryPointassemblyName="MyProj.www.dll"targetFrameworkVersion="v4.5" />
      </EntryPoint>

But due to some trigger I never fully resolved, VS always sets this to v3.5 as soon as you try to build a v4.5 package. Yes, you read that right, it doesn’t even set it to 4.0, it falls back to some default when it can’t figure out what to do.

I couldn’t for the life of me find out how to get Visual Studio to do the right thing here. As soon as you use a v4.5 based role VS just creates a bad package. This is presumably another reason why they didn’t ship with this feature in the RC.

Workaround #1 – using csPack
You can bypass Visual Studio’s packaging with csPack, but along with having two build scripts now, you’ll also need to jump through those other hoops of specifying a physical folder for the web project on the command line AND in your ServiceDefinition.csdef;

Packaging with csPack.exe
cspack \vs11Projects\MyProj\Azure11\Azure2012\ServiceDefinition.csdef         /out:\vs11Projects\MyProj\Azure11\Azure2012\bin\Release\app.publish\MyProjDotCom.cspkg /role:MyProj.www;\vs11Projects\MyProj\www\obj\Release\AspnetCompileMerge\Source /rolePropertiesFile:MyProj.www;\vs11Projects\MyProj\AzureCommandLine\roleProperties.txt

WARNING: Make sure the physical directory you specify is NOT your project folder. It must be a published folder (I pointed mine at the pre-compiled folder) otherwise you’ll ship all your source code and developer web.configs in the package instead of the properly built web site.

The second piece of magic here is to use a roleProperties.txt file thus;

TargetFrameWorkVersion=v4.5
EntryPoint=MyProj.www.dll

Workaround #2 – using Visual Studio
Now don’t get me wrong, I’m all for scripted builds, but I’d rather be using msbuild over my Visual Studio project file than a completely separate build script which could easily become out of sync with my project. Just so you know, I’m not proud of this next kludge, but at least it produces consistent builds that deploy and start up. As I said, I couldn’t for the life of me work out how to get VS to package up the correct files for a v4.5 role so since I was already patching the Azure Instance, so I decided to do the same for these glitches which I assume will get patched by the Azure team later.

I simply ship a correct version of the waIISHost.exe.config file with my WebRole project (remembering to set the file to ‘Copy Always’);

image

And then it’s simply a matter of running the other file copyWaIIShost.cmd;

copy Installation\waIISHost.exe.config \base\x64\. /Y >> Installation\copywaIIShost.txt

…and call it from your ServiceDefinition.csfg with;

<?xmlversion="1.0"encoding="utf-8"?>
<ServiceDefinitionname="MyProjDotCom"xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"schemaVersion="2012-05.1.7">
  <WebRolename="MyProj.www"vmsize="ExtraSmall"enableNativeCodeExecution="true">
      <!--omitted--> 
    <Startuppriority="1">
      <TaskexecutionContext="elevated"taskType ="background"commandLine ="Installation\copyWaIIShost.cmd"/>
    </Startup>
  </WebRole>
</ServiceDefinition>

I chose to keep this kludge with the project rather than make a plugin, but either would work. Note that plugins will execute from a dynamically attached disk (initially E: but will change) in E:\plugins\<yourPluginName> whereas startup tasks will run from E:\AppRoot\bin and in my case my files will copy to E:\AppRoot\bin\Installation, but keep in mind the current path will still be E:\AppRoot\bin, so my copy command needs to reference the Installation path as well.

Blocker #3 – IIS AppPools Incorrectly configured

So when deploying to Azure this time, it’s only now that the machine is configured and the role starts that the ASP.Net site itself still fails to load. In fact for an MVC project you’ll probably get this;

image

Basically, routing isn’t working, because the IISConfigurator.exe or whatever calls it can’t figure out how to configure a v4.5 site and so chooses the default, which is v2.0 and a non-integrated pipeline. It’s possible that somewhere above I’ve made too many 4.5 changes and somewhere it would have been happy (since v4.5 is an in-place upgrade to v4.0) with a v4.0 framework tag, but I don’t know where.

Again, I spent hours spelunking through the Microsoft.WindowsAzure.ServiceRuntime code with ILSpy but couldn’t quite figure out where it was failing or what would be the trigger to get it to configure the site as it would for a v4.0 project. However, at this point, having spent way too much time on the problem, I was getting quite comfortable with my kludges, so I added this to the compWaIIShost.cmd file;

%windir%\system32\inetsrv\appcmd set config -section:applicationPools
-applicationPoolDefaults.managedRuntimeVersion:v4.0
-applicationPoolDefaults.managedPipelineMode:Integrated >> appcmd.txt

Basically, I changed the defaults for newly configured application pools. To my astonishment (and immense relief) two pieces of luck were on my side. Firstly, my task seemed to run before it tried to configure the site, and secondly, it actually picked up the default. I didn’t have high hopes on that second point from what I was inferring in the ServiceRuntime source which appeared to be hard coded to a V2.0 default, but fortunately, it clearly wasn’t running that line of code.

Finally

My roles now start. I can consistently, repeatedly and reliably deploy new packages when I need to, at least until the Azure team fix up the Visual Studio extensions, then I can remove the kludges. But for now, I’m happy and can move on. If you’ve felt the need to read this far, I imagine you have been in a similar position recently so I hope this post has helped you move on too.

Similarly, if somebody can see where I’ve gone wrong and can avoid Blocker #3 altogether I’d be very grateful of a pointer.

About these ads

11 Responses to How I got .Net 4.5 RC Running in a Windows Azure WebRole

  1. Great article, thanks for sharing your experience.

  2. I was getting 403 also, but not with framework 4.5 but with 4.0. The solution was in this post:

    http://social.msdn.microsoft.com/Forums/en-US/windowsazuredevelopment/thread/fa0bdc4c-7cd7-4f0e-bec0-bc8b669f78d5/

    Putting this
    inside system.webServer section

  3. <modules runAllManagedModulesForAllRequests=”true” />

  4. danieljsinclair says:

    Juan, that’s a useful extra tidbit, although in my case I don’t actually want runAllManagedModulesForAllRequests so I didn’t come across it.

  5. Slav says:

    About waIISHost.exe.config. It’s created from MSBuild task defined in C:\Program Files\Microsoft SDKs\Windows Azure\.NET SDK\2012-06\bin\Microsoft.ServiceHosting.Tools.MSBuildTasks.dll.

    List of supported frameworks is hardcoded:

    Dictionary dictionary = new Dictionary();
    dictionary.Add(“v3.5″, “v2.0.50727″);
    dictionary.Add(“v4.0″, “v4.0″);
    this.supportedFrameworks = dictionary;

    So, you can’t do anything about it but hack.

  6. Pingback: Are you thinking what I'm thinking?

  7. Pingback: Content from the Southwest District VS 2012 Roadshows | MSDN Blogs

  8. Richard says:

    Great work!

    Any chance you could add the plugin to the library, to make it easy for other people to consume?

  9. danieljsinclair says:

    Hi Richard
    I would have done if I’d known how :) but please help yourself. Note that the plugin currently also includes warmup.dll which strictly speaking has nothing to do with doing the .Net45 upgrading, but it’s trivial to take out. If you include it in your library, I’d appreciate shared credit. Daniel

  10. Richard says:

    Done! I have removed the warmup stuff components, although they may make an interesting module as well!

    To install you can use the APM tool (http://richorama.github.com/AzurePluginLibrary/#APM)

    > APM install NetFx45

    Then just add this module to the Service Definition file:

    The plugin is located here: https://github.com/richorama/AzurePluginLibrary/tree/master/plugins/NetFx45

  11. 44825 says:

    Solid goods here on wordpress.com, man. I’m glad you’ve
    had the opportunity to collect all this knowledge on this page, and I’m loving the way you deliver it. You are making it enjoyable and you still take care to keep it sharp. I think I can gather a lot from you. A helpful blog without a doubt.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 41 other followers