Running .cmd/.bat script in Powershell - powershell

I'm trying to write and execute a .cmd script in powershell. The code I have for this is:
$script = #'
#echo off
SETLOCAL
CALL something here
'#
Invoke-Expression -Command: $script
This is based off this link which explains the here string in powershell. It's at the bottom of the link. Here's the related msdn.
Here's another related link to someone trying to do the same thing.
I keep getting an error that has to do with including the '#' operator within the string:
Invoke-Expression : At line:1 char:7
+ #echo off
+ ~~~
Unexpected token 'off' in expression or statement.
At line:1 char:1
+ #echo off
+ ~~~~~
The splatting operator '#' cannot be used to reference variables in an expression. '#echo' can be used only as an argument to a command. To reference variables in an expression use '$echo'.
I've tried escaping the '#' symbol, and a plethora of other things. I'd like to know why it seemed to work for them in the third link, but throws this error in my case.
Edit:
Writing to a .bat file then running the bat file resulted in the same error:
$batchFileContent = #'
#echo off
c:\windows\system32\ntbackup.exe backup "C:\Documents and Settings\Administrator\Local Settings\Application Data\Microsoft\Windows NT\NTBackup\data\chameme.bks" /n "1file.bkf1 created 06/09/2013 at 09:36" /d "Set created 06/09/2013 at 09:36" /v:no /r:no /rs:no /hc:off /m normal /j chameme /l:s /f "\\fs1\Exchange Backups$\1file.bkf"
'#
$batchFileContent | Out-File -LiteralPath:"$env:TEMP\backup.cmd" -Force
Invoke-Expression -Command:"$env:TEMP\backup.cmd"
Remove-Item -LiteralPath:"$env:TEMP\backup.cmd" -Force
As Bill Stewart pointed out, I should write the content of the .cmd script in powershell.
Edit:
This
$script = #'
cmd.exe /C "#echo off"
cmd.exe /C "SETLOCAL"
cmd.exe /C "CALL something here"
'#
Invoke-Expression -Command: $script
Seems to work.

This happens because Invoke-Expression interprets your string with PowerShell. PowerShell allows you to run shell commands, but it interprets things as PowerShell first. The # character is the splatting operator in PowerShell.
You should save the commands in a batch file, and then execute that.
Or you can execute single line commands by shelling out to cmd.exe:
Invoke-Expression "cmd.exe /c #echo something"

Related

Powershell - How to pass arguments with apostrophes in them?

I'm using a third party tool to do some AD manipulation. I run a powershell script and pass arguments to it.
Everything works except if that argument contains an apostrophe in it like Jerry O'Connor. I've tried lots of different escape combinations without any luck.
Here is my command: script.ps1 -name "'%name%'" and name contain is Jerry O'Connor.
The error is
Result:The string is missing the terminator: '.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
I've tried:
script.ps1 -name "'%name%'"
script.ps1 -name \"%name%\"
script.ps1 -name ''name''
all with same error.
If you run this at the PS CMD level you'll see the error
powershell echo -name "'Jerry O'Connor'"
Anyone know how to pass an argument to script.ps1 -name "name" where that argument contains an apostrophe?
Cheers
You need to escape any ' chars. inside the value of %name% as '' to make them work inside a single-quoted PowerShell string, which from cmd.exe you can do with %name:'=''%:
From cmd.exe / a batch file:
powershell.exe -Command ./script.ps1 -name "'%name:'=''%'"
If %name% contains verbatim Jerry O'Connor, the above expands to the following, which should work as intended:
powershell.exe -Command ./script.ps1 -name "'Jerry O''Connor'"
However, you can simplify quoting if you use the -File CLI parameter instead of -Command:
powershell.exe -File ./script.ps1 -name "%name%"
See also:
For guidance on when to use -Command vs. -File, see this answer
For a comprehensive overview of the PowerShell CLI, see this answer
Try the escape character as shown here:
$Test = "`'test`'"
$Test

Running Powershell from CMD Causing Command Line to Not Parse Correctly

