1

So I'm trying to get the contents of a bunch of files to replace the header string:

$replaces = dir | select Name, @{n='content';e={(get-content $_) -replace "header-foo","header-bar"}}

That then gives me a list of:

Name       Content
----       ----------
a.txt      header-foo blah blah
b.txt      header-foo blah blah blah
c.txt      header-foo blah

Then I want to pass the value of this to set-content -value like so:

$replaces | set-content -Path {"alt/" + $_.Name} -Value {$_.content}

Only all of my files have the content $_.content now. I also tried -Value ($_.content) but that doesn't do the right thing either.

It's only when I use a foreach does it work:

$replaces | foreach { set-content -Path ("alt/" + $_.Name) -Value $_.content }

Why is this the case? Why does it not work correctly without the foreach?

2 Answers2

2

You're trying to use a delay-bind script block ({ ... }) in order to dynamically determine the argument for Set-Content's -Value parameter, based on each pipeline input object.

However, delay-bind script blocks cannot be used with -Value, because that parameter's type is [object[]] (System.Object[]) (see Get-Help -Parameter Value Set-Content); the same limitation applies to [scriptblock]-typed parameters.

To work around this limitation, you do need a loop-like construct in order to invoke Set-Content once for every intended -Value argument, as you have done with the ForEach-Object (whose built-in alias is foreach) cmdlet.


The reason that [object] and [scriptblock] parameters (and their array variants) do not work as delay-bind script blocks is that passing as script block to such parameters binds it instantly, as-is, because the argument type matches the parameter type.

With any other parameter type - assuming that the parameter is designated as accepting pipeline input - PowerShell infers that a script-block argument is a delay-bind script block and evaluates the script block for each pipeline input object, as intended, and the script block must then ensure that its output is of a type that matches the target parameter.

Esperento57
  • 16,521
  • 3
  • 39
  • 45
mklement0
  • 382,024
  • 64
  • 607
  • 775
-1

You problem is in "get-content $" you must use get-content $.Name

But you should modify you script like this :

  1. Use Get-childItem (norme powershell)
  2. Use -file for take only file and not directory
  3. Use FullName, you can then use recurse if necessary
  4. Use .Replace and not -replace operator (doesnt work in this case)

-replace use regular expression : detail here

$replaces = Get-ChildItem -file | select FullName, @{n='content';e={(get-content $_.FullName).Replace('header-foo', 'header-bar')}}
$replaces | %{set-content -Path $_.FullName -Value $_.content}
Esperento57
  • 16,521
  • 3
  • 39
  • 45
  • 1
    Unfortunately the -value argument is taken literally in the file, `$_.content`. Parameters of type [object] can't be piped to in that way. – js2010 Dec 27 '20 at 16:22
  • To spell out in more detail why your answer is broken and therefore deserving of down-votes: the _verbatim content_ of script block `{$_.content}` is written to the output file. That is, instead of writing the _value of the `.content` property_ of each input object to the file, _verbatim string_ `$_.content` is written, which is clearly not the intent. You can easily verify this: `@{ FullName = 't.txt'; content = 1 } | Set-Content -Path { $_.FullName } -Value {$_.content}; Get-Content t.txt` outputs verbatim `$_.content`, not `1`. My answer explains why this behavior occurs. – mklement0 Dec 28 '20 at 18:27