2

I want to change the value of a ProductVersion tag in the below test.csproj file. I need to only change the value of the first occurrence of ProductVersion: 8A-0V-W3 to A0-B0-C0

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform>
    <ProductVersion>8A-0V-W3</ProductVersion>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
    <DebugSymbols>true</DebugSymbols>
    <ProductVersion>PK-0X-SD</ProductVersion>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
    <DebugType>none</DebugType>
    <ProductVersion>SD-AA-SW</ProductVersion>
  </PropertyGroup>

I have come up with the below command, but it deletes all occurences of the tag. Is there a way only to delete the first occurence and then insert the updated tag into the same location

get-content ./test.csproj | select-string -pattern 'ProductVersion' -notmatch | Out-File ./test1.csproj
ninja666
  • 29
  • 8

2 Answers2

3

Use the Select-Xml cmdlet:

$firstVersion = (
  Select-Xml //ns:ProductVersion test.csproj -Namespace @{ ns='http://schemas.microsoft.com/developer/msbuild/2003' }
)[0].Node

$firstVersion.InnerText = 'A0-B0-C0'

$firstVersion.OwnerDocument.Save((Join-Path $PWD.ProviderPath test1.csproj))
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • is there a way I can apply this same command to modify only one string tag in the below file, eg change the value of com.ab c.com to com.def.com, without using array as in previous command where whe specify the occurence count - as the file may be updated MinimumOSVersion 12.0 CFBundleDisplayName mycompanyy CFBundleIdentifier com.abc.com CFBundleVersion 1.0.1 CFBundleShortVersionString 1.0 – ninja666 Mar 27 '21 at 22:14
  • @ninja666, you'll want something like `Select-String '//string[.="com.abc.com"]' some.xml`. If you need further assistance, please ask a _new question_; feel free to notify me here once you have done so. – mklement0 Mar 27 '21 at 22:37
1
$oldValue = "8A-0V-W3";
$newValue = "A0-B0-C0";
$projFile = "./test.csproj";

$config = (Get-Content $projFile) -as [Xml];
$ns = New-Object System.Xml.XmlNamespaceManager($config.NameTable);
$ns.AddNamespace("cs", $config.DocumentElement.NamespaceURI);

$config.DocumentElement.SelectNodes("//cs:ProductVersion", $ns) | % {
    $node = $_;
    if ($node.InnerText -ieq $oldValue) {
        $node.InnerText = $newValue;
    }
}

$config.Save($projFile);
Ivan Starostin
  • 8,798
  • 5
  • 21
  • 39
  • Two asides: .NET's working directory usually differs from PowerShell's, so you should always pass _full_ paths to .NET method calls. Use [`Convert-Path`](https://learn.microsoft.com/powershell/module/microsoft.powershell.management/convert-path) to convert a relative path to a full one, assuming it already exists. For more information, see [this answer](https://stackoverflow.com/a/57791227/45375) – mklement0 Mar 27 '21 at 22:11
  • 1
    `Get-Content` is generally _not_ the right command to use with XML files, as it will (in the absence of a BOM) assume a _default encoding_ rather than honor a potential specific encoding in a given file's declaration (e.g., ``). The safe approach is to use `$xmlDoc = [xml]::new()`, followed by `$xmlDoc.Load('/full/path/to/some.xml')` - use of a _full_ path is required. If you happen to know that `Get-Content` is safe to use _in a given situation_, you may use `$xmlDoc = [xml] (Get-Content -Raw some.xml)` - note the `-Raw` for improved performance. – mklement0 Mar 27 '21 at 22:11