Having some trouble with Powershell -replace and Environment Variables from Batch File - powershell

I finally narrowed down the problem, but need help understanding why.
Invoking Powershell from a batch file, the following works to replace a string within a file:
Powershell -Command "$foobar=[IO.File]::ReadAllText("$Env:File") ;
$foobarred= $foobar -replace '(?<foo>.*Backup.).*.(?<bar>..Backup.)', '${foo}Enabled${bar}' ;
[IO.File]::WriteAllText("$Env:File", $foobarred); "
Now if I try this, the command fails:
Powershell -Command "$foobar=[IO.File]::ReadAllText("$Env:File") ;
$foobarred= $foobar -replace '(?<foo>.*$Env:StrStart.).*.(?<bar>..$Env:StrEnd.)', '${foo}$Env:StrVal${bar}' ;
[IO.File]::WriteAllText("$Env:File", $foobarred); "
If I use a variable passed in from Batch, it fails everytime. If I use PlainText in the command instead for the replacement value, it works just fine. Why does this happen?

My observations would be
you need to [regex]::Escape() arbitrary values when you build regular expressions dynamically.
PowerShell does not do any string interpolation in single-quoted strings, so things like '${foo}$Env:StrVal${bar}' will not work the way you want.
I'd use the following command:
(Get-Content "filename" -Raw) -replace (
'(.*' + [regex]::Escape("start string") + '.).*.(..' + [regex]::Escape("end string") + '.)'
),(
'$1' + "replacement string" + '$2'
) | Set-Content "filename"
called ad-hoc from a batch file as follows (compressed onto one line):
#echo off
setlocal
set "FILENAME=filename"
set "START=start string"
set "END=end string"
set "REPLACEMENT=replacement string"
set "PSCMD=(gc $Env:FILENAME -Raw) -replace ('(.*' + [regex]::Escape($Env:START) + '.).*.(..' + [regex]::Escape($Env:END) + '.)'),('$1' + $Env:REPLACEMENT + '$2') | sc $Env:FILENAME"
powershell -NoLogo -Command "&{%PSCMD%}"
But this is disproportionately hard to maintain.
I'd recommend writing a .ps1 file and passing named arguments, instead of juggling environment variables.
# MyReplace.ps1
param(
[string]$Filename,
[string]$Start,
[string]$End,
[string]$Replacement
)
$ErrorActionPreference = "Stop"
$content = Get-Content $Filename -Raw
$content = $content -replace ('(.*' + [regex]::Escape($Start) + '.).*.(..' + [regex]::Escape($End) + '.)'),('$1' + $Replacement + '$2')
$content | Set-Content $Filename
and in batch
powershell -NoLogo -File MyReplace.ps1 -Filename "filename" -Start "start string" -End "end string" -Replacement "replacement string"
That seems more manageable to me.

At the advice of #Gerhard, adding what I found into the fray as an answer, but did ultimately giving #Tomalak the credit for the better answer overall.
In order to accept variables into both the match pattern and the replace pattern, you have to concatenate the strings (similar to how it is done in Visual Basic).
Reference below:
I have split the command up into multiple lines for readability - if you use this, place it on one line.
Important - when using this, be sure to remove the line-breaks if you use it in a batch file. Also - be wary - I have had some circumstances where even using Set-Content can change the Encoding of the file. I much prefer the secondary solution offered down below.
Powershell -Command "$pattern= '(?<RangeStart>.*' + [regex]::Escape($Env:StrStart) + '.).*.(?<RangeEnd>..' + [regex]::Escape($Env:StrEnd) + '.)' ;
$repl= '${RangeStart}' + $Env:StrVal + '${RangeEnd}' ;
$fil2parse=(Get-Content $Env:FileTOParse) -replace $pattern, $repl | Set-Content $Env:FileTOParse; "
This solution works as well, but I have had much fewer issues with it changing Encoding.
Powershell -Command "$pattern= '(?<RangeStart>.*' + [regex]::Escape($Env:StrStart) + '.).*.(?<RangeEnd>..' + [regex]::Escape($Env:StrEnd) + '.)' ;
$repl= '${RangeStart}' + $Env:StrVal + '${RangeEnd}' ;
$fil2parse=[IO.File]::ReadAllText("$Env:FileTOParse") ;
$filParsed= $fil2parse -replace $pattern, $repl ;
[IO.File]::WriteAllText("$Env:FileTOParse", $filParsed); "

Related

How to add a line to .txt file with special characters and variables in PowerShell

