I have a set of files inside a folder. They all have a name which matches the pattern DR__.*. I want to copy them to another folder, but removing the DR__ prefix. How can I do this with MSBuild? I used to do it like this using NAnt:
<mkdir dir="${ClientPath + '\bin\' + ConfigurationName + '\Parameters'}"/>
<foreach item="File" property="Filename" in="CVParameters">
<if test="${string::contains(Filename, Client + '_')}">
<property name="newFilename" value="${ string::substring( Filename, string::last-index-of(Filename, '__') + 2, string::get-length(Filename) - string::last-index-of(Filename, '__') - 2) }"/>
<copy file="${ Filename }" tofile="${ ClientPath + '\bin\' + ConfigurationName + '\Parameters\' + newFilename }" overwrite="true"/>
</if>
</foreach>
I agree with @Si's solution. But with MSBuild 4.0 you can do it with built in functionality. NAnt script is much clearer than mine. But I will add it as a solution just to show MSBuild 4.0 techniques:
<ItemGroup>
<CVParameters Include="$(YourBaseDir)\**\DR__*" />
</ItemGroup>
<Target Name="CopyAndRename"
Condition="'@(CVParameters)'!=''"
Outputs="%(CVParameters.Identity)">
<PropertyGroup>
<OriginalFileName>%(CVParameters.FileName)%(CVParameters.Extension)</OriginalFileName>
<Prefix>DR__</Prefix>
<PrefixLength>$(Prefix.Length)</PrefixLength>
<OriginalFileNameLength>$(OriginalFileName.Length)</OriginalFileNameLength>
<SubstringLength>$([MSBuild]::Subtract($(OriginalFileNameLength),$(PrefixLength)))</SubstringLength>
<ModifiedFileName>$(OriginalFileName.Substring($(PrefixLength),$(SubstringLength)))</ModifiedFileName>
<DestinationFullPath>$([System.IO.Path]::Combine($(DestinationDir),$(ModifiedFileName)))</DestinationFullPath>
</PropertyGroup>
<Copy SourceFiles="%(CVParameters.FullPath)"
DestinationFiles="@(DestinationFullPath)"
SkipUnchangedFiles="true" />
</Target>
Edit (by OP): To get this working, I had to replace $(DestinationFullPath)
in Copy
with @(DestinationFullPath)
, to match the number of Source and Destination Files. Also, I had to change the prefix to DR__
, since DR__.
wouldn't work.
Recently I had to solve similar task and I did it using Item's metadata.
Advantages of this solution are:
- Small build script size.
- Using standard System.String functions to get modified name.
- Custom metadata is copying with definition of new items, that based on existing items (e.g. Items
<TempItemsBak Include="@(TempItems-> ... CustomTransform ... )">
will get original item's metadata values so you can use it for further processing)
<Project DefaultTargets="CopyNoPrefix" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<InputDir Condition="'$(InputDir)' == '' ">.</InputDir>
<OutputDir Condition="'$(OutputDir)' == '' ">Output</OutputDir>
</PropertyGroup>
<ItemGroup>
<CVParameters Include="$(InputDir)\**\DR__.*" />
</ItemGroup>
<Target Name="CopyNoPrefix">
<ItemGroup>
<!-- Items with new name. Use System.String's Remove method to get full file name without prefix -->
<!-- http://msdn.microsoft.com/en-us/library/vstudio/ee886422(v=vs.100).aspx -->
<TempItems Include="@(CVParameters->'%(Filename)%(Extension)'->Remove(0, 5))">
<!-- Define metadata based on original item for next step -->
<OriginalPath>%(Identity)</OriginalPath>
<SavedRecursiveDir>%(RecursiveDir)</SavedRecursiveDir>
</TempItems>
</ItemGroup>
<!-- Use new items along with their metadata for copying -->
<Copy SourceFiles="@(TempItems->'%(OriginalPath)')" DestinationFiles="@(TempItems->'$(OutputDir)\%(SavedRecursiveDir)%(Identity)')" />
</Target>
</Project>
Can you use MSBuild 4.0? If so, then refer to this answer (and MSDN help). Otherwise the RegexReplace task in MSBuildCommunityTasks should also work, at the cost of having to support an external tool (so go MSBuild 4.0 if possible).
Another (untested) option is the TextString task in MSBuildExtensionPack.
Failing those, roll your own task?
I recently had to do something similar to this as well, and ended up with a hacky, but workable solution based on in-line custom tasks.
<UsingTask TaskName="GetNewFileName" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<OriginalFile ParameterType="System.String" Required="true" />
<PackageVersion ParameterType="System.String" Required="true" />
<NewFile Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
int versionIndex = OriginalFile.IndexOf(PackageVersion);
versionIndex = versionIndex < 0 ? OriginalFile.Length : versionIndex;
NewFile = OriginalFile.Substring(0,versionIndex) + "nupkg";
]]>
</Code>
</Task>
</UsingTask>
<UsingTask TaskName="Combine" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<Path ParameterType="System.String" Required="true" />
<File ParameterType="System.String" Required="true" />
<FullPath Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
FullPath = System.IO.Path.Combine(Path, File);
]]>
</Code>
</Task>
</UsingTask>
<Target Name="AfterCompile" Condition="'$(IsDesktopBuild)'!='true'">
<ItemGroup>
<Nuspecs Include="$(SolutionRoot)\$(WorkingBranch)\Nuspec\**\*.nuspec"/>
</ItemGroup>
<!-- Build nugets -->
<Message Text="DEBUG: Build nuget packages" />
<Exec
WorkingDirectory="$(NuspecsPath)"
Condition=""
Command='$(NugetPath) pack "%(Nuspecs.FullPath)" -Version 1.0.0.$(PackageVersion)' />
<ItemGroup>
<Nupkgs2 Include="$(NuspecsPath)\**\*.nupkg"/>
</ItemGroup>
<GetNewFileName OriginalFile="%(Nupkgs2.FileName)%(Nupkgs2.Extension)" PackageVersion="$(PackageVersion)">
<Output ItemName="RenamedNuPkgs" TaskParameter="NewFile"/>
</GetNewFileName>
<Combine File="%(RenamedNuPkgs.Filename)%(RenamedNuPkgs.Extension)" Path="$(NugetRepository)">
<Output ItemName="PackagesRepository" TaskParameter="FullPath"/>
</Combine>
<Message Text="Renamed nuget packages: @(RenamedNuPkgs)"/>
<Message Text="Repository Renamed nuget packages: @(PackagesRepository)"/>
<Copy SourceFiles="@(Nupkgs2)" DestinationFiles="@(PackagesRepository)"/>