I am trying to run the following PowerShell code via a CMD shell:
$logfile = "x:\test.log"
try {
get-service
Add-Content - Path $logfile -Value "It Worked"
} catch {
Add-Content -Path $logfile -Value $_.Exception.Message
}
From a CMD script I am calling the script as follows:
Powershell.exe -executionpolicy bypass -command "I paste the code above
here"
I have also tried it as follows:
Powershell.exe -executionpolicy bypass -command "& 'Command From Above'"
You can see from the error, it doesn't seem to be trying to run the entire command, because it seems to be trying to run my log command:
!https://imgur.com/y62FzB2
If I run something simple, it works without issue. As follows:
Powershell.exe -executionpolicy bypass -command "get-service"
As for what you tried:
When calling from cmd.exe, PowerShell code you pass to Windows PowerShell's CLI, powershell.exe (pwsh.exe for PowerShell Core) must be single-line:
Your screenshot suggests that you indeed used a single line, but you're missing ; chars. between your statements; these statement separators are needed whenever you want to place multiple statements on a single line.
Your next problem is related to passing the code as individual arguments to -command, to be pieced together by PowerShell before execution (as opposed to trying to pass a single argument with outer double-quoting).
In passing arguments individually, any enclosing "..." are stripped on parsing by PowerShell, so that "x:\test.log" turns into x:\test.log - no quotes - which caused the error you saw.
A solution:
By:
properly separating your statements with ;
escaping the " chars. in your command as \" (sic)
you can get your command to work:
powershell -c $logfile = \"x:\test.log\"; try { get-service; Add-Content -Path $logfile -Value \"It Worked\" } catch { Add-Content -Path $logfile -Value $_.Exception.Message }
Generally, however, note that if your code happens to contain characters (also) reserved in cmd.exe - notably & | < > % ^ - you would have to individually ^-escape them.
It's challenging to get quoting right on the command line, and ultimately the only robust solution - short of avoiding the problem by creating a (temporary) script file - is to use PowerShell's -EncodedCommand CLI parameter, which takes a Base64-encoded representation of the PowerShell code using the UTF-16LE character encoding.
Unfortunately, such a representation is not easy to come by in batch files.
I have always had trouble calling multi-line PowerShell scripts through cmd.
I have seen people convert their script into base64 and run that string from PowerShell but I can't remember how.
The easiest way would be to save your script as a .PS1 and run PowerShell -NoProfile -ExecutionPolicy Bypass -file "C:\ps.ps1"
Otherwise you could ECHO each line out to a .ps1 file and then run it. Would work as a .bat file.
#echo off
set WD=%~dp0
ECHO $logfile = "x:\test.log" >> "%WD%Script.ps1"
ECHO try { >> "%WD%Script.ps1"
ECHO get-service; >> "%WD%Script.ps1"
ECHO Add-Content - Path $logfile -Value "It Worked" >> "%WD%Script.ps1"
ECHO } catch { >> "%WD%Script.ps1"
ECHO Add-Content -Path $logfile -Value $_.Exception.Message >> "%WD%Script.ps1"
ECHO } >> "%WD%Script.ps1"
powershell.exe -ExecutionPolicy Bypass -File "%WD%Script.ps1"
del "%WD%Script.ps1"

Start-Process and the Stop Parsing (--%) parameter

I am having trouble getting the --% parameter to work as expected. My $TaskParams variable has the character '<' which is interpreted as a redirection by powershell, therefore needs to be escaped.
However the following does not work:
$CreateTask = Start-Process PowerShell.exe "$ScriptLocation --% $TaskParams" -Wait -PassThru
Without the --%, and when I manually remove any '<' characters, it works:
$CreateTask = Start-Process PowerShell.exe "$ScriptLocation $TaskParams" -Wait -PassThru
error received:
Start-Process : A positional parameter cannot be found that accepts argument
'--%'.
note: I am using PS 5.0
Am I using the --% parameter wrong? Any help or pointers is appreciated. Thanks
The stop-parsing symbol --% only works when calling executables directly or with the call operator &; it's not for use when calling PowerShell scripts / functions / cmdlets.
You do not need to spin up a new copy of powershell.exe or use Start-Process to run a script from within another script. Just put the script command and its parameters as a line from within the other script. For example, suppose you have script2.ps1:
param(
[String] $Name
)
Write-Host "Hello, $Name"
Now suppose you also have script1.ps1:
Write-Host "This is script1.ps1"
.\Script2.ps1 -Name "Bill Stewart"
Write-Host "script1.ps1 is finished"
If you now run script1.ps1:
PS C:\> .\Script1.ps1
This is script1.ps1
Hello, Bill Stewart
script1.ps1 is finished
If you really want to use Start-Process you could encode the argument, and run it as such. I use something similar to this when elevating past UAC:
$Code = ". '$ScriptLocation' $TaskParams"
$Encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($code))
Start-Process PowerShell.exe -ArgumentList "-EncodedCommand",$Encoded -Wait -PassThru
I'm fairly certain that would accomplish what you're looking for.