I have a PowerShell file e.g. C:\MyPowerShell.ps1 and I would like to have the following line there:
$myNewVariable = "Lukas"
Where string Lukas will be taken from variable $name. The variable $name will be declared before I run a command to do this action. I would like to use another PowerShell command to do that.
$name = "Lukas" <br>
Add-Content C:\MyPowerShell.txt ???
Please help me ;-)
Or use the -f Format operator:
$name = 'Lukas'
Add-Content -Path 'C:\MyPowerShell.txt' -Value ('$myNewVariable = "{0}"' -f $name)
Use an expandable (interpolating) string ("...") in which you individually `-escape $ characters in tokens you do not want to be expanded as variable references / subexpressions; similarly, escape embedded " characters as `" ("" would work too):
$name = 'Lucas'
Add-Content C:\MyPowerShell.txt -Value "`$myNewVariable = `"$name`""
Alternatively, use the -f operator, as shown in Theo's helpful answer.
This answer compares and contrasts these two approaches.

How to get powershell to output variable with quotes around it

Despite going over the documentation on single and double quotes using 'about_quoting_rules', I can't seem to get the output I'm looking for.
A little backstory:
I have an excel doc and I'm using powershell to create a foreach over every entry. This, in turn, is being used to generate a robocopy command to kick off a sync job.
The part of my script that's pertinent to this is below:
Foreach($script in $roboSource)
{
$StandardSwitches = "/copy:DAT /s /dcopy:DAT /V /L"
$log = "/log:`"$($logPath)$($logFileName)`"" #`
$FileSource = "$($script.a)"
$FileDestination = "$($script.a)"
$RoboArgs = "I:\" + $FileSource + " " + "Z:\" + $FileDestination + " " + $StandardSwitches + " " + $log
}
So, right now, $RoboArgs outputs this:
I:\XXXXX\XXXXX\X\XXXXXXX\XXXXX\XXXX Z:\XXXXX\XXXXX\X\XXXXXXX\XXXXX\XXXX /copy:DAT /s /dcopy:DAT /V /L /log:"C:\XXXXX\XXXXX\X\XXXXXXX\XXXXX\XXXX"
What I need $RoboArgs to output is this:
"I:\XXXXX\XXXXX\X\XXXXXXX\XXXXX\XXXX" "Z:\XXXXX\XXXXX\X\XXXXXXX\XXXXX\XXXX" /copy:DAT /s /dcopy:DAT /V /L /log:"C:\XXXXX\XXXXX\X\XXXXXXX\XXXXX\XXXX"
I've tried adding the backticks to the string, and encapsulating the string and variable together:
$RoboArgs = `""I:\" + $FileSource`"" + " " + "Z:\" + $FileDestination + " " + $StandardSwitches + " " + $log
But regardless of what I try, nothing is resulting in the output that I'm looking for. Any nudge in the right direction would be very helpful and appreciated!
In Powershell ` followed by quotes works, like '\' in other languages:
For example:
"`"toto`""
prints
"toto"
I suggest using PowerShell's string-formatting operator, -f:
$RoboArgs = '"I:\{0}" "Z:\{1}" {2} {3} {4}' -f
$FileSource, $FileDestination, $StandardSwitches, $log
Using '...' (single-quoting) as the delimiters allows you to embed " instances as-is.
{0} is a placeholder for the 1st RHS operand, {1} for the 2nd, and so on.
Try delimiting your string with single quotes. Any double qoutes inside will be preserved in the resulting string.
Example:
$stringWithQuotes = '"' + (pwd) + '"'
And you can also do it the other way around if you want single quotes in your string:
$stringWithQuotes = "'" + (pwd) + "'"

Piping from a variable instead of file in Powershell

