Reputation: 13
I have a problem with my PowerShell script and I don't find the answer to my question.
I try to replace:
get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {
With:
get-vm | Where-Object {$_.IsClustered -eq $False} | Where-Object {$_.State -eq 'Running'} | ForEach-Object {
My code:
$old = [regex]::Escape(' get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {')
$new = " get-vm | Where-Object {$_.IsClustered -eq `$False} | Where-Object {$_.State -eq 'Running'} | ForEach-Object {"
(Get-Content -Path c:\test\test.txt) | ForEach {$_ -Replace "$old","$new"} | Set-Content -Path c:\test\testsuccess
Before:
After:
I don't understand why the $_
is replaced by nothing. I would like to keep $_
in the output file.
UPDATE 1
I have tried to do
$old = [regex]::Escape(' get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {')
$new = " get-vm | Where-Object {`$_.IsClustered -eq `$False} | Where-Object {`$_.State -eq 'Running'} | ForEach-Object {"
(Get-Content -Path c:\test\test.txt) | ForEach {$_ -Replace "$old","$new"} | Set-Content -Path c:\test\testsuccess
But it doesn't work. The output in the testsuccess file is:
get-vm | Where-Object { get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object { .IsClustered -eq $False} | Where-Object { get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object { .State -eq 'Running'} | ForEach-Object {
Upvotes: 1
Views: 1346
Reputation: 25021
You can use the PowerShell parser to do this along with here-strings and the .NET Replace
method.
$parsedcode = [System.Management.Automation.Language.Parser]::ParseFile('c:\test\test.txt',[ref]$null,[ref]$null)
$old = @'
get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {
'@
$new = @'
get-vm | Where-Object {$_.IsClustered -eq $False} | Where-Object {$_.State -eq 'Running'} | ForEach-Object {
'@
$parsedcode.ToString().Replace($old,$new) | Out-File c:\test\testsuccess -Force
Since we are doing static string replacements, I believe regex is just making this more complicated. The PowerShell language parser can read PowerShell code from a string or file and output it to the console or store it in memory as is. Other capabilities open up using the parser as well.
Using the here-strings (@''@
) allows for special symbols in the strings to be treated literally.
The .Replace()
method is a case-sensitive string replacement. It does not use regex.
Upvotes: 1
Reputation: 11294
Try this one:
$regex = '\sget-vm\s\|\sWhere-Object\s\{\$_\.IsClustered\s-eq\s\$False\}\s\|\sForEach-Object\s\{'
$new = " get-vm | Where-Object {$_.IsClustered -eq $False} | Where-Object {$_.State -eq 'Running'} | ForEach-Object {"
$testString = ' get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {'
$testString -replace $regex, $new
An online version of the above example can be found under tio.run.
You've to be aware to escape special characters like $
in your regex correctly ( use \
).
Also, be aware to escape the simple quotes in $new
correctly. I used the PowerShell quoting rules and defined $new
as "formattable" string to escape ``'Running'
correctly.
Update1 (based on @Ansgars comments):
Change quotation of $new
to:
$new = ' get-vm | Where-Object {$_.IsClustered -eq $False} | Where-Object {$_.State -eq "Running"} | ForEach-Object {'
When enclosing the string of $new
in single quotes no substitution actions are performed. In this case, you've to change the single quotes at 'Running'
with double-quotes. An online example can be found here.
UPDATE 2:
Hopefully, this will be the last update. Your problem is related to the .NET defined substitution elements (which PowerShell seems to use behind the scenes):
$_ Includes the entire input string in the replacement string.
In addition PowerShell's quoting rules come into play. Let's check this example. We want to replace the word car
with $_
:
"car" -replace "car", "$_"
The output is empty. Why? Because of using double quotes PowerShell substitutes $_
with its actual value. Well since nothing is stored in $_
( because it was not defined and it normally holds the actual pipeline value, but there is no pipeline included), car
is replaced to an empty string.
Ok, next try, lets put $_
into single quotes:
"car" -replace "car", '$_'
The output is car
. Why? Well using single quotes deactivates PowerShell's variable substitutions, and $_
is forwarded as literal to the replace
operator. Now the .Net regex substitutions come into play. As stated in substitution elements $_
includes the entire input string, in our case car. So the -replace
operator replaces car
with car
. We can see the entire input is included in this example:
"car" -replace "car", '$_ $_ $_'
Output:
car car car
So, that's the reason why we see this result in the OP:
get-vm | Where-Object { get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object { .IsClustered -eq $False} | Where-Object { get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object { .State -eq 'Running'} | ForEach-Object {
Resulting from that it seems that $_
can't be escaped. The only way I could achieve the request solution for the OP is performing a double substitution (which is not the best way when thinking about performance, maybe there is a better way).
TL;DR
$old = [regex]::Escape('get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {')
$new = ' get-vm | Where-Object {#_.IsClustered -eq $False} | Where-Object {#_.State -eq "Running"} | ForEach-Object {'
$testString = 'get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {'
($testString -replace $old, $new) -replace '#', '$'
Output:
get-vm | Where-Object {$_.IsClustered -eq $False} | Where-Object {$_.State -eq "Running"} | ForEach-Object {
So I used #_
instead of $_
to deactivate substitution algorithms in general, and perform the first substitution with #_
in $new
, which result in:
get-vm | Where-Object {#_.IsClustered -eq $False} | Where-Object {#_.State -eq "Running"} | ForEach-Object {
Now it is easy to substitute #
with $
, since $
alone is not defined in substitution elements.
Hope that solve the problem.
Upvotes: 0