55

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

0

7 Answers 7

113

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.

Sign up to request clarification or add additional context in comments.

8 Comments

How pass List<string> as parameter for do multiple replaces ?
See blogs.clariusconsulting.net/kzu/… for an enhanced version supporting Items and Regex options
I had to add ToolsVersion="4.0" to my project tag in order to get it to look in the correct MSBuildToolsPath directory.
Note that the version of MSBuild that comes with .NET Core does not support CodeTaskFactory, so this is not a portable solution.
For .NET Core just edit the UsingTask element to contain TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" instead.
|
25

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.

4 Comments

Be careful with Encoding="Unicode"! For example, the default for app.config is Encoding="UTF-8" and it will not work with Unicode!
Using Regex for replacing the version number: <PropertyGroup> <FilePath>Properties\AssemblyInfo.cs</FilePath> </PropertyGroup> <WriteLinesToFile File="$(FilePath)" Lines="$([System.Text.RegularExpressions.Regex]::Replace($([System.IO.File]::ReadAllText('$(FilePath)')), 'AssemblyVersion\(&quot;\d+.\d+.\d+.\d+&quot;\)', 'AssemblyVersion(&quot;$(VersionTag)&quot;)'))" Overwrite="true" /> Sorry for the bad formatting!
Love it! Can I also pass RegexOptions? I need RegexOptions.Multiline.
Answering my own question: yes, it's actuallly easy. Just pass "RegexOptions.Multiline" as a fourth parameter to Replace().
16

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>

Comments

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 ;)

2 Comments

i have to read a particular node with attribute name object from file and replace this node another one? how to handle this issue?
These tasks are from the MSBuildCommunityTasks project, which can be pretty undesirable to install for a simple task like this.
3

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'))" />

Comments

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.

1 Comment

You can pass the encoding parameter to File.WriteAllText to add the BOM, but either way gets it done :)
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>

2 Comments

Given that you have changed the parameters from the James' example you should probably show how you are using this custom task. Are you missing a closing UsingTask tag?
looks like a lot has happened since I wrote the post. Also, a new approach is recommended on James answer. So please check that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.