Is ther any way in Powershell to pipe in from an virable instead of a file?
There are commands that I need to pipe into another command, right now that is done by first creating a file with the additional commands, and then piping that file into the original command. Code looks somehting like this now:
$val = "*some command*" + "`r`n" + "*some command*" + "`r`n" + "*some command*"
New-Item -name Commands.txt -type "file" -value $val
$command = #'
db2cmd.exe /C '*custom db2 command* < \Commands.txt > \Output.xml'
'#
Invoke-Expression -Command:$command
So instead of creating that file, can I somehow just pipe in $val insatead of Commands.txt?
Try this
$val = #("*some command*1","*some command2*","*some command3*")
$val | % { db2cmd.exe /C $_ > \Output.xml }
You should be able to pipe in from $val provided you use Write-Output or its shorthand echo, but it may also be worth trying passing the commands directly on the command line. Try this (and if it doesn't work I can delete the answer):
PS C:\> filter db2cmd() { $_ | db2cmd.exe ($args -replace '(\\*)"','$1$1\"') }
PS C:\> $val = #"
>> *custom db2 command*
>> *some command*
>> *some command*
>> *some command*
>> "#
>>
PS C:\> db2cmd /C $val > \Output.xml
What happens here is that Windows executables receive their command line from a single string. If you run them from cmd.exe you cannot pass newlines in the argument string, but Powershell doesn't have that restriction so with many programs you can actually pass multiple lines as a single argument. I don't know db2cmd.exe so it might not work here.
The strange bit of string replacement is to handle any double quotes in the arguments: Powershell doesn't quote them and the quoting rules expected by most exe files are a bit bizarre.
The only limitation here would be that $val must not exceed about 32,600 characters and cannot contain nulls. Any other restrictions (such as whether non-ascii unicode characters work) would depend on the application.
Failing that:
echo $val | db2cmd.exe /C '*custom db2 command*' > \Output.xml
may work, or you can use it in combination with the filter I defined at the top:
echo $val | db2cmd /C '*custom db2 command*' > \Output.xml

Trying to run legacy executables from powershell script

I am looking to run net.exe from a script and I am having some trouble with spaces. Here is the code...
# Variables
$gssservers = Import-Csv "gssservers.csv"
$gssservers | Where-Object {$_.Tier -match "DB"} | Foreach-Object {
net.exe use "\\"$_.Name '/user:'$_.Name'\Administrator' $_.Pass
$sqlcheck = sc.exe \\$gsssql[1] query "WUAUSERV"
}
When I set line 5 to Write-Host I see that there are spaces that are added outside of anywhere I have quotes which is breaking the net.exe command. How can I remove those spaces?
For anyone questioning how I am doing this, the net.exe command is the only way I can get to these machines as WMI is blocked in this enclave.
My first guess is that you've got "invisible" spaces in your CSV file. For example their is likely a trailing whitespace after the names of your servers in the CSV that your eyes of course don't see. You can fix that either by fixing the CSV file, or using .Trim() on your imported strings -- i.e. $_.Name.Trim()
If that's not the case, or not the only issue, then this is something I've had issues with to. When I have complicated strings like your desired net.exe arguments I've liked to take precautions and get extra pedantic with defining the string and not rely on PowerShell's automatic guessing of exactly where a string begins and ends.
So, instead of baking your parameters inline on your net.exe command line hand-craft them into a variable first, like so
$args = '\\' + $_.name + '/user:' + $_.name + '\Administrator' + $_.pass
If you write-Host that out you'll see that it no longer has your rogue spaces. Indeed you may notice that it no longer has enough spaces, so you'll have to get a little explicit about where they belong. For instance the above line doesn't put the proper spaces between \\servername and /user, or between the username and password, so you'd have to add that space back in, like so.
$args = '\\' + $_.name + ' /user:' + $_.name + '\Administrator ' + $_.pass
Notice the explicit spaces.
I finally resolved this myself using #EdgeVB's solution. The code ended up like this...
# Variables
$gssservers = Import-Csv "gssservers.csv"
$gssservers | Where-Object {$_.Tier -match "DB"} | Foreach-Object {
$cmd1 = 'use'
$arg1 = '\\' + $_.Name
$arg2 = ' /user:' + $_.Name + '\Administrator '
& net.exe $cmd1 $arg1 $arg2 $_Pass
$cmd2 = 'query'
$svc1 = 'mssqlserver'
& sc.exe $arg1 $cmd2 $svc1 | Write-Host
}
Not only do you need to bake the variables in beforehand, but they also cannot cross certain thresholds (for instance, if "use" and "\" are in the same variable, it breaks.

replace exception in powershell

I'm a beginner in powershell and know C# pretty well. I have this command http://www.f2ko.de/programs.php?lang=en&pid=cmd that downloads stuff. I'm writing this script to download all the sgf go games from this url http://www.gogameworld.com/gophp/pg_samplegames.php, and was trying to write a powershell script to do it for me. So I wrote a script:
Get-Content test.txt|
ForEach-Object
{
if($_ -eq "=`"javascript:viewdemogame(`'*.sgf`')`" tit")
{
$filename = $_ -replace '=`"javascript:viewdemogame(`''
$filename = $filename -replace '`')`" tit'
&"(Path)/download.exe" ("http://www.gogameworld.com/webclient/qipu/" + $filename)
}
}
However, when I run the script, I keep getting this error:
Unexpected token '`'' in expression or statement.
At (PATH)\test.ps1:7 char:37
+ $filename = $filename -replace '`' <<<< )'
+ CategoryInfo : ParserError: (`':String) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken
I've looked at the script lots of times and still can't figure out whats wrong. Thanks.
Try this, read the content of the file as one string and then use the Regex.Matches to get all occurrences of the text contained in the parenthesis:
$content = Get-Content test.txt | Out-String
$baseUrl = 'http://www.gogameworld.com/webclient/qipu/'
[regex]::matches($content,"javascript:viewdemogame\('([^\']+)'\)") | Foreach-Object{
$url = '{0}{1}' -f $baseUrl,$_.Groups[1].Value
& "(Path)/download.exe" $url
}
here's an explanation of the regex pattern (created with RegexBuddy):
javascript:viewdemogame\('([^\']+)'\)
Match the characters “javascript:viewdemogame” literally «javascript:viewdemogame»
Match the character “(” literally «\(»
Match the character “'” literally «'»
Match the regular expression below and capture its match into backreference number 1 «([^\']+)»
Match any character that is NOT a ' character «[^\']+»
Between one and unlimited times, as many times as possible, giving back as needed (greedy) «+»
Match the character “'” literally «'»
Match the character “)” literally «\)»
Match the character “"” literally «"»
'{0}{1}' is used with the -f operator to create a string. {0} maps to the first value on the right hand side of the operator (e.g $baseUrl) and {1} is mapped to the second value. Under the hood, PowerShell is suing the .NET String.Format method. You can read more about it here: http://devcentral.f5.com/weblogs/Joe/archive/2008/12/19/powershell-abcs---f-is-for-format-operator.aspx
'')" tit'
The -replace operator takes 2 arguments, comma separated. The first is a regular expression that matches what you want replaced. The second is the string you want to relace that with. You appear to be missing the second argument.