Power Shell: A command as a variable with parameter - powershell

I have a simple question. I would like to assign a command to a variable and execute it with extra parameters, like:
C:\test.exe /q /v
But when I do:
$path=C:\test.exe
$path /q /v
It doesn't work.. Any idea?

The most canonical way to do this is to use the call operator on the variable that contains the "name" of the command e.g.:
& $path /q /v
This is actually required when the path to the command (ie native exe) contains spaces.

Command as string:
With Invoke-Expression cmdlet you execute an arbitrary string as a piece of code. It takes the string, compiles it, and executes it.
So you do:
$path='C:\test.exe';
Invoke-Expression "$path /q /v";
As a side note: When you do $path=C:\test.exe without the quotes, you are actually assigning the STDOUT of test.exe to the variable $path. You have to make clear to PowerShell that it is actually a string you wish to execute later.
Command as script object:
If you are concerned with performance, you could also try converting your command to a compiled scriptblock, and execute it with the & call operator.
$path='C:\test.exe';
$cmd = [scriptblock]::Create("$path /q /v");
& $cmd;

Related

Powershell - Cat out string in different colors

Any advice or assistance would be greatly appreciated.
I am writing a simple base64 encoder for windows so I can encode strings faster when required.
The encoding happens in CMD and then the script opens a PowerShell in order to cat out the encoded string in the terminal.
For easier reading I am trying to change the color of the string that is retrieved with cat but am having a hard time achieving this. Here is a snippet of the code that is problematic:
certutil -encode data.txt tmp.b64 && findstr /v /c:- tmp.b64 > secret.b64
powershell.exe -Command "Write-Host Your secret is: -ForegroundColor Green";cat secret.b64
PAUSE
When the information is retrieved; "Your secret is: " is green as intended in the code but I cannot seem to find a way to color the "cat secret.b64" command.
As far as the original request goes, I suppose you wanted something like:
powershell -command "$i = cat secret.b64; Write-Host Your secret is: $i -ForegroundColor Green"
You can however skip powershell and do batch-file only.
#echo off
for /F %%a in ('echo prompt $E ^| cmd') do set "cl=%%a"
(certutil -encode data.txt tmp.b64 && findstr /v /c:- tmp.b64)>secret.b64
set /p secr=<secret.b64
echo Your secret is: %cl%[92m%secr%%cl%[0m
pause
Do it entirely in PowerShell; PS can call any console executables that CMD can. To take the stdout of a console executable and use it as string input to a PS cmdlet or function, either enclose the entire console executable's command in parens (e.g., $foocontent = (cat.exe foo)), or pipe it to a parameter of the PS cmdlet/function that accepts pipeline input (e.g., cat foo | Format-Table)
(Note that in PowerShell under Windows, cat is generally aliased to the PowerShell cmdlet Get-Content.)

Invoke .cmd file using variable name

The current PowerShell script invokes a .cmd file as follows:
cd $pathToCmdFile
.\Xyz.Web.deploy /y
I would like to change this so that Xyz is used as a variable, something like:
$webAppName = "Xyz"
cd $pathToCmdFile
.\$webAppName.Web.deploy /y
I've already tried
$cmd = ".\$webAppName.Web.deploy /y"
Invoke-Expression $cmd
Shows
The term '.\Xyz.Web.deploy' is not recognized as the name of a cmdlet, function, script file or operable program...
I've also tried Invoke-Command, Invoke-Item but none seem to work. I have also tried putting an ampersand ($cmd = "& .\$webAppName.Web.deploy /y"), but that didn't work either.
The proper way of doing what you want is to use the call operator (&). You must not include the operator or command arguments in the command string, though.
This should work:
$webAppName = "Xyz"
cd $pathToCmdFile
& ".\${webAppName}.Web.deploy.cmd" /y
I strongly recommend to always specify commands/scripts with their extension.
So Invoke-Expressions should work:
test.cmd
set
test.ps1 calls test.cmd
cd .
$webAppName = "Test"
Invoke-Expression ".\\$webAppName.cmd"
So now you run the powershell script
> .\test.ps1
ALLUSERSPROFILE=C:\ProgramData
ANSICON=234x1000 (234x47)
ANSICON_DEF=7
...
Or you could do this so you can pass a parameter to the powershell script
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[string]$webAppName
)
cd .
Invoke-Expression ".\\$webAppName.cmd"

Passing a cmd-line IF statement through Invoke-Expression breaks on output

If I pass an IF statement through PowerShell's Invoke-Expression, the command appears to be running and completing, but then it appears that the output is being evaluated as a new command instead of being returned to PowerShell. Three examples:
Invoke-Expression 'echo "hi"' (No IF statement)
Normal Output: hi
Invoke-Expression 'cmd /c IF exist C:\Windows (echo "hi")'
Error on Output: 'hi' is not recognized as an internal or external command, operable program or batch file.
Invoke-Expression 'cmd /c IF exist C:\Windows (query user)'
Error on Output:
'" USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME"' is not recognized as an internal or external command, operable program or batch file.
What's the best way to run a command-line IF statement from PowerShell and be able to read its output? I tried Start-Process but cannot figure out for the life of me how to read its output. Tried a System.Diagnostics.ProcessStartInfo object copied from another StackOverflow post, but no luck there either.
Because people are bound to ask: The reason why I'm passing this through cmd in the first place is because this entire code block needs to be passed through Invoke-Command to a remote machine and cmd has folder/file access to computers on its network while PowerShell does not.
Your immediate problem is unrelated to the use of Invoke-Expression, which should generally be avoided:
cmd /c IF exist C:\Windows (echo "hi") # WRONG
is interpreted by PowerShell first, up front, and (echo "hi") is the same as (Write-Output "hi"), which PowerShell expands (interpolates) to the command's output, a string with content hi.
The - broken - command line that cmd exe ends up seeing is the following, which explains the error message:
cmd /c IF exist C:\Windows hi
For an overview of how PowerShell parses unquoted command-line arguments, see this answer.
There are several ways to fix that problem, appropriate in different scenarios:
# Single-quoting - passed as-is.
cmd /c 'IF exist C:\Windows (echo "hi")'
# Double-quoting - PowerShell would still expand $-prefixed tokens up front.
cmd /c "IF exist C:\Windows (echo `"hi`")"
#`# The stop-parsing symbol, --%, prevents PowerShell from parsing subsequent arguments,
# with the exception of cmd-style environment-variable references (%FOO%)
cmd /c --% IF exist C:\Windows (echo "hi")
Now, with Invoke-Expression you'd have to add escape those quotes due to having to specify them as part of a string, but, as mentioned in the comments, there is rarely a need for Invoke-Expression, and it is neither needed here, nor would I expect it to help with the "double hop" authentication problem you describe in a comment.
To address the latter, try this answer, which uses explicitly passed credentials to establish an auxiliary drive mapping on the remote machine.

How to simulate a batch script

I need to setup a PowerShell script based upon a batch script. The original batch script looks like the following:
call %SYSTEMROOT%\setup_Env.BAT
command_name command_arguments
The command is dependent upon the environmental variables from the setup_ENV.BAT being setup.
$tempFile = [IO.Path]::GetTempFileName()
$script = "%SYSTEMROOT%\setup_Env.BAT"
cmd /c " $script && set > $tempFile "
cmd /c " command_name command_arguments"
I received the error:
cmd : 'command_name is not recognized as an internal or external command,...
If there is a better way to do this in PowerShell, I am open to it.
You need to pass a single command line to cmd to make this work:
cmd /c "call %SYSTEMROOT%\setup_Env.BAT && command_name command_arguments"
As Ansgar Wiechers points out, every cmd invocation runs in a child process, and any environment modifications made in a child process are not visible to the calling process and therefore also not to future child processes.
By contrast, in the single command line above, the environment-variable modifications performed by setup_Env.BAT are visible to command_name by the time it executes.
Caveat: If command_arguments contains %...%-style references to the environment variables defined in setup_Env.BAT, more work is needed:
Change the %...%-style references to !...!-style references.
Additionally invoke cmd with /v to enable delayed variable expansion (the equivalent of setlocal enabledelayedexpansion inside a script`:
cmd /v /c "call %SYSTEMROOT%\setup_Env.BAT && command_name args_with_delayed_var_refs"
Caveat: The above may still not work as intended if command_arguments happens to contain ! chars. that should be treated as literals (and/or command_name is another batch file containing such).
In that event, the simplest approach is to simply recreate the entire batch file in a temporary file and invoke that:
# Get temp. file path
$tempBatFile = [IO.Path]::GetTempFileName() + '.bat'
# Write the content of the temp. batch file
#'
#echo off
call %SYSTEMROOT%\setup_Env.BAT
command_name command_arguments
'# | Set-Content $tempBatFile
# Execute it.
& $tempBatFile
# Clean up.
Remove-Item -LiteralPath $tempBatFile

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%