51

I want to replace a string such "how r u" in file test.xml with a string "i am fine" in another file xy.xml.using regular expression in ms build.

ie i have to read string from one file(xy.xml) and replace it in another file test.xml. so please provide necessary steps to solve this issue with example

Nithin
  • 921
  • 3
  • 10
  • 17

8 Answers8

111

This is no longer required... you can now inject C# into the project/build file...

Define a custom task and parameters as follows:

<UsingTask TaskName="ReplaceFileText" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
  <ParameterGroup>
    <InputFilename ParameterType="System.String" Required="true" />
    <OutputFilename ParameterType="System.String" Required="true" />
    <MatchExpression ParameterType="System.String" Required="true" />
    <ReplacementText ParameterType="System.String" Required="true" />
  </ParameterGroup>
  <Task>
    <Reference Include="System.Core" />
    <Using Namespace="System" />
    <Using Namespace="System.IO" />
    <Using Namespace="System.Text.RegularExpressions" />
    <Code Type="Fragment" Language="cs">
      <![CDATA[
            File.WriteAllText(
                OutputFilename,
                Regex.Replace(File.ReadAllText(InputFilename), MatchExpression, ReplacementText)
                );
          ]]>
    </Code>
  </Task>
</UsingTask>

Then simply call it like any other MSBuild task

<Target Name="AfterBuild">
  <ReplaceFileText 
    InputFilename="$(OutputPath)File.exe.config" 
    OutputFilename="$(OutputPath)File.exe.config" 
    MatchExpression="\$version\$" 
    ReplacementText="1.0.0.2" />
</Target>

The above example replaces "$version$" with "1.0.0.2" in the "File.exe.config" located in the output directory.

csharptest.net
  • 62,602
  • 11
  • 71
  • 89
  • How pass **List** as _parameter_ for do multiple replaces ? – Kiquenet Oct 01 '15 at 13:16
  • See http://blogs.clariusconsulting.net/kzu/how-to-perform-regular-expression-based-replacements-on-files-with-msbuild/ for an enhanced version supporting Items and Regex options – Giulio Vian Nov 01 '15 at 08:30
  • I had to add ToolsVersion="4.0" to my project tag in order to get it to look in the correct MSBuildToolsPath directory. – CamHart Jan 20 '16 at 02:19
  • For the match expression, I tried "[label]" but that failed while "label" worked fine. – dknight Jun 21 '16 at 20:55
  • 3
    Note that the version of MSBuild that comes with .NET Core does not support CodeTaskFactory, so this is not a portable solution. – Zastai Feb 19 '18 at 11:08
  • See https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-roslyncodetaskfactory and https://github.com/Microsoft/msbuild/issues/616. RoslynCodeTaskFactory is coming to MSBuild 15.8 for dotnet core, and exists now as a standalone nuget package – David Ferretti Aug 06 '18 at 15:03
  • 1
    For .NET Core just edit the UsingTask element to contain `TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"` instead. – Ondrej Janacek Aug 19 '21 at 13:00
  • Is there any way to execute the custom task on multiple files, say every *.js file in a particular project folder? – g.pickardou Sep 30 '21 at 01:19
19

There is very simple approach to just replace string in a file:

  <Target Name="Replace" AfterTargets="CoreCompile">
      <PropertyGroup>
          <InputFile>c:\input.txt</InputFile>
          <OutputFile>c:\output.txt</OutputFile>
      </PropertyGroup>
      <WriteLinesToFile
          File="$(OutputFile)"
          Lines="$([System.IO.File]::ReadAllText($(InputFile)).Replace('from','to'))"
          Overwrite="true"
          Encoding="Unicode"/>
  </Target>

See https://learn.microsoft.com/en-us/visualstudio/msbuild/property-functions?view=vs-2019 to explore inlinable C# code. [System.Text.RegularExpressions.Regex] included into the list.

Dmitriy Ivanov
  • 1,050
  • 11
  • 11
  • 3
    Be careful with `Encoding="Unicode"`! For example, the default for `app.config` is `Encoding="UTF-8"` and it will not work with Unicode! – Jinjinov Aug 05 '20 at 16:36
  • Using Regex for replacing the version number: ` Properties\AssemblyInfo.cs ` Sorry for the bad formatting! – pschlz Mar 22 '22 at 14:43
13

The answer from @csharptest.net is good, but it doesn't work on DotNetCore. I would have added this as a comment, but I don't have enough reputation.

On DotNetCore you have to update:

  • Task Factory to "RoslynCodeTaskFactory"
  • Task Assembly to "$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"
  • Remove the reference to "System.Core"
  • The consuming Target has to specify the "AfterTargets" attribute as "Build"

Everything else should be the same:

<Project Sdk="Microsoft.NET.Sdk.Web">
  ...

  <UsingTask
    TaskName="ReplaceFileText"
    TaskFactory="RoslynCodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
    <ParameterGroup>
      <InputFilename ParameterType="System.String" Required="true" />
      <OutputFilename ParameterType="System.String" Required="true" />
      <MatchExpression ParameterType="System.String" Required="true" />
      <ReplacementText ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Using Namespace="System"/>
      <Using Namespace="System.IO"/>
      <Using Namespace="System.Text.RegularExpressions" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[  
          File.WriteAllText(
            OutputFilename,
            Regex.Replace(File.ReadAllText(InputFilename), MatchExpression, ReplacementText)
            );
        ]]>
      </Code>
    </Task>
  </UsingTask>

  <Target Name="AfterBuildStep" AfterTargets="Build">
    <ReplaceFileText
       InputFilename="$(OutputPath)File.exe.config" 
       OutputFilename="$(OutputPath)File.exe.config" 
       MatchExpression="\$version\$" 
       ReplacementText="1.0.0.2" />
  </Target>
</Project>
James
  • 1,305
  • 11
  • 20
4

You can use the task FileUpdate from MSBuild Community Tasks as explained in the article http://geekswithblogs.net/mnf/archive/2009/07/03/msbuild-task-to-replace-content-in-text-files.aspx

Victor Ionescu
  • 1,967
  • 2
  • 21
  • 24
4

EDIT: This answer is obsolete. Use solution below...

Use ReadLinesFromFile task to get replacement string from the xy.xml file. Check this

Then use value from xy.xml as a replacement string for FileUpdate task. Check this

And put it all together ;)

Ludwo
  • 6,043
  • 4
  • 32
  • 48
  • i have to read a particular node with attribute name object from file and replace this node another one? how to handle this issue? – Nithin Nov 02 '11 at 13:43
  • 1
    These tasks are from the MSBuildCommunityTasks project, which can be pretty undesirable to install for a simple task like this. – makhdumi Jan 11 '16 at 19:34
2

If you prefer not using third party (community) binaries, nor embedding code into your msbuild project, I'd suggest creating a simple task library which implements File.WriteAllText and can later host other tasks :

using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

public class FileWriteAllText : Task
{
    [Required]
    public string Path { get; set; }
    [Required]
    public string Contents { get; set; }

    public override bool Execute()
    {
        File.WriteAllText(Path, Contents);
        return true;
    }
}

Then you can replace, append, etc. in msbuild :

<UsingTask TaskName="FileWriteAllText" AssemblyFile="MyTasks.dll" />
<FileWriteAllText Path="test.xml"
     Contents="$([System.Text.RegularExpressions.Regex]::Replace(
         $([System.IO.File]::ReadAllText('test.xml')), 'how r u', 'i am fine'))" />
McX
  • 1,296
  • 2
  • 12
  • 16
0

I ran the both replacements against same file that sits on a Unix drive and used the unc path to it \server\path...:

<ReplaceFileText
  InputFilename="$(fileToUpdate)"
  OutputFilename="$(fileToUpdate)"
  MatchExpression="15.0.0"
  ReplacementText="15.3.1"/>


<FileUpdate Files="$(fileToUpdate2)"
            Regex="15.0.0"
            ReplacementText="15.3.1" />

and the cs custom action above does not add the bom; however the FileUpdate did:

%head -2 branding.h branding2.h
==> branding.h <==
#/* branding.h
#** This file captures common branding strings in a format usable by both sed and C-preprocessor.

==> branding2.h <==
#/* branding.h
#** This file captures common branding strings in a format usable by both sed and C-preprocessor.

Thanks csharptest.net - I was doing doing exec's with perl subtitute commands for unix builds.

Tom
  • 1
0

An updated to answer from James

  <UsingTask TaskName="ReplaceTextInFiles" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.$(VsBuildTaskBinarySuffix).dll">
<ParameterGroup>
  <MatchExpression ParameterType="System.String" Required="true" />
  <ReplacementExpression ParameterType="System.String" Required="true" />
  <InputFile ParameterType="Microsoft.Build.Framework.ITaskItem" Required="true" />      
  <IsTextReplaced ParameterType="System.Boolean"  Output="True"/>
</ParameterGroup>
<Task>
  <Reference Include="System.Core" />
  <Using Namespace="System" />
  <Using Namespace="System.IO" />
  <Using Namespace="System.Text.RegularExpressions" />
  <Code Type="Fragment" Language="cs">
    <![CDATA[
      bool isMatchFound = false;
      string filecontent = "";
      string path = InputFile.ItemSpec;

      Log.LogMessage(MessageImportance.High, "[ReplaceTextInFiles]: Match= " + MatchExpression);
      Log.LogMessage(MessageImportance.High, "[ReplaceTextInFiles]: Replace= " + ReplacementExpression);

      IsTextReplaced = false;
      using(StreamReader rdr = new StreamReader(path))
      {
        filecontent = rdr.ReadToEnd();
        if (Regex.Match(filecontent, MatchExpression).Success)
        {
          filecontent = Regex.Replace(filecontent, MatchExpression, ReplacementExpression);
          isMatchFound = true;            
        }
      }

      if(isMatchFound){
        using(StreamWriter wrtr = new StreamWriter(path))
        {
          wrtr.Write(filecontent);
          IsTextReplaced = true;
          Log.LogMessage(MessageImportance.Normal, "[ReplaceTextInFiles]: Replaced text in file:" + path);
        }
      }       
    ]]>
  </Code>
</Task>

vikas pachisia
  • 553
  • 5
  • 8