Nant : change file permission - nant

I have an ASP.NET application.
Basically the delivery process is this one :
Nant builds the application and creates a zip file on the developer's computer with the application files without SVN folders and useless files. This file is delivered with a Nant script.
The zip and nant files are copied to the client's computer
the Nant script replaces the current website files with the file contained in the zip file.
My problem is that with this process I have an Unauthorized access error when I try to open the website.
It seems that the files need to have a permission set for the user "IIS_WPG".
I don't have the power to change IIS configuration so I have to manually change the permissions of each file. And each time I replace the files the permissions are removed and I need to set them again.
So I have two questions :
Can I change files permissions with Nant ? How to do it ?
Is it possible to avoid this problem ? (developers don't have this user on their computers)

#Jeff Fritz
Ouch...
Your suggestion is the right solution but the parameters are... dangerous :).
On dev computers I'm logged as administrator and I tried your suggestion with cmd.
It replaces all the permissions set in order to set only the ones defined in the command (so, after the command, accessing files resulted in a "Access denied" even with my admin user)
It applied on the C:\WINDOWS\ directory, while I called the command from the wwwroot folder. :)
So, after some tests, the right command is :
cacls [full folder path] /T /E /G IIS_WPG:F
/T : applies on specified folder and subfolders
/E : edits the ACL instead of replacing it :)

You need to run the CACLS program in windows to grant permissions to files and folders. From Nant, you can do this with the EXEC task.
Try a tag block like:
<exec program="cacls">
<arg value="*" />
<arg value="/G IIS_WPG:F" />
</exec>

We ended up writing our own task for this with some fairly straight forward code:
[TaskName("addusertodir")]
public class AddUserToDirectorySecurity : Task
{
[TaskAttribute("dir", Required=true)]
public string DirPath { get; set; }
[TaskAttribute("user", Required=true)]
public string UserName { get; set; }
protected override void ExecuteTask()
{
FileSystemAccessRule theRule1 = new FileSystemAccessRule(UserName, FileSystemRights.ListDirectory, AccessControlType.Allow);
FileSystemAccessRule theRule2 = new FileSystemAccessRule(UserName, FileSystemRights.ReadAndExecute, AccessControlType.Allow);
FileSystemAccessRule theRule3 = new FileSystemAccessRule(UserName, FileSystemRights.Read, AccessControlType.Allow);
DirectorySecurity theDirSecurity = new DirectorySecurity();
theDirSecurity.AddAccessRule(theRule1);
theDirSecurity.AddAccessRule(theRule2);
theDirSecurity.AddAccessRule(theRule3);
Directory.SetAccessControl(DirPath, theDirSecurity);
}
}
Then you can write a nant script that loads the custom task and executes:
<loadtasks>
<fileset>
<include name="MyTask.dll"/>
</fileset>
</loadtasks>
<addusertodir dir="MyDir" user="IIS_WPG"/>
Obviously, this could be modified for your certain rules or you could even parameterize this in the task if you so wish. We preferred this over the using the exec task as it have us a bit more control over permissions that were being applied.

CACLS is now deprecated. Here's a version that uses ICACLS, the replacement.
Let's say we have the following:
The root folder of our installation is "c:\inetpub\wwwroot", and it's stored in the NANT variable ${paths.myprogram.inetpub}
The folder we want to modify is called "uploads", and it's stored in ${upload.foldername}
The user we want to grant access to is "IIS_UPLOAD_USER", stored in ${iis.upload.user}
The permission level we want to grant is "M", for "modify" permissions, stored in ${iis.user.permissionlevel}
With these assumptions, our task is this:
<exec program="icacls">
<arg value="${path::combine(paths.myprogram.inetpub, upload.foldername)}" />
<arg value="/grant" />
<arg value="${iis.upload.user}:${iis.user.permissionlevel}" />
</exec>
Hope this helps!

Related

Script to clone a template folder retaing permissions and prompt for new folder name

