MsBuild Deploy with PackageLocation drop file permissions - msbuild

I have an MsBuild Build in TFS that is publishing a web zip package.
This is the command line I am using:
/t:Build;Package
/p:DeployOnBuild=true;Configuration=Release;
DeployTarget=Package;PackageLocation=\\xxx\MyApp.zip
It is working properly and it is also replacing in the web.config the parameters as expected.
The only issue I am facing is the permissions applied to the package file.
Right now the file is deployed into:
* \myshare\myapp\ *
And the folder is set with the permission:
Everyone: full control
The package inside the folder has the permission:
TFSAdmin: full control
and nothing else, so I can't open it or copy it ... Is there any way I can avoid that?

So far it seems that the problem can't be fix if not with a workaround.
I have found an easy and simple workaround by executing in the workflow a batch file at the end of the build process.
In the batch file I use the very old ICACLS to re-set the permissions:
ICACLS \\xxx\MyPackage.zip /GRANT Everyone:F
ICACLS \\xxx\MyPackage.zip /GRANT Users:F

Add in MSBuild .proj file:
<Exec Command="icacls "\\xxx\MyApp.zip" /grant User:F" ContinueOnError="true" />
a sequence of simple rights:
F - full access
M - modify access
RX - read and execute access
R - read-only access
W - write-only access

In case you want to make a folder accessible by your website you can place the following snippet in your "PostBuild" events at the very bottom of your .csproj (you need to edit the .csproj file manually via a text editor):
<Target Name="AfterBuild">
<!-- grant everyone the modify right recursively even for files and folders created dynamically in the future -->
<!-- note the use of (OI) and (CI) flags which stand for object inherit & container inherit these flags -->
<!-- indicate that subordinate containers will inherit the same access control element or ace this means that -->
<!-- files and folders created in the future within the targeted folder will get the same permissions -->
<Exec Command=" icacls ".\Logs" /grant Users:(CI)(OI)M /T " ContinueOnError="true" />σ
<Exec Command=" icacls ".\Logs" /grant IIS_IUSRS:(CI)(OI)M /T " ContinueOnError="true" />
</Target>
Sidenote: If you use publish/deploy-on-remote-server functionality of visual studio to deploy your website, then it goes without saying that the folder permissions will probably NOT be preserved and that you will have to use some sort of post-installation script to re-apply them (probably using the 'icacls' approach again show here). This sort of post-installation script should probably be part of WebDeploy - haven't used WebDeploy myself though so your mileage may vary on this particular aspect.

Related

How can I change the folder that Exec tasks in a csproj file generates its temporary exec.cmd files in?

