Calling powershell function with parameter from .cmd or .bat file - powershell

I have written a powershell script which is a complete function taking parameters (e.g. function name (param) { } ) and below this is a call to the function, with the parameter.
I want to be able to call this function in its .ps1 file, passing in the parameter. How would I be able to package a call to the function via a .bat or .cmd file? I am using Powershell v2.0.

You should use so called "dot-sourcing" of the script and the command with more than one statement: dot-sourcing of the script + call of the function with parameters.
The test script Test-Function.ps1:
function Test-Me($param1, $param2)
{
"1:$param1, 2:$param2"
}
The calling .bat file:
powershell ". .\Test-Function.ps1; Test-Me -Param1 'Hello world' -Param2 12345"
powershell ". .\Test-Function.ps1; Test-Me -Param1 \"Hello world\" -Param2 12345"
Notes: this is not a requirement but I would recommend enclosing the entire command text with double quotation marks escaping, if needed, inner quotation marks using CMD escape rules.

I believe all you have to do is name the parameters in the call to the script like the following:
powershell.exe Path\ScripName -Param1 Value1 -Param2 Value2
Param1 and Param2 are actual parameter names in the function signature.
Enjoy!

To call a PowerShell function from cmd or batch with arguments you need to use the -Commmand Parameter or its alias -C.
Romans answer will work with PowerShell 5.1 for example but will fail for PowerShell 7.1.
Quote from an issue I left on GitHub on why the same command didn't work is:
So as to support Unix shebang lines, pwsh's CLI now defaults to the -File parameter (which expects only a script-file path), whereas powershell.exe default to -Command / -c.
To make your commands work with pwsh, you must use -Command / -C explicitly.
So if you have a PowerShell file test.ps1 with:
function Get-Test() {
[cmdletbinding()]
Param (
[Parameter(Mandatory = $true, HelpMessage = 'The test string.')]
[String]$stringTest
)
Write-Host $stringTest
return
}
And the batch file will then be:
rem Both commands are now working in both v5.1 and v7.1.
rem v7.1
"...pathto\pwsh.exe" -NoExit -Command ". '"...pathto\test.ps1"'; Get-Test ""help me"""
rem v5.1
powershell.exe -NoExit -Command ". '"...pathto\test.ps1"'; Get-Test ""help me"""
The quotes around ...pathto\test.ps1 are a must if your .ps1 contains spaces.
The same goes for ...pathto\pwsh.exe
Here's the Github issue I posted in full:
https://github.com/PowerShell/PowerShell/issues/15281

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.

Call program from powershell.exe command and manipulate parameters

I'm trying to call an EXE file program that accepts command line parameters from PowerShell. One of the parameters I'm required to send is based on the string length of the parameters.
For example,
app.exe /param1:"SampleParam" /paramLen:"SampleParam".length
When I run the above, or for example:
notepad.exe "SampleParam".length
Notepad opens with the value 11 as expected.
I would like to achieve the same result when calling PowerShell from cmd / task scheduler.
For example,
powershell notepad.exe "SampleParam".length
But when I do that I get "SampleParam".length literally instead of the "SampleParam".length calculated value.
The expected result was:
running notepad.exe 11
Use the -Command parameter for powershell.exe:
powershell -Command "notepad.exe 'SampleParam'.length"
Be careful with the "'s since they can be picked up by the Windows command processor. This will also work:
powershell -Command notepad.exe 'SampleParam'.length
But this will not:
powershell -Command notepad.exe "SampleParam".length
I'd suggest using variables to store your string, etc.
$Arg1 = 'SampleParam'
## This will try to open a file named 11.txt
powershell notepad.exe $Arg1.Length
In your specific example:
app.exe /param1:$Arg1 /paramLen:$Arg1.Length
Utilizing splatting:
## Array literal for splatting
$AppArgs = #(
"/param1:$Arg1"
"/paramLen:$($Arg1.Length)"
)
app.exe #AppArgs

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('\')

Escaping arguments when passing %* from batch script as $args to powershell script

I have a batch script that takes any number of arguments (list of files) and executes a powershell script with the following command structure:
"%POWERSHELL%" -Command "%SCRIPT%" %*
%POWERSHELL% is the path to PowerShell.exe, and %SCRIPT% is my powershell script that interprets that receives %* as $args. The problem is that if I pass in something like the filename test$file.name, PowerShell receives test.name, presumably because $file is interpreted as an empty variable.
Is there a good way to escape each argument with single quotes or backticks from the batch script, or otherwise deal with this?
Escape $ characters before you pass %* to the PowerShell script.
set ARGS=%*
set ARGS=%ARGS:$=`$%
"%POWERSHELL%" -Command "%SCRIPT%" %ARGS%

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