Multiline powershell function inside batch script - powershell

I want to run .bat-script which calls some powershell function inside it. Function is not so small, so I want to split it. But I cannot do it, escape symbols doesn`t help ( ` ,^).
Script example:
set file=%1
set function="$file=$Env:file; ^
$hash = CertUtil -hashfile $file SHA256 | Select -Index 1"
powershell -command %function%

You can leave the quote at the end of each line like so:
set file=%1
set function="$file=$Env:file; "^
"$hash = CertUtil -hashfile $file SHA256 | Select -Index 1; "^
"example break line further...."
powershell -command %function%
The ^ works as multiline character but it also escapes the first character, so also a quote would be escaped.

Do not mix batchfile syntax with PowerShell. As #Stephan mentioned $function= won't work in batch file. You need to use set function= instead. Let's say I want to execute the following:
Get-Process
Get-ChildItem
Then the code should look like this:
set function=Get-Process; ^
Get-ChildItem;
And you start PowerShell with:
powershell -noexit -command %function%
-noexit added so that you can verify that the code was successfully executed.
Also keep in mind that what you pass to PowerShell is batch multiline and in PowerShell it's visible as one line so you have to remember about semicolon (which you actually do but I'm leaving this comment here for future readers).
There's also another option how to pass variable from batch script to PowerShell. You can do it like this:
set name=explorer
set function=get-process $args[0]; ^
get-childitem
powershell -noexit -command "& {%function% }" %name%
Explanation:
$args[0] represents first argument passed to the scriptblock. To pass that argument, add %name% after the scriptblock while starting powershell. Also, as pointed out in this answer (credits to #Aacini for pointing this out in comments), you have to add & operator and keep your scriptblock inside curly brackets { }.
Sidenote: to be honest, I'd avoid running scripts like this. Much simpler way would be to just save the file as .ps1 and run this in your batch file:
powershell -noexit -file .\script.ps1

Related

How do I have to change PowerShell variables code so that I can run it via CMD?

How do I have to change PowerShell code so that I can run it via CMD?
I came up with the following code:
$text_auslesen = Get-Content $env:APPDATA\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt
$text_auslesen.Replace("Count :","") > $env:APPDATA\BIOS-Benchmark\Count_only.txt
$text_auslesen = Get-Content $env:APPDATA\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt
$text_auslesen.Replace("Average :","") > $env:APPDATA\BIOS-Benchmark\Durchschnitt_only.txt
If I copy and paste it completely into a powershell, it can run. But now I have to put the code next to other code in a batch file. How do I have to adjust the code so that the cmd.exe executes the whole thing?
I suspect setting the variables via Powershell code is problematic here.
Unfortunately, a PS1 file is out of the question for my project.
To execute PowerShell commands from a batch file / cmd.exe, you need to create a PowerShell child process, using the PowerShell CLI (powershell.exe for Windows PowerShell, pwsh for PowerShell (Core) 7+) and pass the command(s) to the -Command (-c) parameter.
However, batch-file syntax does not support multi-line strings, so you have two options (the examples use two simple sample commands):
Pass all commands as a double-quoted, single-line string:
powershell.exe -Command "Get-Date; Write-Output hello > test.txt"
Do not use quoting, which allows you to use cmd.exe's line continuations, by placing ^ at the end of each line.
powershell.exe -Command Get-Date;^
Write-Output hello ^> test.txt
Note:
In both cases multiple statements must be separated with ;, because ^ at the end of a batch-file line continues the string on the next line without a newline.
Especially with the unquoted solution, you need to carefully ^-escape individual characters that cmd.exe would otherwise interpret itself, such as & and >
See this answer for detailed guidance.
Powershell -c executes PowerShell commands. You can do this from cmd, however, it looks like it needs to be run as administrator.
PowerShell -c "$text_auslesen = Get-Content $env:APPDATA\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt;
$text_auslesen.Replace('Count :','') > $env:APPDATA\BIOS-Benchmark\Count_only.txt;
$text_auslesen = Get-Content $env:APPDATA\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt;
$text_auslesen.Replace('Average :','') > $env:APPDATA\BIOS-Benchmark\Durchschnitt_only.txt"
It is possible to execute the PowerShell code in a batch file, but technically what you are doing is pulling a copy of it out and executing it someplace else. Here are 3 methods that I know of.
mklement0's answer addresses executing a copy of it that is passed as a parameter to PowerShell.
You could build a ps1 file from CMD, and then execute that ps1 file by passing it as a parameter to PowerShell.
And the method I've worked with the most is to pass specially designed PowerShell code to PowerShell that, when it runs, will load all, or part, of the current CMD file into memory and execute it there as a ScriptBlock. I have tried loading parts of the current CMD file, but my experience has been that this gets too complicated and I just stick with loading the entire current CMD file.
That last method is what I'm presenting here. The trick is to make the batch/CMD portion of the script look like a comment that is ignored by PowerShell, but still runs without throwing error messages in CMD. I'm not sure where I first found this trick, but it goes like this:
First, place <# : at the start of script. PowerShell sees this as the start of a comment, but CMD seems to ignore this line. I think CMD is trying to redirect < the contents of a non-existing file : to a non-existing command. But what does CMD do with the #? It works, and that's the important thing.
Place your batch code in lines following the <# :.
You end the batch/CMD part with a GOTO :EOF.
You then end the PowerShell comment with #>, but visually I find it easier to find <#~#>, which does the same job.
The rest of the file is your PowerShell code.
This version treats the PowerShell code as a function with defined parameters. The batch part builds %ARGS% and passes, with double quotes intact, to a PowerShell ScriptBlock that in turn is wrapped in another ScriptBlock. The PowerShell function is called twice with the same SourceFile parameter, but different DestinationFile and TextToRemove parameters. Perhaps there is a simpler way to reliably pass double quotes " in arguments passed to a ScriptBlock from batch, but this is the method I got working.
<# :
#ECHO OFF
SET f0=%~f0
SET SourceFile=%APPDATA%\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt
SET ARGS="%SourceFile%" "%APPDATA%\BIOS-Benchmark\Count_only.txt" "Count :"
PowerShell -NoProfile -Command ".([scriptblock]::Create('.([scriptblock]::Create((get-content -raw $Env:f0))) ' + $Env:ARGS))"
SET ARGS="%SourceFile%" "%APPDATA%\BIOS-Benchmark\Durchschnitt_only.txt" "Average :"
PowerShell -NoProfile -Command ".([scriptblock]::Create('.([scriptblock]::Create((get-content -raw $Env:f0))) ' + $Env:ARGS))"
GOTO :EOF
<#~#>
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$SourceFile,
[Parameter(Mandatory = $true, Position = 1)]
[string]$DestinationFile,
[Parameter(Mandatory = $true, Position = 2)]
[string]$TextToRemove
)
(Get-Content $SourceFile).Replace($TextToRemove, '') > $DestinationFile
This script passes a single parameter that, in PowerShell, is used by the Switch command to decide which section of PowerShell you intend on executing. Since we are not passing double quotes " in the args, the PowerShell lines can be greatly simplified. Information could still be passed to PowerShell by defining environmental variables in batch and reading them in PowerShell.
<# :
#ECHO OFF
SET f0=%~f0
PowerShell -NoProfile -Command .([scriptblock]::Create((get-content -raw $Env:f0))) Script1
PowerShell -NoProfile -Command .([scriptblock]::Create((get-content -raw $Env:f0))) Script2
GOTO :EOF
<#~#>
switch ($args[0]) {
'Script1' {
(Get-Content $env:APPDATA\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt).Replace("Count :", '') > $env:APPDATA\BIOS-Benchmark\Count_only.txt
break
}
'Script2' {
(Get-Content $env:APPDATA\BIOS-Benchmark\PowerShell-Protokoll-Auswertung.txt).Replace("Average :", '') > $env:APPDATA\BIOS-Benchmark\Durchschnitt_only.txt
break
}
default {}
}
The -c parameter is intended to solve this scenario.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pwsh?view=powershell-7.2#-command---c
If possible, it would be more efficient to invoke PowerShell\Pwsh directly rather than using a cmd wrapper.

