Several developers have asked how they can extend WebDeploy to do more. As discussed in my Anatomy of WebDeploy Package post, WebDeploy creates a package based on a manifest. The manifest is simply a list of MSDeploy providers. So how do we add more to the manifest?
Sayed Hashimi posted an article entitled “Setting Folder Permissions on Web Publish” which shows how to do this. Basically you can add items to the MSDeploySourceManifest item group to achieve this. For example adding a setAcl provider:
<ItemGroup> <MsDeploySourceManifest Include="setAcl"> <Path>$(_MSDeployDirPath_FullPath)\Elmah</Path> <setAclAccess>Read,Write</setAclAccess> <setAclResourceType>Directory</setAclResourceType> <AdditionalProviderSettings>setAclResourceType;setAclAccess</AdditionalProviderSettings> </MsDeploySourceManifest> </ItemGroup>
That seems easy right? Well not quite, here are a couple of tips for using this technique.
Timing is Everything
First we have to wrap this ItemGroup addition inside a Target using our wpp.targets file and inject that target into the build process at the right time. You can either add your custom providers before or after the WebDeploy default providers. The default providers are added within the IisSettingAndFileContentsToSourceManifest target so we can use the Before/After properties to call our target. I named my target AddMSDeployProviderToManifest. Obviously, you wouldn’t want to add both of these, you would use one or the other.
PowerShell Scripts can be Tricky
If you only need to run commands that would exist on the target by default you may be done. But if you need to run custom scripts from your solution there are a few more steps.
Lets extend WebDeploy to run a PowerShell script from our project. For example, this could be used to disable a Windows Service before deploying an update to the service and then restarting it after the deployment. Really it could do whatever you need.
In this situation we would use the RunCommand MSDeploy provider to run PowerShell on the target server. Like so
<MsDeploySourceManifest Include="runCommand"> <path>PowerShell -NoProfile -ExecutionPolicy Bypass -File &quot;$(PowerShellFilename)&quot;</path> <waitInterval>2000</waitInterval> </MsDeploySourceManifest>
The problem is the deployment fails with the following error:
2>Info: Updating runCommand (PowerShell -NoProfile -ExecutionPolicy Bypass -File "test.ps1"). 2>MSDEPLOY(0,0): Warning : The argument 'test.ps1' to the -File parameter does not exist. Provide the path to an existing '.ps1' file as an argument to the -File parameter. 2> 2>MSDEPLOY(0,0): Warning : The process 'C:\Windows\system32\cmd.exe' (command line '') exited with code '0xFFFD0000'.
Basically it can’t find the file you want to run. This makes sense as the deployment will be running on the target machine which doesn’t have all source files by default. So we need to copy the script to the target first.
In most situations you don’t want the PS file to actually be deployed with your application. So step one is to exclude the deployment files from the application package. I placed my deployment files in the _deployment folder in the root of the project.
<ItemGroup> <DeploymentFiles Include="$(MSBuildProjectDirectory)/_deployment/**" /> <ExcludeFromPackageFiles Include="@(DeploymentFiles)"> <FromTarget>Project</FromTarget> </ExcludeFromPackageFiles> </ItemGroup>
Next we need to add another provider to the manifest to also package and deploy the deployment files to a temporary deploy folder on the target but first we will copy the deployment files to the same temporary file locally for packaging. Then we can add the dirPath provider to the manifest to copy the files:
<PropertyGroup> <DeploymentFilesTempPath>C:\temp\_deploymentFiles\$(MSBuildProjectName)\</DeploymentFilesTempPath> </PropertyGroup> <Target Name="AddMSDeployProviderToManifest"> <Copy SourceFiles="@(DeploymentFiles)" DestinationFolder="$(DeploymentFilesTempPath)" /> <ItemGroup> <MsDeploySourceManifest Include="dirPath"> <path>$(DeploymentFilesTempPath)</path> </MsDeploySourceManifest>
Now the files are packaged and deployed to the target server in addition to the PowerShell call:
... <dirPath path="C:\temp\_deploymentFiles\WebApplication7\" /> <runCommand path="PowerShell -NoProfile -ExecutionPolicy Bypass -File &quot;C:\temp\_deploymentFiles\WebApplication7\test.ps1&quot;" /> </sitemanifest>
Optionally, you can delete the temporary directory of deployment files by adding another RunCommand a
<MsDeploySourceManifest Include="runCommand"> <path>rd $(DeploymentFilesTempPath) /s /q</path> </MsDeploySourceManifest>
wpp.target Refresh Issue
If you are testing your deployment via right click publish in VS, you will find updates to your wpp.targets file are not always applied as you would expect. This can be very confusing! Apparently VS caches the wpp.targets file until you close and reopen the solution or restart VS. This is really annoying!
In general, I try to build/deploy via the command line to avoid this headache. Otherwise I have to close the solution and reopen after each change.
I have uploaded the full example to GitHub as well – https://github.com/rschiefer/ExtendWebDeployManifest
If you found this article helpful, please share on Reddit: