Embedding PowerShell in batch - windows

I want to run a few PowerShell commands through a batch file. Very similar question has been asked but I dont want to run a seperate shell file from a batch. Instead I want to embed PowerShell commands to a batch file.
When I try to run
powershell -command "&{$var = "something"}"
I get the following error:
something : The term 'something' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:10
&{$var = something}
CategoryInfo : ObjectNotFound: (something:String) [], CommandNotFoundException
FullyQualifiedErrorId : CommandNotFoundException
But if I run something like
powershell -command "&{echo "something"}"
Then everything is fine :/
Am I doing a syntax error or something? And please don't give answers like "Instead of using PowerShell commands use batch commands etc..."
Thanks in advance!

You can not use nested quotes when defining the value of a variable as a string. You may either use apostrophes instead of the nested quotes:
powershell -command "&{$var = 'something'; echo $var}"
... or escape the nested quotes:
powershell -command "&{$var = \"something\"; echo $var}"
Another example:
powershell -command "&{$var = 'one'+\" two\"; echo $var}"

Maybe a bit late but I program pure powershell scripts and embed the complete script in a .cmd file to bypass the execution policy setting. I have 2 variants pick the one you like. Both are one-liners that you put on the first line of the .cmd file. Starting from the second line you just program in pure powershell. No need for modifying anything in your powershell script.
Variant1:
#type "%0" | findstr /v "^#type \"%0\" | findstr /v " | PowerShell.exe -noprofile - & goto :eof
Varant2:
#powershell -command "(Get-Content '%0') | select -skip 1 " | powershell -noprofile - & goto :eof

Related

What registry commands allow me to run PowerShell 5 / CMD as admin from the right click context menu from a folder that has an apostrophe in it?

I am trying to set up my right click context menu to launch both CMD and PowerShell5 at the current directory. My PowerShell 7 commands work fine.
This is what I got:
The registry command to open PowerShell5 as Admin is as follows:
PowerShell -Command "Start-Process cmd -ArgumentList '/s,/k,pushd %V && start PowerShell -NoExit && exit' -Verb RunAs"
The registry command to open the Command Prompt as Admin is as follows:
pwsh -noprofile -windowstyle hidden -Command "Start-Process cmd.exe -ArgumentList '/s,/k,pushd,%V' -Verb RunAs"
The registry command I have to open the Node command prompt is this:
cmd.exe /s /k "pushd "%V" & "C:\Program Files\nodejs\nodevars.bat""
(I couldn't figure out how to open the node prompt as admin).
Today, I realized that all of these commands fail if the directory I'm opening to has an apostrophe in the path or folder name. I've tried so many different registry commands and combinations of quotes and escape characters and nothing is working. It's getting enormously frustrating.
Does anyone know of any working commands to open both CMD and PowerShell 5 in directories that have an apostrophe?
Any help at all would be greatly appreciated.
The keys to the solution are:
Launch via cmd.exe and pipe (|) the literal folder path being passed via %V to powershell.exe:
cmd /c <NUL set /p="%V" is a trick that echoes the value of %V without double quotes, even if the value contains cmd.exe metacharacters (it also echoes without a trailing newline, but that is not a problem here).
In the powershell.exe command line being piped to, the folder path can then be referenced indirectly, via the automatic $input variable, which provides access to the stdin (piped) input.
This prevents problems that would arise if %V were embedded directly in the command line, notably with respect to paths containing $ and ` characters.
Also, the PowerShell command can perform string replacement on the value of $env:_dir in order to double embedded ' chars, which allows embedding the value in a verbatim '...' string.
Nested "..." quoting for PowerShell requires intricate escaping using \ as the escape character.
Note: The commands invariably cause creation of a auxiliary, transitory console window, which the PowerShell commands hide as quickly possible and which then auto-closes. This will cause some brief visual disruption.
Avoiding this would require an additional, GUI-subsystem helper executable for launching the command lines without a visible (initial) console window. This extra level of indirection would further complicate quoting and escaping.
Note:
The following commands are designed to be placed verbatim in the registry. Doing so programmatically complicates escaping further.
The commands use powershell.exe, the Windows PowerShell CLI, but they should also work with pwsh.exe, the cross-platform, install-on-demand PowerShell (Core) 7+ CLI; depending on how you installed the latter, you may have to use its full path in the commands below.
You can test-drive the commands as follows:
Replace %V with the literal path of a folder of interest.
Submit via the Windows Run dialog (WinKey-R).
Commands for elevated sessions (run as admin):
A powershell.exe (Windows PowerShell) sesssion:
cmd /c <NUL set /p="%V" | powershell.exe -WindowStyle Hidden -NoProfile -Command "Start-Process -Verb RunAs powershell.exe -ArgumentList ('-NoExit -Command \"Push-Location -LiteralPath ''{0}''\"' -f $input.Replace(\"'\", \"''\"))"
A cmd.exe session:
cmd /c <NUL set /p="%V" | powershell.exe -WindowStyle Hidden -NoProfile -Command "Start-Process -Verb RunAs cmd -ArgumentList \"/k pushd \"\"$input\"\"\""
A cmd.exe session with the Node.js environment set up:
cmd /c <NUL set /p="%V" | powershell.exe -WindowStyle Hidden -NoProfile -Command "Start-Process -Verb RunAs cmd -ArgumentList \"/k pushd \"\"$input\"\" ^& \"\"C:\Program Files\nodejs\nodevars.bat\"\"\""
Note: If you wanted to use environment-variable %ProgramFiles% instead of hard-coding C:\Program Files as part of the Node.js initialization-batch file path for increased robustness, you'd have to define the registry value as REG_EXPAND_SZ.
Running wt.exe (Windows Terminal), as discovered by you, with the notable need to escape ; chars. in the path as \;, because ; is a metacharacter on the wt.exe command line).
cmd /c <NUL set /p="%V" | powershell.exe -WindowStyle Hidden -NoProfile -Command "Start-Process -Verb RunAs wt.exe -ArgumentList (\"-d \"\"{0}\" -f $input.Replace(';','\;'))"
Note: This approach is shell-agnostic. That is, Windows Terminal itself sets the working directory and then runs whatever shell is configured to be its default.
Commands for non-elevated sessions:
The need for nested invocation of PowerShell then falls away, which simplifies the commands.
However, for opening a PowerShell session special considerations apply:
A different approach for passing the folder path verbatim is required: an auxiliary environment variable, _dir is set, which the PowerShell commands can access as $env:_dir.
The visual disruption by an auxiliary, transitory console window, you have two options with the following tradeoffs:
Avoid the disruption, which has the disadvantage that cmd.exe's console-window settings are applied (and that the initial cmd.exe process used to launch the PowerShell session stays alive as the PowerShell process' parent process; they terminate together, however).
Live with the disruption (as is inevitable with elevation), which has the advantage that the usual console settings associated with the PowerShell executable are used.
No visual disruption, but use of cmd.exe's console-window settings even for PowerShell:
A powershell.exe session:
cmd /c title Windows^ PowerShell & pushd "%V" & powershell.exe
A cmd.exe session (append & "C:\Program Files\nodejs\nodevars.bat" for the Node.js initialization):
cmd /k pushd "%V"
Visual disruption, but use of PowerShell's console-window settings:
A powershell.exe session:
cmd /c pushd "%V" & start powershell.exe
Note:
The above will use PowerShell's usual console-window settings, but show the full executable path as the window's title.
While you could change the title with start "Windows PowerShell" powershell.exe, for instance, default settings then apply - you could customize them, however.
An alternative is to change the window title from inside PowerShell (however, the change won't take effect until PowerShell is fully loaded):
cmd /c pushd "%V" & start powershell.exe -NoExit -Command [Console]::Title='Windows PowerShell'
Finally, if you invoke pwsh.exe by its full path and that path contains spaces, you need to double-quote the spaces individually, because double-quoting the path as a whole would cause it to be being mistaken for the window title argument (if you explicitly pass an (of necessity double-quoted) title argument, no extra work is needed); e.g.:
:: Note the individually quoted space (" ")
cmd /c pushd "%V" & start C:\PowerShell" "7\pwsh.exe

how to debug mixed cmd and powershell scripts to solve special character problems like ampersand &

I am creating a dos batch script (cmd.exe) .cmd which through powershell elevates itself as administrator.
But if it's stored in a folder that contains both spaces and ampersand in its name, it won't work.
I am not an expert so I searched the web, but I could not find a solution or documentation to solve the problem. So I would also like to know how to debug this particular situation and the possible solutions to the problem and where to find the documentation describing the solution or the steps to get there .. (if any).
To recreate the problem: I created a folder under the desktop named "Pie & tea" without quotes. And in the folder I created a script "test.cmd" inside which I put only the essential to reproduce the problem. This is the content of the script:
#echo off
pause
PowerShell start "%~f0" -verb runas
pause
goto: eof
I run the script by double clicking on it (this is the desired mode). An error is shown with this code (I translate from italian with a translator):
Press any key to continue. . .
In row: 1 car: 34
+ start C:\Users\fposc\Desktop\Pie & tea\test.cmd -verb runas
+ ~
Ampersand (&) not allowed. The & operator is reserved for future use. Use "&" to pass the ampersand as
string.
+ CategoryInfo: ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId: AmpersandNotAllowed
Press any key to continue. . .
And following some instructions I modified the line of code with powershell by tripling the double quotes:
PowerShell start """%~f0""" -verb runas
But the cmd window opens and after pressing the space the UAC starts and after giving the consent another window appears for a moment but it closes immediately and I could not see any errors.
I have seen on the web any solutions using single quotes ' or the powershell escape character ` or the caret ^ of the dos batch or the --% parameter of the powershell etc.
But i was unable to resolve.
I do not put the attempts made here because I copied in a stupid way (since I am not familiar with the mixed cmd / powershell operation) and I made numerous random attempts combining the various solutions. Without any positive outcome.
I have also seen solutions that I would like to evaluate, such as:
change directories and use relative paths.
prepare string variables and replace characters.
Use powershell -encodedcommand to pass parameters.
But the preferred solution is the one that explains how to escape characters on the single line of code PowerShell start "%~f0" -verb runas and how to get there and the reference documentation.
Some links visited:
VBS Shell.Application and PowerShell Start-Process do not escape ampersands internally
Quoting issues with PowerShell
Issues on windows when the username contains &
Executing PowerShell script via explorer context menu on items containing ampersands in their names
Stackoverflow: batch file deal with Ampersand (&) in folder name
Stackoverflow: How to access file paths in PowerShell containing special characters
Stackoverflow: How to pass strings to powershell from cmd with ampersands in values?
Debugging tips:
If something goes wrong on the PowerShell side, you'll see the error message in the calling cmd.exe session; to print the command line that PowerShell itself sees, prepend [Environment]::CommandLine; to the command(s) being passed.[1]
To make PowerShell's Start-Process cmdlet (whose built-in alias is start) launch the batch file in a cmd.exe process that keeps the session open, invoke cmd.exe explicitly, with cmd /k, and pass it the batch-file path. Once you're sure everything works as expected, replace /k with /c (to create a session that terminates when the batch file terminates).
The primary problem in your case:
Regrettably, if a batch-file path contains & characters, a bug in cmd.exe requires manual ^-escaping of the & as well as spaces, and possibly other cmd.exe metacharacters, even if the path is enclosed in "..." as a whole; additionally, it seems that the full batch-file path must be used on invocation (which %~f0 ensures):
For instance, to invoke C:\foo\a & b.cmd, cmd /k "C:\foo\a & b.cmd" is not enough - you must use cmd /k "C:\foo\a^ ^&^ b.cmd"
The command below uses a -replace operation to perform this ^-escaping in PowerShell code, on the value of %~f0, which cmd.exe expands to the batch file's full path.
The following solution:
uses aux. environment variables to make it work in most situations, even with paths containing other exotic characters, such as $, % and '.
also includes support for passing arguments through on invocation.
See the bottom section for the evolution of the solution, which includes detailed explanations.
#echo off
:: Save the full path of this batch file in an aux. environment variable.
set "__THISFILE=%~f0"
:: Save the arguments received in an aux. environment variable.
:: Note: The space after "=" is required for the PowerShell command
:: to also work if *no* arguments were passed.
set __ARGS= %*
:: Unless already elevated, relaunch this batch file,
:: with arguments passed through.
:: Note: Replace `exit /b` with just `exit` to
:: close the current console window after relaunching.
net session >NUL 2>NUL || (powershell.exe -noprofile -c "Start-Process -Verb RunAs cmd /k, ($env:__THISFILE -replace '[ &%%^]', '^$&'), $env:__ARGS" & exit /b)
:: Reset the aux. variables.
set __ARGS=
set __THISFILE=
echo Now running with elevation. Arguments received:
echo. %*
Evolution of the solution above:
If your batch-file paths do not contain ' characters:
Embedded '...' quoting is used in the PowerShell command below, so as to avoid having to escape embedded " quotes, which can get tricky; this assumes that the batch-file path itself contains no ' chars (see below if your paths do contain ' chars.)
#echo off
:: Unless already elevated, relaunch this batch file with elevation.
net session >NUL 2>NUL || (powershell.exe -c "Start-Process -Verb RunAs cmd /k, ('%~f0' -replace '[ &]', '^$&')" & goto :eof)
echo Now running with elevation.
Note:
net session >NUL 2>NUL is a simple trick to test if the current process is elevated (being run as admin) or not: it only succeeds in elevated sessions (reflected in exit code 0, which the || and && operators implicitly act on).
-c (-Command) makes it explicit that command(s) are being passed to powershell.exe; while not strictly necessary, it is a good habit to form, given that pwsh.exe, the PowerShell (Core) 7+ CLI, now requires it (see the linked CLI documentation below).
/k, ... creates an array of arguments, which are positionally passed to the -ArgumentList parameter of Start-Process - in essence, Start-Process builds a command line from the executable name, cmd, and all arguments by string concatenation with spaces behind the scenes - see this answer to your follow-up question for details.
If your batch-file paths contain ' chars., embedded "..." quoting must be used:
With powershell.exe, the Windows PowerShell CLI, the most robust approach - which is indeed required here - is to use "^"" (sic) to escape each embedded ", as shown below.
(By contrast, pwsh.exe, the PowerShell (Core) 7+ CLI now fortunately accepts "".)
net session >NUL 2>NUL || (powershell.exe -nop -c "Start-Process -Verb RunAs cmd /k, ("^""%~f0"^"" -replace '[ &]', '^$&')" & goto :eof)
If your batch-file paths contain $ chars. and ' chars., an aux. environment variable must be used:
$ chars. alone wouldn't be a problem if you used embedded '...' quoting, but if ' are also present, embedded "..." quoting isn't an option, because PowerShell would treat the $ chars. as the start of a variable reference.
See the solution at the top.
If you additionally want to support passing arguments to the batch file:
Note: The solution at the top now incorporates this approach, while also handling a wider array of exotic batch-file paths.
To prevent further quoting headaches, an aux. environment variable, __ARGS is used in the code below to store all arguments that the batch file received (%*), which the PowerShell code can then access via $env:__ARGS on re-invocation.
#echo off
:: Save the arguments received in an aux. environment variable.
:: Note: The space after "=" is required for the PowerShell command
:: to also work if *no* arguments were passed.
set __ARGS= %*
:: Unless already elevated, relaunch this batch file,
:: with arguments passed through.
net session >NUL 2>NUL || (powershell.exe -nop -c "Start-Process -Verb RunAs cmd /k, ("^""%~f0"^"" -replace '[ &]', '^$&'), $env:__ARGS" & goto :eof)
:: Reset the aux. variable.
set __ARGS=
echo Now running with elevation. Arguments received:
echo. %*
[1] A simple example (call from cmd.exe): powershell -c "[Environment]::CommandLine; '%OS%'"

How to start PowerShell script from BAT file with proper Working Directory?

I'm trying to create bat script that can start PowerShell script named the same as bat file in proper working directotry.
This is what I got:
#ECHO OFF
PowerShell.exe -NoProfile -Command "& {Start-Process PowerShell.exe -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File ""%~dpn0.ps1""' -WorkingDirectory '%~dp0' -Verb RunAs}"
PAUSE
Passing working directory this way does not work.
How to make script that will pass proper working directroy and also command line arguments?
The -WorkingDirectory parameter doesn't work when using -Verb RunAs. Instead, you have to set the working directory by calling cd within a -Command string.
This is what I use: (cmd/batch-file command)
powershell -command " Start-Process PowerShell -Verb RunAs \""-Command `\""cd '%cd%'; & 'PathToPS1File';`\""\"" "
If you want to make a "Run script as admin" right-click command in Windows Explorer, create a new registry key at HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell\Run with PowerShell (Admin)\Command, and set its value to the command above -- except replacing %cd% with %W, and PathToPS1File with %1 (if you want it to execute the right-clicked file).
Result: (Windows Explorer context-menu shell command)
powershell -command " Start-Process PowerShell -Verb RunAs \""-Command `\""cd '%W'; & '%1';`\""\"" "
EDIT: There's an alternative way to have the script be run as admin from Explorer, by using the "runas" sub-key: https://winaero.com/blog/run-as-administrator-context-menu-for-power-shell-ps1-files
If you want to run your script as admin from an existing powershell, remove the outer powershell call, replace %W with $pwd, replace %1 with the ps1 file-path, and replace each \"" with just ".
Note: The \""'s are just escaped quotes, for when calling from the Windows shell/command-line (it's quote-handling is terrible). In this particular case, just \" should also work, but I use the more robust \"" for easier extension.
See here for more info: https://stackoverflow.com/a/31413730/2441655
Result: (PowerShell command)
Start-Process PowerShell -Verb RunAs "-Command `"cd '$pwd'; & 'PathToPS1File';`""
Important note: The commands above are assuming that your computer has already been configured to allow script execution. If that's not the case, you may need to add -ExecutionPolicy Bypass to your powershell flags. (you may also want -NoProfile to avoid running profile scripts)
A workaround is to let the PowerShell script change the directory to it's own origin with:
Set-Location (Split-Path $MyInvocation.MyCommand.Path)
as the first command.
As per mklement0s hint: In PSv3+ use the simpler:
Set-Location -LiteralPath $PSScriptRoot
Or use this directory to open adjacent files.
$MyDir = Split-Path $MyInvocation.MyCommand.Path
$Content = Get-Content (Join-Path $MyDir OtherFile.txt)

Why isn't for /f working in this batch script?

I'm trying to capture the output of a command in a batch script with for /f. Here is my code:
set RUNPS="powershell -NoProfile -ExecutionPolicy Bypass -Command"
set OLDPATHPS="[Environment]::GetEnvironmentVariable('PATH', 'User')"
for /f %%i in ('%RUNPS% %OLDPATHPS%') do ^
set OLDPATH=%%i
When I run the script from the console, here is the output:
C:\Users\James\Downloads>testscript
' ' is not recognized as an internal or external command,
operable program or batch file.
' ' is not recognized as an internal or external command,
operable program or batch file.
' ' is not recognized as an internal or external command,
operable program or batch file.
...
On the other hand, when I run the command normally I get
C:\Users\James\Downloads>powershell -NoProfile -ExecutionPolicy Bypass -Command "[Environment]::GetEnvironmentVariable('PATH', 'User')"
C:\Users\James\.dnx\runtimes\dnx-clr-win-x86.1.0.0-beta6\bin;C:\Users\James\.dnx\bin;...
Why does this happen with for /f and what can I do to fix it?
EDIT: Thought it may have been an issue with the single quotes being unescaped. Tried using usebackq:
for /f usebackq %%i in (`%RUNPS% %OLDPATHPS%`) do ^
set OLDPATH=%%i
But it looks like I'm getting the same output.
Try this:
#ECHO OFF
SETLOCAL
set "RUNPS=powershell -NoProfile -ExecutionPolicy Bypass -Command"
set "OLDPATHPS=[Environment]::GetEnvironmentVariable('PATH', 'User')"
for /f "delims=" %%i in ('%RUNPS% "%OLDPATHPS%"') DO (
set OLDPATH=%%i
)
SET o
GOTO :EOF
The syntax SET "var=value" (where value may be empty) is used to ensure that any stray trailing spaces are NOT included in the value assigned. set /a can safely be used "quoteless".
The required syntax for the command is
powershell -NoProfile -ExecutionPolicy Bypass -Command "[Environment]::GetEnvironmentVariable('PATH', 'User')"
not
"powershell -NoProfile -ExecutionPolicy Bypass -Command" "[Environment]::GetEnvironmentVariable('PATH', 'User')"
The ' ' is not recognized ... message appears to be the consequence of attempting to use the caret. It appears to be invalid as you've used it - I've never seen that ateempted before.
Note that you need the "delims=" to ensure that a user-path-containing-spaces is not tokenised.
Why would you escape the linebreak after do? You should use:
set RUNPS="powershell -NoProfile -ExecutionPolicy Bypass -Command"
set OLDPATHPS="[Environment]::GetEnvironmentVariable('PATH', 'User')"
for /f %%i in ('%RUNPS% %OLDPATHPS%') do (
set OLDPATH=%%i
)
Note that if you want to use variables like OLDPATH inside a for loop (after the do) you have to use setlocal EnableDelayedExpansion at the top of your script, and use ! instead of % for variables inside the loop
EDIT: your powershell currently has this exception:
-Command : The term '-Command' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a
path was included, verify that the path is correct and try again.
At line:1 char:1
-Command [Environment]::GetEnvironmentVariable('PATH', 'User')
~~~~~~~~
CategoryInfo : ObjectNotFound: (-Command:String) [], CommandNot
FoundException
FullyQualifiedErrorId : CommandNotFoundException

How to run PowerShell in CMD

I'm trying to run a PowerShell script inside cmd command line. Someone gave me an example and it worked:
powershell.exe -noexit "& 'c:\Data\ScheduledScripts\ShutdownVM.ps1'"
But the problem is my PowerShell script has input parameters, so I tried, but it doesn't work:
powershell.exe -noexit "& 'D:\Work\SQLExecutor.ps1 -gettedServerName "MY-PC" ' "
The error is:
The term 'D:\Work\SQLExecutor.ps1 -gettedServerName "MY-PC" ' is not recognized as the name of a cmdlet, function,
How can I fix this problem?
You need to separate the arguments from the file path:
powershell.exe -noexit "& 'D:\Work\SQLExecutor.ps1 ' -gettedServerName 'MY-PC'"
Another option that may ease the syntax using the File parameter and positional parameters:
powershell.exe -noexit -file "D:\Work\SQLExecutor.ps1" "MY-PC"
I'd like to add the following to Shay Levy's correct answer:
You can make your life easier if you create a little batch script run.cmd to launch your powershell script:
run.cmd
#echo off & setlocal
set batchPath=%~dp0
powershell.exe -noexit -file "%batchPath%SQLExecutor.ps1" "MY-PC"
Put it in the same path as SQLExecutor.ps1 and from now on you can run it by simply double-clicking on run.cmd.
Note:
If you require command line arguments inside the run.cmd batch, simply pass them as %1 ... %9 (or use %* to pass all parameters) to the powershell script, i.e.
powershell.exe -noexit -file "%batchPath%SQLExecutor.ps1" %*
The variable batchPath contains the executing path of the batch file itself (this is what the expression %~dp0 is used for). So you just put the powershell script in the same path as the calling batch file.
Try just:
powershell.exe -noexit D:\Work\SQLExecutor.ps1 -gettedServerName "MY-PC"

Resources