Powershell script is failing when files with a single quote are passed through script. Alternate batch file is also failing with & and ! characters

This is a deceptively complex issue, but I'll do my best to explain the problem.
I have a simple wrapper script as follows called VSYSCopyPathToClipboard.ps1:
param (
[Parameter(Mandatory,Position = 0)]
[String[]]
$Path,
[Parameter(Mandatory=$false)]
[Switch]
$FilenamesOnly,
[Parameter(Mandatory=$false)]
[Switch]
$Quotes
)
if($FilenamesOnly){
Copy-PathToClipboard -Path $Path -FilenamesOnly
}else{
Copy-PathToClipboard -Path $Path
}
Copy-PathToClipboard is just a function I have available that copies paths/filenames to the clipboard. It's irrelevant to the issue, just assume it does what it says.
The way the wrapper is called is through the Windows right click context menu. This involves creating a key here: HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\.
Mine looks like this:
The command is as follows:
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -Path $files" "%1"
And similarly for the "Copy as Filename":
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -FilenamesOnly -Path $files" "%1"
I am using a tool here called SingleInstanceAccumulator. This allows me to pass multiple selected files to a single instance of PowerShell. If I didn't use this program and ran my command with multiple files selected it would launch multiple instances of PowerShell for each file selected. It's the next best thing to creating your own shell extension and implementing IPC etc.
This has been working great until today when I encountered a file with a single quote in its filename (I.E.testing'video.mov) and the entire script failed. It's failing because the delimiter I'm using with SingleInstanceAccumulator is also a single quote and PowerShell sees no matching quote... thus errors out.
I could fix this if my variables were static by just doubling up the offending single quote, but since my parameters are files I have no opportunity to escape the single quote beyond renaming the file itself ... which is a non-solution.
So now I have no clue how to handle this.
My first try at solving the problem was as such:
Create a batch file and redirect my registry command to it.
Change the SingleInstanceAccumulator delimiter to '/' (All files will be separated by a forward slash.)
Replace the offending single quote to two single quotes.
Replace the '/' delimiters with single quotes.
Finally pass the whole argument list back to Powershell.
This image demonstrates how the above process looks:
This is the batch file's code:
#echo off
setlocal EnableDelayedExpansion
:: This script is needed to escape filenames that have
:: a single quote ('). It's replaced with double single
:: quotes so the filenames don't choke powershell
:: echo %cmdcmdline%
set "fullpath=%*"
echo Before
echo !fullpath!
echo ""
echo After
set fullpath=%fullpath:'=''%
set fullpath=%fullpath:/='%
echo !fullpath!
:: pwsh.exe -noprofile -windowstyle hidden -command "%~dpn0.ps1 -Path !fullpath!
pause
Once I got that wired up I started celebrating ... until I hit a file with an ampersand (&) or an exclamation point (!). Everything fell apart again. I did a whole bunch of google-fu with regards to escaping the & and ! characters but nothing suggested worked at all for me.
If I pass 'C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\testing&video.mov' into my batch file, I get 'C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\testing back.
It truncates the string at the exact position of the ampersand.
I feel like there has to be a way to solve this, and that I'm missing something stupid. If I echo %cmdcmdline% it shows the full commandline with the &, so it's available somehow with that variable.
In conclusion: I'm sorry for the novel of a post. There is a lot of nuance in what I'm trying to accomplish that needs to be explained. My questions are as follows:
Can I accomplish this with Powershell only and somehow pre-escape single quotes?
Can I accomplish this with a batch file, and somehow pre-escape & and ! (and any other special characters that would cause failure)?
Any help at all would be hugely appreciated.
Edit1:
So in the most hideous and hackish way possible, I managed to solve my problem. But since it's so horrible and I feel horrible for doing it I am still looking for a proper solution.
Basically, to recap, when I do either of these variable assignments:
set "args=%*"
set "args=!%*!"
echo !args!
& and ! characters still break things, and I don't get a full enumeration of my files. Files with & get truncated, etc.
But I noticed when I do:
set "args=!cmdcmdline!"
echo !args!
I get the full commandline call with all special characters retained:
C:\WINDOWS\system32\cmd.exe /c ""C:\Tools\scripts\VSYSCopyPathToClipboardTest.bat" /C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\KylieCan't.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\The !Rodinians - Future Forest !Fantasy - FFF Trailer.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\Yelle - Je Veu&x Te Voir.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\Erik&Truffaz.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\my_file'name.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\testing&video.mov/"
So what I did was simply strip out the initial C:\WINDOWS\system32\cmd.exe /c ""C:\Tools\scripts\VSYSCopyPathToClipboardTest.bat" part of the string:
#echo off
setlocal enableDelayedExpansion
set "args=!cmdcmdline!"
set args=!args:C:\WINDOWS\system32\cmd.exe=!
set args=!args: /c ""C:\Tools\scripts\VSYSCopyPathToClipboard.bat" =!
set args=!args:'=''!
set args=!args:/='!
set args=!args:~0,-1!
echo !args!
pwsh.exe -noprofile -noexit -command "%~dpn0.ps1 -Path !args!
And... it works flawlessly. It handles any crazy character I throw at it without needing to escape anything. I know It's totally the most degenerate garbage way of approaching this, but not finding a solution anywhere leads me to desperate measures. :)
I am probably going to make the string removal a bit more universal since it literally breaks if I change the filename.
I am still VERY much open to other solutions should anyone know of a way to accomplish the same thing in a more elegant way.
A fully robust solution based on PowerShell's -Command (-c) CLI parameter that can handle ' characters in paths as well as $ and ` ones requires a fairly elaborate workaround, unfortunately:[1]
Use an aux. cmd.exe call that echoes the $files macro as-is and pipe that to pwsh.exe; make SingleInstanceAccumulator.exe double-quote the individual paths (as it does by default), but use no delimiter (d:"") in order to effectively output a string in the form "<path 1>""<path 2>""...
Make pwsh.exe reference the piped input via the automatic $input variable and split it into an array of individual paths by " (removing empty elements that are a side effect of splitting with -ne ''). The necessity for providing the paths via the pipeline (stdin) is discussed in more detail in this related answer.
The resulting array can safely be passed to your scripts.
Also, enclose the entire -Command (-c) argument passed to pwsh.exe in \"...\" inside the "-c:..." argument.
Note: You may get away without doing this; however, this would result in whitespace normalization, which (however unlikely) would alter a file named, say, foo bar.txt to foo bar.txt (the run of multiple spaces was normalized to a single space).
Escaping " characters as \" is necessary for PowerShell's -Command (-c) CLI parameter to treat them verbatim, as part of the PowerShell code to execute that is seen after initial command-line parsing, during which any unescaped " characters are stripped.
Therefore, the first command stored in the registry should be (adapt the second one analogously; note that there must be no space between the echo $files and the subsequent |):
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -d:"" "-c:cmd /c echo $files| pwsh.exe -noprofile -c \"& 'C:\Tools\scripts\VSYSCopyPathToClipboard.ps1' -Path ($input -split '\\\"' -ne '')\"" "%1"
Note:
If you modified your scripts to accept the paths as individual arguments rather than as an array, a much simpler solution via the -File CLI parameter (rather than -Command (-c)) is possible. This could be as simple as decorating the $Path parameter declaration with [Parameter(ValueFromRemainingArguments)] and then invoking the script without naming the target parameter explicitly (-Path):
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -d:" " "-c:pwsh.exe -noprofile -File \"C:\Tools\scripts\VSYSCopyPathToClipboard.ps1\" $files" "%1"
Note the use of -d:" " to make SingleInstanceAccumulator.exe space-separate the (double-quoted by default) paths. Since -File passes the pass-through arguments verbatim, there is no concern about what characters the paths are composed of.
Self-contained PowerShell sample code:
The following code defines a Copy Paths to Clipboard shortcut-menu command for all file-system objects (except drives):
No separate .ps1 script is involved; instead, the code passed to -Command / -c directly performs the desired operation (copying the paths passed to the clipboard).
The following helps with troubleshooting:
The full command line with which PowerShell was invoked ([Environment]::CommandLine) is printed, as is the list of paths passed ($file)
-windowstyle hidden is omitted to keep the console window in which the PowerShell commands visible and -noexit is added so as to keep the window open after the command has finished executing.
Prerequisites:
Download and build the SingleInstanceAccumulator project using Visual Studio (using the .NET SDK is possible, but requires extra work).
Place the resulting SingleInstanceAccumulator.exe file in one of the directories listed in your $env:Path environment variable. Alternatively, specify the full path to the executable below.
Note:
reg.exe uses \ as its escape character, which means that \ characters that should become part of the string stored in the registry must be escaped, as \\.
The sad reality as of PowerShell 7.2 is that an extra, manual layer of \-escaping of embedded " characters is required in arguments passed to external programs. This may get fixed in a future version, which may require opt-in. See this answer for details. The code below does this by way of a -replace '"', '\"' operation, which can easily be removed if it should no longer be necessary in a future PowerShell version.
# RUN WITH ELEVATION (AS ADMIN).
# Determine the full path of SingleInstanceAccumulator.exe:
# Note: If it isn't in $env:PATH, specify its full path instead.
$singleInstanceAccumulatorExe = (Get-Command -ErrorAction Stop SingleInstanceAccumulator.exe).Path
# The name of the shortcut-menu command to create for all file-system objects.
$menuCommandName = 'Copy Paths To Clipboard'
# Create the menu command registry key.
$null = reg.exe add "HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\$menuCommandName" /f /v "MultiSelectModel" /d "Player"
if ($LASTEXITCODE) { throw }
# Define the command line for it.
# To use *Windows PowerShell* instead, replace "pwsh.exe" with "powershell.exe"
# SEE NOTES ABOVE.
$null = reg.exe add "HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\$menuCommandName\command" /f /ve /t REG_EXPAND_SZ /d (#"
"$singleInstanceAccumulatorExe" -d:"" "-c:cmd /c echo `$files| pwsh.exe -noexit -noprofile -c \\"[Environment]::CommandLine; `$paths = `$input -split [char] 34 -ne ''; `$paths; Set-Clipboard `$paths\\"" "%1"
"# -replace '"', '\"')
if ($LASTEXITCODE) { throw }
Write-Verbose -Verbose "Shortcut menu command '$menuCommandName' successfully set up."
Now you can right-click on multiple files/folders in File Explorer and select Copy Paths to Clipboard in order to copy the full paths of all selected items to the clipboard in a single operation.
[1] An alternative is to use the -f option instead, which causes SingleInstanceAccumulator.exe to write all file paths line by line to an auxiliary text file, and then expands $files to that file's full path. However, this requires the target scripts to be designed accordingly, and it is their responsibility to clean up the auxiliary text file.

Passing newline character to PowerShell via Cmd

I'm trying to run a PowerShell script from Windows cmd.exe. The input to the PowerShell script is a string, which contains newline characters using PowerShell backtick escaping - i.e:
`r`n
For demonstration purposes, the input string is then written to the console, and also dumped to a file.
The issue I have is that when the script is run from cmd.exe using the syntax
powershell.exe script.ps1 "TEST`r`nTEST"
The newline characters in the string are not treated as newline, and are included literally in both the console output and the output text file.
TEST`r`nTEST
However, if I run this from a PowerShell environment, I get the expected result (i.e. the newline characters are parsed correctly, and a newline is inserted in the appropriate location).
TEST
TEST
Similarly, if I pass in \r\n instead of the escaped newline characters through Windows cmd.exe, and do a .replace in the PowerShell script
$date = $data.replace("\r\n","`r`n")
I get the expected output:
TEST
TEST
Is anyone able to shed some light on why this happens?
The test script is as follows:
param([string]$data) # data to send
Write-Host $data
[IO.File]::WriteAllText('d:\temp.txt', $data)
return 0
And the file is called from the command line as:
powershell.exe script.ps1 "TEST`r`nTEST"
The script is running on Windows Server 2012 R2, using PowerShell v4.0
tl;dr
Use -Command and pass the entire PowerShell command as a single string; e.g.:
C:\> powershell -NoProfile -Command "script.ps1 \"TEST`r`nTEST\""
TEST
TEST
Note how the internal " instances are escaped as \", which PowerShell requires when called from the outside (alternatively, for full robustness, use "^"" (sic) in Windows PowerShell and "" in PowerShell (Core) v6+).
In your specific case,
powershell -NoProfile -Command script.ps1 "TEST`r`nTEST" would have worked too, but generally that only works as intended if the string has no embedded spaces.
Given that -Command is the default up to PSv5.1, your command - as currently posted - should work as-is.
As of PowerShell v5.1, arguments passed to powershell.exe from the outside:
ARE subject to interpretation by PowerShell, including string interpolation, by default and when you use -Command (i.e., specifying neither -File nor -Command currently defaults to -Command).
Caveat: The default behavior will change in v6: -File will be the default then - see the relevant change on GitHub.
are NOT subject to interpretation if you use -File to invoke a script - (after potential interpretation by cmd.exe) PowerShell treats all arguments as literal strings.
Caveat: This behavior is currently being discussed with respect to v6, given that it is overtly problematic in at least one case: trying to pass Boolean values.
Optional reading: Why you should pass the entire PowerShell command as a single argument when using -Command:
When you use -Command with multiple arguments, PowerShell essentially assembles them into a single command line behind the scenes before executing it.
Any "..."-quoting around the individual arguments is lost in the process, which can have unexpected results; e.g.:
C:\> powershell -NoProfile -Command "& { $args.count }" "line 1`r`nline 2"
3 # !! "line 1`r`nline 2" was broken into 3 arguments
Given that the outer "..." quoting was removed in the process of parsing the command line, the actual command line that PowerShell ended up executing was:
C:\ PS> & { $args.Count } line 1`r`nline 2
3
To illustrate why, let's look at an equivalent command that uses explicit quoting:
C:\ PS> & { $args.Count } "line" "1`r`nline" "2"
In other words: After the enclosing " were removed, the resulting token was broken into multiple arguments by spaces, as usual.
The parameter will need to be reinterpreted as a PowerShell string. Will this get you down the road?
The reason your -replace did not work is that the original string actually contains a backtick. It needs to be escaped in the search string.
C:\src\t>type p1.ps1
Param([string]$s)
Write-Host $s
$p = Invoke-Expression `"$s`"
Write-Host $p
$p2 = $s -replace "``r``n","`r`n"
Write-Host $p2
C:\src\t>powershell -noprofile -file .\p1.ps1 "TEST`r`nTEST"
TEST`r`nTEST
TEST
TEST
TEST
TEST
Carriage return and Linefeed are bytes with values 13 and 10, you can't write them, you can't see them.
As a convenience, when writing PowerShell code, the language will let you write:
"`r`n"
in a double quoted string, and when processing PowerShell source code (and at no other time), it will read those and replace them with bytes value 13 and 10.
It is this line of code in the PowerShell tokenizer which does it.
There is nothing special about backtick-n to the cmd.exe interpreter, and nothing special about having it in a string - you can put it there in a single quoted string
'`n'
or replacing it in a string - except that you have to note when the replacement happens. e.g. in your comment:
For example, if you pass in 'r'n and then replace 'r'n with 'r'n, the 'r'n is still output literally
Because your code
-replace "`r`n"
becomes
-replace "[char]13[char]10"
and your string passed in from outside contains
`r`n
and they don't match. Backtick-n in a string isn't magic, strings are not all interpreted by the PowerShell engine as PowerShell code, nor are parameters, or anything. And it's only in that context - when you write your -replace code, that is when the swap for actual newline characters happens.

passing \ in argument to powershell script causes unexpected escaping

This is my powershell script test.ps1:
Write-Output $args;
Now suppose I have a batch script that calls this powershell script with all kinds of paths. One of those is c:\:
powershell -executionpolicy Bypass -file test.ps1 "c:\"
The output is:
c:"
Is there any way to quote my arguments such that c:\ would actually be taken and stored as is in the $args[0] variable? I know I can solve this quick'dirty by passing "c:\\", but that's not a real solution.
EDIT: using named parameters in test.ps1 doesn't make any difference:
[CmdletBinding()]
param(
[string]$argument
)
Write-Output $argument;
EDIT2: using a batch file instead works fine.
My test.bat script:
echo %~1
I run it:
test.bat "c:\"
Returns nicely:
c:\
Are you sure this comes form powershell and not from the program which invokes your statement? The backslash is no escape code in powershell.
my test.ps1 is working, when run from ise.
this works for me:
powershell -executionpolicy Bypass -command "test.ps1 -argument 'C:\'"
(end with quote double-quote)
Help file for PowerShell.exe says:
File must be the last parameter in the command, because 'all characters' typed after the file parameter name are "interpreted" as the script file path followed by the script parameters.
You are against Powershell.exe's command line parser, which uses "\" to escape quotes. Do you need quotes? Not in your case:
powershell -file test.ps1 c:\
prints
c:\
Similarly, this works too
powershell -file test.ps1 "c:\ "
c:\
but then your arg has that extra space which you would want to trim. BTW, Single quotes do not help here:
powershell -file test.ps1 'c:\'
'c:\'
If you need the final backlash to be passed to the command, you can use
$ArgWithABackslashTemp = $ArgWithABackslash -replace '\\$','\\'
&$ExePath $ArgWithABackslashTemp
Or, if the exe is smart enough to handle it without the trailing backslash
&$ExePath $ArgWithABackslash.trim('\')

Pass parameter from a batch file to a PowerShell script

In my batch file, I call the PowerShell script like this:
powershell.exe "& "G:\Karan\PowerShell_Scripts\START_DEV.ps1"
Now, I want to pass a string parameter to START_DEV.ps1. Let's say the parameter is w=Dev.
How can I do this?
Let's say you would like to pass the string Dev as a parameter, from your batch file:
powershell -command "G:\Karan\PowerShell_Scripts\START_DEV.ps1 Dev"
put inside your powershell script head:
$w = $args[0] # $w would be set to "Dev"
This if you want to use the built-in variable $args. Otherwise:
powershell -command "G:\Karan\PowerShell_Scripts\START_DEV.ps1 -Environment \"Dev\""
and inside your powershell script head:
param([string]$Environment)
This if you want a named parameter.
You might also be interested in returning the error level:
powershell -command "G:\Karan\PowerShell_Scripts\START_DEV.ps1 Dev; exit $LASTEXITCODE"
The error level will be available inside the batch file as %errorlevel%.
Assuming your script is something like the below snippet and named testargs.ps1
param ([string]$w)
Write-Output $w
You can call this at the commandline as:
PowerShell.Exe -File C:\scripts\testargs.ps1 "Test String"
This will print "Test String" (w/o quotes) at the console. "Test String" becomes the value of $w in the script.
When a script is loaded, any parameters that are passed are automatically loaded into a special variables $args. You can reference that in your script without first declaring it.
As an example, create a file called test.ps1 and simply have the variable $args on a line by itself. Invoking the script like this, generates the following output:
PowerShell.exe -File test.ps1 a b c "Easy as one, two, three"
a
b
c
Easy as one, two, three
As a general recommendation, when invoking a script by calling PowerShell directly I would suggest using the -File option rather than implicitly invoking it with the & - it can make the command line a bit cleaner, particularly if you need to deal with nested quotes.
Add the parameter declaration at the top of ps1 file
test.ps1
param(
# Our preferred encoding
[parameter(Mandatory=$false)]
[ValidateSet("UTF8","Unicode","UTF7","ASCII","UTF32","BigEndianUnicode")]
[string]$Encoding = "UTF8"
)
write ("Encoding : {0}" -f $Encoding)
Result
C:\temp> .\test.ps1 -Encoding ASCII
Encoding : ASCII
The answer from #Emiliano is excellent. You can also pass named parameters like so:
powershell.exe -Command 'G:\Karan\PowerShell_Scripts\START_DEV.ps1' -NamedParam1 "SomeDataA" -NamedParam2 "SomeData2"
Note the parameters are outside the command call, and you'll use:
[parameter(Mandatory=$false)]
[string]$NamedParam1,
[parameter(Mandatory=$false)]
[string]$NamedParam2