May I please get your insight?
I have a user group in AD call Contract Admin. I like would this user group to be able to run a script that clones a template folder (retaining permissions) to a new folder and prompt for a new client name.
The folder structure would be as follows:
Clients
New Folder (template folder) (Permissions: Admins, Contract Admin, Financial Admin)
Statements Folder (Permissions: Financial Admin)
End result after script:
Clients
ABC Company (Permissions: Admins, Contract Admin, Financial Admin)
Statements Folder (Permissions: Financial Admin)
Above is an example of the folder structure where New Folder is the template folder which also has a folder inside of it as well. I'd like to have the folders cloned with permissions retained.
I hope this is clear, I apologize if it is not. Thank you so much for your help.
The code I currently have for copying the permissions are as folows:
robocopy /e /copy:DATS "X:\Template" "X:\New Folder Name"
attrib -h "Template"
This is what I have for cloning the folders but I am lost from here.
Thank you
Solution:
#echo off
set "src=C:\Users\XXX\Desktop\template folder"
set /p "dest=Name of Client?: "
robocopy /e /copy:DATS %src% %dest%
the source can simply be the folder/file name without the full path if both the script and the folder/file to be copied is in the same directory.
/copy: Specifies the file properties to be copied. The following are the valid values for this option:
D Data A Attributes
T Time stamps S NTFS access control list (ACL)
O Owner information
U Auditing information
The default value for
CopyFlags is DAT (data, attributes, and time stamps).
But this is not a script. It's a robocopy command.
There are many switches robocopy, see its help file for them.
Specifically the mirror (/MIR) a folder tree to a new location.
As for prompting users, there are a few ways to do this. The most direct is Read-Host cmdlet provides. You simply capture that in a variable and pass that as the folder.
$FolderPath = Read-Host 'Enter a folder path.'

Conditional <exec> based on results of <copy>

In our nant build script for our web-based application, we <copy> a set of files to a target directory and then run aspnet_compiler over them via <exec>.
<copy> only copies files that have changed, however here is no way to pass this information to <exec>, and I want to avoid running aspnet_compiler when nothing has actually changed.
Options I've tried to find are: <copy> setting a property when any file is copied that can then be checked with <if>; or being able to create a file before the copy and doing something like <if test="any-file-newer-than(targetdir, timestampfile)">. Even better would be if <copy> could return a list of copied files that I can then iterate over to avoid having to process the entire tree, but I think that might be asking a bit too much.
So far, I've drawn a blank: is what I'm looking for possible without writing a custom extension?
Why don't you just simply replace copy task with robocopy? (you're on Windows, right?)
Robocopy returns different exit codes on different successful copy situations:
https://ss64.com/nt/robocopy-exit.html
For example:
0 - ok, nothing copied
1 - ok, something copied
You could do something like this:
<exec program="robocopy.exe" commandline="${SourceDir} ${DestDir}" failonerror="false" resultproperty="ExitCode" />
<fail unless="${ExitCode < 8}" message="Failed to copy"/> <!-- Anything between 0..7 is OK for robocopy -->
<exec unless="${ExitCode == 0}" ...

MSDeploy runCommand using relative path

I am trying to run a command as a part of my packaging/deployment process via MSDeploy. In particular, I am trying to create a custom event log by running installutil against one of my DLLs, but I am having trouble with specifying a relative path to the DLL from the deployment directory. To get started, I added the below config to my csproj in order to generate the runCommand provider inside of my Manifest file. Please note the absolute path to the DLL.
<PropertyGroup>
<!-- Extends the AfterAddIisSettingAndFileContentsToSourceManifest action to create Custom Event Log -->
<IncludeEventLogCreation>TRUE</IncludeEventLogCreation>
<AfterAddIisSettingAndFileContentsToSourceManifest Condition="'$(AfterAddIisSettingAndFileContentsToSourceManifest)'==''">
$(AfterAddIisSettingAndFileContentsToSourceManifest);
CreateEventLog;
</AfterAddIisSettingAndFileContentsToSourceManifest>
</PropertyGroup>
<Target Name="CreateEventLog" Condition="'$(IncludeEventLogCreation)'=='TRUE'">
<Message Text="Creating Event Log" />
<ItemGroup>
<MsDeploySourceManifest Include="runCommand">
<path>installutil C:\inetpub\wwwroot\MyTestApp\bin\BusinessLayer.dll</path>
</MsDeploySourceManifest>
</ItemGroup>
</Target>
<ItemGroup>
After calling msbuild, this generated my manifest correctly inside of my package.zip. When I ran MyTestApp.deploy.cmd /Y it correctly called msdeploy and deployed my files to inetpub\wwwroot\MyTestApp and ran my command from the manifest below:
<runCommand path="installutil C:\inetpub\wwwroot\MyTestApp\bin\BusinessLayer.dll ... etc
The problem I am having is I do not want to hardcode this DLL path to c:\inetpub\etc. How can I make the above call using the relative path from my deployment directory under Default Web Site? Ideally, I would like MSDeploy to take this path and pass it as a variable to the runCommand statement in order to find the DLL. Then I could write something like: <path>installutil $DeploymentDir\NewTestApp\bin\BusinessLayer.dll</path> without having to worry about hard-coding an absolute path in.
Is there any way to do this without using the absolute path to my DLL every time?
You can add definition of DeploymentDir to the .csproj with the action you wrote above:
<PropertyGroup>
<DeploymentDir Condition="'$(Configuration)'=='Release' AND '$(DeploymentDir)'==''">Release Deployment Dir</DeploymentDir>
<DeploymentDir Condition="'$(Configuration)'=='Debug' AND '$(DeploymentDir)'==''">Debug Deployment Dir</DeploymentDir>
<DeploymentDir Condition="'$(DeploymentDir)'==''">C:\inetpub\wwwroot</DeploymentDir>
<AplicationName Condition="'$(Configuration)'=='Release' AND '$(AplicationName)'==''">NewTestApp</AplicationName>
<AplicationName Condition="'$(Configuration)'=='Debug' AND '$(AplicationName)'==''">MyTestApp</AplicationName>
<ApplicationDeploymentDir Condition="'$(ApplicationDeploymentDir)'==''">$(DeploymentDir)\$(ApplicationName)\bin</ApplicationDeploymentDir>
</PropertyGroup>
Theese conditions will allow to change everything from command line to take full control over the build process in your build system or script.
MSBuild.exe yourproj.proj /p:Configuration=Release /p:DeploymentDir=D:\package /p:ApplivationName=BestAppForever
And inside of your task you can use it
<ItemGroup>
<MsDeploySourceManifest Include="runCommand">
<path>installutil $(ApplicationDeploymentDir)\BusinessLayer.dll</path>
</MsDeploySourceManifest>
</ItemGroup>
I realize this isn't the answer you probably wanted to hear but this is how I got around it.
We created a powershell script on the destination server. So instead of running your command:
installutil C:\inetpub\wwwroot\MyTestApp\bin\BusinessLayer.dll ... etc
We would run:
c:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe d:\powershell\installSites.ps1 siteName <NUL
The "sitename" is being passed in as a param into the powershell script. Inside the script it knows on that destination server which files to install, any commands that need to run, app pools to recycle, etc.
Again, not as easy as finding a relative path, but it does the job.

