Modifying directory permissions with Web Deployment

The new VS2010 Web Deployment model is very powerful.  It allows you to setup all your configuration and then use one-click publish to load your web application (or just the diffs) up to your server.  Scott Hanselman gave a great talk on this at MIX that I highly recommend — http://www.hanselman.com/blog/WebDeploymentMadeAwesomeIfYoureUsingXCopyYoureDoingItWrong.aspx

Under the covers, this is all using MSDeploy which adds all sorts of power to run commands, set ACLs, etc.  Put exactly *how* to do this is not documented very well and I spent many a frustrating hour working on one simple task.  How do you make the web deploy process grant the Application Pool Identity access to a modify a specific directory?  Since the built in deployment resets all the permissions to Read-only, you have to figure this out or you have to manually change it every time.

A lot of the documentation, including Scott’s MIX presentation, talks about creating a manifest.xml file.  Unfortunately this only applies if you’re making calls to MSDeploy manually.  When VS2010 prepares a package, it does its own thing and creates its own manifest.xml (actually called <project>.source.manifest which then feeds to archive.xml in the zip file).  But all this is running through the “Web Publishing Pipeline” (WPP) and you can jump into the pipeline yourself.

Turns out this is quite easy.  You need to create a <ProjectName>.wpp.targets file in the root of your web project.  I made mine part of the project and set it to Build Action = None, Copy to Output Directory = Do not copy.

The content of my file is as follows:

Code Snippet
  1. <!–********************************************************************–>
  2. <!– Task Custom ACLs –>
  3. <!–********************************************************************–>
  4. <Project xmlns=http://schemas.microsoft.com/developer/msbuild/2003>
  5.  
  6.   <PropertyGroup>
  7.     <!– Extends the AfterAddIisSettingAndFileContentsToSourceManifest action do also set ACLs–>
  8.  
  9.     <IncludeCustomACLs>TRUE</IncludeCustomACLs>
  10.  
  11.     <AfterAddIisSettingAndFileContentsToSourceManifest Condition=‘$(AfterAddIisSettingAndFileContentsToSourceManifest)’==”>
  12.       $(AfterAddIisSettingAndFileContentsToSourceManifest);
  13.       SetCustomACLs;
  14.     </AfterAddIisSettingAndFileContentsToSourceManifest>
  15.   </PropertyGroup>
  16.  
  17.   <Target Name=SetCustomACLs Condition=‘$(IncludeCustomACLs)’==’TRUE’>
  18.     <Message Text=Adding Custom ACls />
  19.     <ItemGroup>
  20.       <!–Make sure the by default Networkservice/AppPoolIdentity have write permission to the root–>
  21.       <MsDeploySourceManifest Include=setAcl
  22.                                Condition=$(IncludeSetAclProviderOnDestination)>
  23.         <Path>$(_MSDeployDirPath_FullPath)</Path>
  24.         <setAclAccess>Read,Write,Modify</setAclAccess>
  25.         <setAclResourceType>Directory</setAclResourceType>
  26.         <AdditionalProviderSettings>setAclResourceType;setAclAccess</AdditionalProviderSettings>
  27.       </MsDeploySourceManifest>
  28.  
  29.     </ItemGroup>
  30.   </Target>
  31. </Project>

Lets talk about the different sections of this.

<IncludeCustomACLs>TRUE</IncludeCustomACLs>

This line just introduces a flag we can use if we ever want to turn this process off.  Is is referenced in the Condition property for our Target.

<AfterAddIisSettingAndFileContentsToSourceManifest Condition="'$(AfterAddIisSettingAndFileContentsToSourceManifest)'==''">
      $(AfterAddIisSettingAndFileContentsToSourceManifest);
      SetCustomACLs;
</AfterAddIisSettingAndFileContentsToSourceManifest>

These lines inject our task into the pipeline.  Some examples talked about doing it on AfterAddContentPathToSourceManifest, but I found this could inject our SetACL command before the IISApp declaration.  Putting it AfterAddIisSettingAndFileContentsToSourceManifest meant our code would be right after all the default stuff the deployment will automatically do.

<Target Name="SetCustomACLs" Condition="'$(IncludeCustomACLs)'=='TRUE'">

This declares our custom target.  The Name is whatever we want it to be, and the Condition looks at our custom flag.

<MsDeploySourceManifest Include="setAcl"
                              Condition="$(IncludeSetAclProviderOnDestination)">

This command tells MSDeploy to do the “setAcl” command.  All ACLs in the default pipeline are set based on the IncludeSetAclProviderOnDestination flag, so we’ll honor that.

<Path>$(_MSDeployDirPath_FullPath)</Path>

This controls what we want to change the ACL on.  In this example we’re changing the web application root, so $(_MSDeployDirPath_FullPath) is all we need.  If we wanted a certain file, we could do something like $(_MSDeployDirPath_FullPath)\Files\myfile.txt.

<setAclAccess>Read,Write,Modify</setAclAccess>

This is where you set the rights you want.  Options are combinations of Execute, Write, Read, ReadAndExecute, and Modify. 

<setAclResourceType>Directory</setAclResourceType>

We need to specify if this is a Directory or a File.

<AdditionalProviderSettings>setAclResourceType;setAclAccess</AdditionalProviderSettings>

These are needed to make our earlier settings be sent to MSDeploy.

We could also add entries for setAclUser.  The default is the application pool’s identity, which is usually what you need.