I have a .NET 6 project that includes some Exec nodes, and those commands are failing because (as in this discussion on the msbuild repo) the paths of the generated tmp<blah>.exec.cmd files are not whitelisted.
The suggested fix in there is
The location of this file is controlled by the environment variable
TEMP. Can you set that to a custom value before invoking MSBuild?
Which I'm sure would work - but I don't know how to do that. According to this question (which is for C++ not C#, but it's the best I can find) you can use EnvironmentVariables="<blah>" in that same node, but the files are still generated in %LOCALAPPDATA% despite my trying to set TEMP to something else. A failing example is below - what am I doing wrong?
<Target Name="ToolRestore" BeforeTargets="PreBuildEvent">
<Exec Command="dotnet tool restore" StandardOutputImportance="high" EnvironmentVariables="TEMP=C:\MSBuildTemp" />
</Target>
An answer should ideally be valid for building/debugging in Visual Studio and via dotnet build/test/publish. Even better would be a method of making the value of TEMP be variable per-user, but that's not necessary.
According to the code for the Exec task it does use the standard GetTempPath function, which means it really should react to user-level environment variables. However that function is documented like:
This method checks for the existence of environment variables in the
following order and uses the first path found:
The path specified by the TMP environment variable.
The path specified by the TEMP environment variable.
...
so the suggested fix you found is not entirely correct: you might need TMP not TEMP. And indeed on my machine I have TMP set and msbuild uses it for its temporary batch files which can be seen using a target which prints the path of the batch file Exec uses:
<Target Name="TempTest">
<Exec Command="echo %~dp0"/>
</Target>
Running on cmd.exe:
>set TMP=c:\temp
>msbuild temptest.proj /t:TempTest /v:m /nologo
c:\temp\
>set TMP=c:\Users
>msbuild temptest.proj /t:TempTest /v:m /nologo
temptest.proj(7,5): error MSB6003: The specified task executable "
cmd.exe" could not be run. Failed to create a temporary file. Temporary files folder is full or its path is incorrect.
Access to the path 'c:\Users\tmpb31f9faffaab49e9b3bd5479a6823550.exec.cmd' is denied.

Prevent .NET Core 2.0 from leaving files in /tmp on RHEL7

Edit: Martin provides a link below to a defect, which is now fixed and released.
I am a university student. I would like to use .NET Core for my coursework. To do so, my code needs to compile and run on the department Linux cluster, because that is what my instructors test my submissions on.
My sysadmin installed the recently-released .NET Core 2.0 RHEL package for me on a trial basis. I created, built, and ran the sample CLI projects Microsoft provides, and they worked. But my sysadmin was displeased because dotnet created at least one file in (global) /tmp, which remained there after I logged off.
-rw------- myuser mygroup /tmp/.NETCoreApp,Version=v2.0.AssemblyAttributes.cs
In principle, he'd prefer dotnet not create any files in /tmp that it doesn't clean up when its process is done. More than that, when he tried to build Microsoft's samples himself, it failed; dotnet tried to access the above file, which his user did not have read permissions for!
Ideally, dotnet wouldn't create any files with a lifetime different from the project it is building. To achieve this, any such files could live in the project directory — maybe under the bin subdirectory, so that a clean will purge them. Is there a way to make dotnet write these files there instead? Otherwise, can it least use transient filenames, to avoid the permissions conflict we encountered?
Whatever the solution is, it has to be systemwide, and it cannot depend on the good behavior of users. So something like asking the user to set $TMPDIR will not work.
The easiest way would be to set the TMPDIR environment variable to a different location as MSBuild uses it to construct the path.
Another way to make msbuild use a local path for this file is to add a target like this to the csproj file:
<Target Name="SetTFMAssemblyAttributesPath"
BeforeTargets="GenerateTargetFrameworkMonikerAttribute">
<PropertyGroup>
<TargetFrameworkMonikerAssemblyAttributesPath>$(IntermediateOutputPath)$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)</TargetFrameworkMonikerAssemblyAttributesPath>
</PropertyGroup>
<ItemGroup>
<!-- GenerateTargetFrameworkMonikerAttribute doesn't add to #(FileWrites) for the global path -->
<FileWrites Include="$(TargetFrameworkMonikerAssemblyAttributesPath)" />
</ItemGroup>
</Target>
This will put it into the IntermediateOutputPath which is obj/{Debug/Release}/{TargetFramework}/. The added FileWrites item allows it to be cleaned on dotnet clean, which is not done for the global location to avoid race conditions during clean.
You can create a Directory.Build.targets file in your user directory / in the directory hierarchy your projects are in to wire up the target to all projects (unless they don't already contain a file with this name). Just surround the target with a <Project> element for this file.
There is a GitHub issue on changing the default location.

Modify files after deployment package is created

I'm in the process of creating a web deployment package through an automatic build trigger on the server.
The package should take care of everything (including creation of a specific website, apppool, and the latest code) on any server desired.
I extracted manually a deploy package from a configured local IIS site, containing all information needed by MsDeploy to create the site, apppool, etc...
They are present in following files
archive.xml
parameters.xml
systeminfo.xml
The idea is now that I would create automatic a deploy package on the build server, that contains the new compiled code, but with the above xml files in the .zip package.
Right now, I'm building the application, after which I execute a PowerShell script that will manually overwrite the files in the .zip with the ones I have.
However, I know you can extend the Target file (with a .wpp.targets file in your project) to plug into the pipeline and modify things along the way.
Unfortunately I'm getting a little lost with the information I found.
I'd like to:
1) configure the creation of the deployment package to use my existing .xml files.
2) if that's not possible, overwrite the files with my own files after the package creation.
My goal is to have a full executable deploy package after the build is finished, so I won't need to PowerShell script anymore.
Any information that will point me closer to a solution or helps me to understand more clearly msbuild targets and/or webdeploy is very appreciated.
I managed doing this by extending the Package MsBuild target.
Adding a .wpp.targets file in the root of the web project with following content.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSBuild.ExtensionPack.4.0\MSBuild.ExtensionPack.tasks"/>
<PropertyGroup>
<DeployFilesDirectory>$(MSBuildProjectDirectory)\Deploy\</DeployFilesDirectory>
</PropertyGroup>
<PropertyGroup>
<OnAfterPackageUsingManifest>
$(OnAfterPackageUsingManifest);
CopyDeployFiles;
ReplaceSetParametersFile;
<!--ZipDeploymentFiles;-->
</OnAfterPackageUsingManifest>
</PropertyGroup>
<Target Name="CopyDeployFiles">
<Message Text="Copy Deploy Files"></Message>
<ItemGroup>
<Files Include="$(DeployFilesDirectory)*.xml" Exclude="$(DeployFilesDirectory)setParameters.xml"></Files>
</ItemGroup>
<MSBuild.ExtensionPack.Compression.Zip TaskAction="AddFiles"
CompressFiles="#(Files)"
ZipFileName="$(PackageFileName)"
RemoveRoot="$(DeployFilesDirectory)"/>
</Target>
<Target Name="ReplaceSetParametersFile" DependsOnTargets="GenerateSampleDeployScript">
<Message Text="Replace Default SetParameters File"></Message>
<Copy DestinationFiles="$(GenerateSampleParametersValueLocationDefault)"
SourceFiles="$(DeployFilesDirectory)setParameters.xml"></Copy>
</Target>
</Project>
The first target is executed after MsDeploy has created the package and will replace the .xml files within the .zip file. I'm using the MsBuild.ExtensionPack Zip support.
The second target is executed after the build has created the sample .cmd and setParameters files and will overwrite the setParameters.xml with my own as well.
It takes a while to understand the concepts of MsBuild targets etc, but once you understand it becomes indeed very powerful.
Creating the package is now as simple as just launching the MsBuild
msbuild "D:\Projects\MyWebProject.csproj" /T:Package /P:Configuration=Release;Platform="AnyCPU";PackageLocation="D:\DeployPackage\package.zip";PublishProfile=MyProfile
And deploying is the same as before
package.deploy.cmd /Y –setParamFile :myParameterFile.xml
Assuming your paths stay the same, you can achieve this by specifying the existing zip as your -dest:package=package.zip. MSDeploy will automatically overwrite the files inside the zip.