Nant - copy only modifiled files

I'm looking for Nant script syntax that will copy from one folder to an other all files that were modified within last 5 days.
How I can cause to to include only modified files?
Thanks
You could use robocopy. Robocopy has an option to exclude older files. See this link.
http://en.wikipedia.org/wiki/Robocopy
You would then run Robocopy for a nAnt exec task.
Hope this helps
Shiraz
The idea is quite simple:
When you need to copy files from folder 1 to folder 2 which are modified relative to each other:
<exec program="C:\Windows\System32\xcopy.exe" failonerror="false" >
<arg line="${source} ${destination} /D /E /C /Q /H /R /Y /K" />
</exec>
However, if you need to check timestamp too, you would have to create some kind of filter. This you could do either in a batch file or writing your own console app.

Nant cmd.exe redirection creating file called 'program' on c:\ drive

I have NAnt script which as part of its project calls a batch file using the following task:
<target name="makeplane">
<exec program="C:\WINDOWS\system32\CMD.EXE"
commandline="/C ${make.file} > ${make.log}"
verbose="false"
workingdir="${make.dir}"
basedir="${make.dir}">
</exec>
<delete>
<fileset basedir="c:\">
<include name="program" />
</fileset>
</delete>
</target>
Unfortunately i dont have control over the contents on the batch file and it spews out a lot of garbage onto the screen which is of no use in the log. So to get around this im redirecting the output from the bat file to a text file using the
> ${make.log}
part which equates to "> log.txt".
This redirection seems to create a file called "program" on the C drive and messes up all sorts of services and windows generally doesnt like it. To get around this Im manually deleting this file after the bat file has executed.
The problem is i now need to run a similar task for another project entirely and if they run at the same time then the first will lock the file called "program" and the second will fail. Not exactly a great situation for Continuous integration.
I searched on the net but because the file is called program i get all sorts of rubbish results. Anyone got any ideas on a work around. I tried the output parameter on the exec task but the issue remains the same.
If the file path to the log contains spaces, one generally would want to surround the path in quotes. In order to do this in nant one can use the " entity.
It sounds like this is what's happening in your particular situation. Therefore, if you change your example to the following I think things should work as expected.
<target name="makeplane">
<exec program="C:\WINDOWS\system32\CMD.EXE"
commandline="/C ${make.file} > "${make.log}""
verbose="false"
workingdir="${make.dir}"
basedir="${make.dir}">
</exec>
</target>
Usually this happens because the script is trying to create a file with a long file name with space in it (c:\program files in your case), but it is not using quotes around the long file name.
Here is what I did. I think it is a bit cleaner for complex commands.
<property name="cmd.label" value="\${ss.previous.label}#$Project.SSPath" />
<echo message="Getting $Project.Name source code with label \${cmd.label}" />
<property name="cmd" value=""\${tfs.root}\tf.exe" get $Project.SSPath "/version:L\${cmd.label}" /force /recursive /noprompt"/>
<exec program="cmd.exe"
workingdir="\${shadow.dir}"
failonerror="true"
verbose="true">
<arg value="/c" />
<arg value=""\${cmd}"" />
<arg value="> nul" />
</exec>