2

Problem

I am trying to modify a file by replacing a very specific substring within a file; however, in this particular instance, the file contains two lines that are nearly identical.

This line is in an AssemblyInfo file and I am trying to replace the value of the AssemblyVersion. See below:

$CurrentVersion = '1.0.*'
$NewVersion = '1.0.7.1'

# Two similar lines:
// [assembly: AssemblyVersion("1.0.*")] # I want to ignore this line
[assembly: AssemblyVersion("1.0.*")] # I want to target this value

I have been trying several different approaches to this, each with varying results.

$Assembly = 'C:\path\to\AssemblyInfo.cs'
$regex = '(?<!\/\/ \[assembly:AssemblyVersion\(")(?<=AssemblyVersion\(")[^"]*'
$regex2 = ('`n\[assembly: AssemblyVersion\("'+$CurrentVersion+'"\)\]')

Attempt 001

(GC $Assembly) |
ForEach-Object { $_.Replace($CurrentVersion, $NewVersion) } |
Set-Content $Assembly

This was an obvious failure. It ends up replacing both instances of '1.0.*'

Attempt 002

GC $Assembly | 
Select-String -Pattern '^\[assembly' -AllMatches | 
ForEach-Object { $_.Replace($CurrentVersion, $NewVersion) } |
Set-Content $Assembly

This ended with incompatible command issues...

Attempt 003

(GC $Assembly) | ForEAch-Object { 
    If ( $_ -MATCH $CurrentVersion ) {
        ForEach ($Line in $_) {
            $_.Replace($CurrentVersion, $NewVersion)
        }
    }
 } |
 Set-Content $Assembly

This ended up removing all lines that contained // as the starting characters... which was not what I wanted...

Attempt 004

GC $Assembly |
ForEach-Object {
    $_.Replace($regex2, ('[assembly: AssemblyVersion("'+$NewVersion+'")]'))
} |
Set-Content $Assembly

I get an error saying the file is in use... but that didn't make sense as I couldn't find anything using it...

I have tried several other paths as well, most variations of the above 4 in hopes of achieving my goal. Even going so far as to target the line using the regex line provided above and variations of it to try and grab

Question

Using PowerShell, how can I replace only the line/value of the target line (ignoring the line that begins with // ), but still keep all the lines in the file upon saving the contents?

Brandon
  • 731
  • 2
  • 12
  • 29
  • 1
    *I get an error saying the file is in use... but that didn't make sense as I couldn't find anything using it...* - you are using it. Reading from it is using it. Attempt 3 add `else { $_ }` - if the line doesn't match the regex you still need to output it and write it. – TessellatingHeckler Nov 14 '17 at 20:20
  • @TessellatingHeckler O.o .... Oh... Wow... I feel... Yeah. Well that was much simpler than I had initially thought... – Brandon Nov 14 '17 at 20:28

1 Answers1

3

You are trying to use regex patterns but keep using the string method .Replace() which does not support it. You should be using the -replace operator. That would solve part of your issue.

Looks like you only want to replace the line that does not have anything else on it besides the assembly info.

$path = "C:\temp\test.txt"
$newVersion = "1.0.0.1"
$pattern = '^\s*?\[assembly: AssemblyVersion\("(.*)"\)\]'
(Get-Content $path) | ForEach-Object{
    if($_ -match $pattern){
        # We have found the matching line
        '[assembly: AssemblyVersion("{0}")]' -f $newVersion
    } else {
        # Output line as is
        $_
    }
} | Set-Content $path

That would be a verbose yet simple to follow way to do what you want. It will only match if the assembly line is at the start of the line with optional spaces.

I would expect that the pattern '^[assembly: AssemblyVersion\("(.*)"\)\]' works just as well since it should appear at the start of the line anyway.

This comes from another answer about almost this exact problem except now there is more that one match possibility. Also you will see my regex pattern isolates the current version in case you need that. If that is the case looked at the linked question.


You can combine this with other options but if you know ahead of time the version you are going to use then the replacement is pretty simple as well.

$newVersion = "1.6.5.6"
(Get-Content $path -Raw) -replace '(?m)^\[assembly: AssemblyVersion\("(.*)"\)\]', ('[assembly: AssemblyFileVersion("{0}")]' -f $newVersion)  | Set-Content $path

That reads the file in as one string and performs the replacement as long as the pattern is at the start of the line in the file. (?m) lets us treat the start of line anchor ^ as something that works at the beginning of lines in the text. Not just the start of the whole string.

Matt
  • 45,022
  • 8
  • 78
  • 119
  • hmm... This is very elegant. Thank you. – Brandon Nov 14 '17 at 20:32
  • Addition: [Site that takes text strings and converts them to regex that matches the string.](http://txt2re.com) – Payden K. Pringle Nov 14 '17 at 20:32
  • What does the `?` provide in the pattern? I've never found good use-cases for it in my regex. – Maximilian Burszley Nov 14 '17 at 20:33
  • @TheIncorrigible1 Because I am not sure if there is any leading whitespace. I make it optional so that it should only match lines with assembly info at the start of the line or with spaces. Its my way of avoiding dealing with the slash logic. Yes `\s*` would also work but I hate using greedy matches all the time because it can get you into trouble. – Matt Nov 14 '17 at 20:49
  • 1
    @TheIncorrigible1 `?` does different things in different places, with `\s*` it makes it non-greedy; try `'abcabc' -match 'a.*c'; $matches` compared to `'abcabc' -match 'a.*?c'; $matches`. The first matches across a 'c' as far as possible, the other stops as soon as it satisfies the match condition. `(?m)` at the start sets an option for the regex engine, and `?` is used in lookarounds as well. – TessellatingHeckler Nov 14 '17 at 20:58