MSBUILD web deploy package zip file does not inherit permissions from parent folder

I'm creating a zip file from msbuild using the package target. It creates it fine and the folder I'm putting it in has permissions for a user that allows that user to have full control of the folder. However, when I remove the zip file, then run msbuild to create the zip file again the zip file has lost the permissions for that user.
So then I am using a certain user to programatically deploy the package file and that user no longer has permissions.
How can I force that zip file to inherit the permissions from the parent folder so I don't have to manually add the permissions back to the zip file each time msbuild creates it?
It would be better to restore permission inheritance for zip file, like this
icacls {packageFileName.zip} /reset
Turns out there isn't a specific way to tell the Package target to set the permissions. So the workaround is to set the permissions yourself after the Package target.
Here is how I did it. The regular expression part is to pull the name of the project file out of the project file path we have in our build script. That name matches up to the .\Output\Packages\ folder that is created. I then call cacls inside an exec command to set the permissions on every file in that directory for the user I specify.
<Target Name="Package">
<MSBuild Projects="#(PackageProject)" Targets="Package" Properties="Platform=$(Platform);
Configuration=$(Configuration);
DeployOnBuild=true;
DeployTarget=Package;
PackageLocation=$(PackageOutputDir)\$([System.Text.RegularExpressions.Regex]::Split($(ProjectName), '(.*\\)([a-z,A-Z,0-9,_,-]+)(\.\*proj;)')[2])\$([System.Text.RegularExpressions.Regex]::Split($(ProjectName), '(.*\\)([a-z,A-Z,0-9,_,-]+)(\.\*proj;)')[2]).zip;
PackageAsSingleFile=true;
ExcludeFilesFromDeployment=Web.config;
_PackageTempDir=$(PackageOutputDir)\temp;">
</MSBuild>
<Exec Command="echo y| cacls $(PackageOutputDir)\$([System.Text.RegularExpressions.Regex]::Split($(ProjectName), '(.*\\)([a-z,A-Z,0-9,_,-]+)(\.\*proj;)')[2])\* /G NetworkService:F"/>
</Target>

How do I copy files to a network share that requires a password using MSBuild?

I've created an MSBuild.xml file to automate our ASP.NET project builds. It builds the project, publishes it, zips it and copies the zip to a network share. This all works perfectly, provided that I have already logged in to the network folder previously. After I have accessed the network share the username and password are remembered for until I log out of my machine. If I haven't logged in previously I get a "failure: unknown user name or bad password" error when I run the build file. I would like the build to work all the time regardless of whether I have previously accessed the network share.
Currently I'm using the Copy task to copy the zip file to the network share. I've checked the Copy task documentation and I can't see any way to include credentials. So, how can I copy files to a network share passing the required username and password?
Here is an example of the Copy task I'm using. All the properties are defined at the top of the MSBuild.xml file:
<Target Name="CopyToServer">
<Copy SourceFiles="$(ReleaseFolder)\$(ZipFileName).zip" DestinationFolder="$(WebServerRoot)" />
</Target>
I had the same problem. But I did not want to enter password each time I deploy app using RunAs.
Instead, I use Copy task wrapped by net use via Exec :
<Target Name="CopyOutput">
<ItemGroup>
<PackagedFiles Include="$(ProjectName)\obj\$(Configuration)\Package\PackageTmp\**\*.*"/>
</ItemGroup>
<Exec Command="net use $(DestPath) /user:$(DestLogin) $(DestPass)" ContinueOnError="false"/>
<Copy SourceFiles="#(PackagedFiles)"
DestinationFiles="#(PackagedFiles->'$(DestPath)\%(RecursiveDir)%(Filename)%(Extension)')"
ContinueOnError="true"/>
<Exec Command="net use $(DestPath) /delete" />
</Target>
DestPath, DestLogin and DestPass I set through environment variables.
Thanks to Phil's answer and this one.
I've found one solution. I'm not entirely happy with it, but it'll do until I find a better solution.
If you run the MSBuild command from a batch file it's possible to use the runas command to run MSBuild as the user that has the required permissions to access the network share.
E.g.
runas /user:someWindowsUser "C:\PathToBatchFile"
I would reccoment to create special account, let's say builder, and give read/write access for that account share on remote computer.
Or you can call script on computer start up to open session. See net use /?