And that’s all there is to it.  When you publish your project, this file will be read and you custom ACL entries will be injected.  To see that they really are, you can go to the directory where it creates the zip file before publishing (specified in your Package/Publish Settings, usually obj/Release/Package or obj/Debug/Package).  There will be a <Project>.SourceManifest.xml file.  Open it and you’ll see lines for the normal setACL commands as well as the one you injected (we injected the last line in the file below).

<?xml version="1.0" encoding="utf-8"?>
<sitemanifest>
  <IisApp path="C:\Users\Kevin\Documents\Visual Studio 2010\Projects\MotoBlur\MotoBlur\obj\Release\Package\PackageTmp" managedRuntimeVersion="v4.0" />
  <setAcl path="C:\Users\Kevin\Documents\Visual Studio 2010\Projects\MotoBlur\MotoBlur\obj\Release\Package\PackageTmp" setAclResourceType="Directory" />
  <setAcl path="C:\Users\Kevin\Documents\Visual Studio 2010\Projects\MotoBlur\MotoBlur\obj\Release\Package\PackageTmp" setAclUser="anonymousAuthenticationUser" setAclResourceType="Directory" />
  <setAcl path="C:\Users\Kevin\Documents\Visual Studio 2010\Projects\MotoBlur\MotoBlur\obj\Release\Package\PackageTmp" setAclResourceType="Directory" setAclAccess="Read,Write,Modify" />
</sitemanifest>

Special thanks go to:

- Xinyang Qiu whose blog teaches how to use the *.wpp.targets file: http://blogs.msdn.com/b/webdevtools/archive/2010/02/09/how-to-extend-target-file-to-include-registry-settings-for-web-project-package.aspx

- Vishal Joshi whose blog teaches about how the process works under the covers: http://vishaljoshi.blogspot.com/2009/03/how-does-web-deployment-with-vs-10.html

- Tali Smith whose post on Learn.IIS.Net teaches the settings for SetACL: http://learn.iis.net/page.aspx/742/set-acls-through-the-manifestxml-file/

About these ads

4 thoughts on “Modifying directory permissions with Web Deployment

  1. Brilliant, thanks! The Web Deploy team seems to have had almost no budget for writing proper documentation, which is too bad, considering how good this tool really is.

  2. Kevin, great article. I am trying to set read, write permissions for a specific directory under the root. I followed what you were doing. My SetCustomACLs target looks like:

    $(_MSDeployDirPath_FullPath)\UploadedFiles
    Domain\User
    Read,Write,Modify
    Directory
    setAclResourceType;setAclAccess;setAclUser

    My *.SourceManifest.xml file looks like:

    When deploying this I get the following error:
    Info: Adding sitemanifest (sitemanifest).
    Info: Updating setAcl (Default Web Site\TEST).
    Info: Updating setAcl (Default Web Site\TEST).
    Info: Adding setAcl (D:\Code\PackageTmp\UploadedFiles).

    Microsoft.Web.Deployment.DeploymentException: () An error occurred when the request was processed on the remote computer. —> System.IO.IOException: The device is not ready.

    at Microsoft.Web.Deployment.Win32Native.RaiseIOExceptionFromErrorCode(Win32ErrorCode errorCode, String maybeFullPath)
    at Microsoft.Web.Deployment.DirectoryEx.CreateDirectory(String path)
    at Microsoft.Web.Deployment.SetAclProvider.Add(DeploymentObject source, Boolean whatIf)
    at Microsoft.Web.Deployment.DeploymentObject.Add(DeploymentObject source, DeploymentSyncContext syncContext)
    at Microsoft.Web.Deployment.DeploymentSyncContext.HandleAdd(DeploymentObject destObject, DeploymentObject sourceObject)
    at Microsoft.Web.Deployment.DeploymentSyncContext.HandleUpdate(DeploymentObject destObject, DeploymentObject sourceObject)
    at Microsoft.Web.Deployment.DeploymentSyncContext.SyncChildrenOrder(DeploymentObject dest, DeploymentObject source)
    at Microsoft.Web.Deployment.DeploymentSyncContext.ProcessSync(DeploymentObject destinationObject, DeploymentObject sourceObject)
    at Microsoft.Web.Deployment.DeploymentObject.SyncToInternal(DeploymentObject destObject, DeploymentSyncOptions syncOptions, PayloadTable payloadTable, ContentRootTable contentRootTable)
    at Microsoft.Web.Deployment.DeploymentAgent.HandleSync(DeploymentAgentWorkerRequest workerRequest)
    — End of inner exception stack trace —
    at Microsoft.Web.Deployment.StatusThreadHandler.CheckForException()
    at Microsoft.Web.Deployment.AgentClientProvider.RemoteDestSync(DeploymentObject sourceObject, DeploymentSyncContext syncContext)
    at Microsoft.Web.Deployment.DeploymentObject.SyncToInternal(DeploymentObject destObject, DeploymentSyncOptions syncOptions, PayloadTable payloadTable, ContentRootTable contentRootTable)
    at Microsoft.Web.Deployment.DeploymentObject.SyncTo(DeploymentProviderOptions providerOptions, DeploymentBaseOptions baseOptions, DeploymentSyncOptions syncOptions)
    at MSDeploy.MSDeploy.ExecuteWorker()
    Error count: 1.

    I have tried several different things and had no luck getting past this. I need to only set the read/write permissions on that 1 directory. Can you help me?

    Thanks,
    Mike

  3. Pingback: Web-deploy i .Net ska inte vara så svårt! – BEKK Open

Comments are closed.