How to pass path with spaces to script

I am trying to use PowerShell to call an EXE that is at a location/path containing spaces. When I call the script from the command line, the EXE's full path is not being passed to the script. Any ideas as to why this is happening?
PowerShell Script Contents (Untitled1.ps1)
Here is the entire script that gets called from the command line:
param(
[string] $ParamExePath
)
function Run-CallThisExe {
param(
[string] $ThisExePath
)
Write-Host "ThisExePath: " "$ThisExePath"
start-process -FilePath $ThisExePath
}
write-host "ParamExePath: " $ParamExePath
Run-CallThisExe -ThisExePath "$ParamExePath"
Command Line String
Here is the command line string being run from the PowerShell script's parent folder:
powershell -command .\Untitled1.ps1 -NonInteractive -ParamExePath "C:\path with spaces\myapp.exe"
Output
Here is what is output after running the script
ParamExePath: C:\path
ThisExePath: C:\path
start-process : This command cannot be run due to the error: The system cannot
find the file specified.
At C:\sample\Untitled1.ps1:11 char:5
+ start-process -FilePath $ThisExePath
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Start-Process], InvalidOperationException
+ FullyQualifiedErrorId : InvalidOperationException,Microsoft.PowerShell.Commands.StartProcessCommand
Just change this:
powershell -command .\Untitled1.ps1 -NonInteractive -ParamExePath "C:\path with spaces\myapp.exe"
To This:
powershell -file .\Untitled1.ps1 -NonInteractive -ParamExePath "C:\path with spaces\myapp.exe"
The -Command Parameter used to execute commands for example {Get-Date}
The -File Parameter used to Run .ps1 Script File
-Command
Executes the specified commands (and any parameters) as though they were
typed at the Windows PowerShell command prompt, and then exits, unless
NoExit is specified. The value of Command can be "-", a string. or a
script block.
-File
Runs the specified script in the local scope ("dot-sourced"), so that the
functions and variables that the script creates are available in the
current session. Enter the script file path and any parameters.
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.
Type Powershell /? to get full details on each Parameter
A couple of other ways to call it:
powershell -command .\Untitled1.ps1 -NonInteractive "-ParamExePath 'C:\path with spaces\myapp.exe'"
powershell -command ".\Untitled1.ps1 -ParamExePath 'C:\path with spaces\myapp.exe'" -NonInteractive
Note that if you pass a folder path to Powershell that has a trailing backslash it cannot handle it. e.g. -ParamFolder "C:\project folder\app\bin debug\". The parameter string ends up with a double quote at the end. So when you try to append the name of a file to it you end up with something like C:\project folder\app\bin debug"Filename.txt. In this case you have to send in a second backslash at the end.

Powershell opening file path with whitespaces

I'm my PS script I want to be able to run another script in another PS instance by doing the following:
$filepath = Resolve-Path "destruct.ps1"
start-process powershell.exe "$filepath"
destruct.ps1 is in the same folder as this script.
However when running this script in a location which includes spaces ("C:\My Scripts\") I will get the following error:
The term 'C:\My' is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again.
I know by using a '&' with the Invoke-Expression method solves this problem, how can I do the same but by using the start-process method?
try this:
start-process -FilePath powershell.exe -ArgumentList "-file `"$filepath`""
edit after comments:
start-process -FilePath powershell.exe -ArgumentList "-file `"$($filepath.path)`""
side note:
$filepath is a [pathinfo] type and not a [string] type.
You can add escaped double quotes so that you pass a quoted argument:
"`"$filepath`""
I am answering here for a general scenario.
If you need to navigate to a folder for example C:\Program Files from the Powerhsell, the following command won't work as it has white space in between the path.
cd C:\Program Files
Instead embed the path with double quotes as like the below.
cd "C:\Program Files"
File name might contain spaces, so preserve spaces in full path:
Notepad++ exec command:
"C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe" "& \"$(FULL_CURRENT_PATH)\""
same from command prompt:
"C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe" "& \"C:\a_work\Systems\name with spaces.ps1\""
Just in case [string]$shipno (which is path & file name) comes in including spaces the following allows it to be passed to -FilePath successfully:
if ($shipno.contains(" ") -eq $true) {
$shipno = """" + $shipno + """"
}