Passing arguments from .BAT to PowerShell script

W

wofat68

Hi,

I'd like to have a PS script, where I can drag some files in explorer
and drop them onto the script (for example, the dropped files are
moved to a specific directory or something similar). This doesn't work
with PS scripts directly. Therefore I wrote a .BAT file which is
supposed to call the PS script. However, this doesn't work if there
are spaces within the file names.

Example:

File names are:

file with spaces 1.txt
file with spaces 2.txt

script.ps1:

foreach($arg in $args) { write-host "[$arg]" }

..BAT file:

powershell .\script.ps1 %*

If I drag and drop the files onto the batch file I get the following
output:

F:\PowerShell>powershell .\test.ps1 "F:\PowerShell\file with spaces
1.txt" "F:\PowerShell\file with spaces 2.txt"

[F:\PowerShell\file]
[with]
[spaces]
[1.txt]
[F:\PowerShell\file]
[with]
[spaces]
[2.txt]


That is clearly not what I want. And in my opinion it's a bug in
PowerShell.

If I replace %* with "%*" I get the same results.

If I replace %* with '%*' I get the following results:

F:\PowerShell>powershell .\test.ps1 '"F:\PowerShell\file with spaces
1.txt" "F:\PowerShell\file with spaces 2.txt"'

[F:\PowerShell\file with spaces 1.txt F:\PowerShell\file with spaces
2.txt]

I'm running out of ideas here. Does anybody have any suggestion how to
solve this problem?

Thanks a lot for any help.
 

My Computer

S

Shay Levy [MVP]

Hi wofat68,

You can't drag-drop files onto a PowerShell script. For security reasons,
PS script files are not associated with PowerShell.exe (when double-clicked
they open in notepad).

You can put the files in a temp directory (e.g c:\temp) and pass the directory
path to a script as an argument:

powershell.exe c:\foo.ps1


## c:\foo.ps1 ##

dir c:\temp\*.ps1 | foreach { ...do what ever you need here...}

############

---
Shay Levy
Windows PowerShell MVP
http://blogs.microsoft.co.il/blogs/ScriptFanatic



w> Hi,
w>
w> I'd like to have a PS script, where I can drag some files in explorer
w> and drop them onto the script (for example, the dropped files are
w> moved to a specific directory or something similar). This doesn't
w> work with PS scripts directly. Therefore I wrote a .BAT file which is
w> supposed to call the PS script. However, this doesn't work if there
w> are spaces within the file names.
w>
w> Example:
w>
w> File names are:
w>
w> file with spaces 1.txt
w> file with spaces 2.txt
w> script.ps1:
w>
w> foreach($arg in $args) { write-host "[$arg]" }
w>
w> .BAT file:
w>
w> powershell .\script.ps1 %*
w>
w> If I drag and drop the files onto the batch file I get the following
w> output:
w>
w> F:\PowerShell>powershell .\test.ps1 "F:\PowerShell\file with spaces
w> 1.txt" "F:\PowerShell\file with spaces 2.txt"
w>
w> [F:\PowerShell\file]
w> [with]
w> [spaces]
w> [1.txt]
w> [F:\PowerShell\file]
w> [with]
w> [spaces]
w> [2.txt]
w> That is clearly not what I want. And in my opinion it's a bug in
w> PowerShell.
w>
w> If I replace %* with "%*" I get the same results.
w>
w> If I replace %* with '%*' I get the following results:
w>
w> F:\PowerShell>powershell .\test.ps1 '"F:\PowerShell\file with spaces
w> 1.txt" "F:\PowerShell\file with spaces 2.txt"'
w>
w> [F:\PowerShell\file with spaces 1.txt F:\PowerShell\file with spaces
w> 2.txt]
w>
w> I'm running out of ideas here. Does anybody have any suggestion how
w> to solve this problem?
w>
w> Thanks a lot for any help.
w>
 

My Computer

A

Alex K. Angelopoulos

Wofat,

Here's one workaround with PowerShell 1.0 that works. The trick is to
generate a complete command line and feed it as input to PowerShell, using
the - argument, using a batch file like this:

@echo off
:: pswrapper.cmd
echo.\script.ps1 %* | powershell -NoExit -Command -



"wofat68" <[email protected]> wrote in message
news:[email protected]

> Hi,
>
> I'd like to have a PS script, where I can drag some files in explorer
> and drop them onto the script (for example, the dropped files are
> moved to a specific directory or something similar). This doesn't work
> with PS scripts directly. Therefore I wrote a .BAT file which is
> supposed to call the PS script. However, this doesn't work if there
> are spaces within the file names.
>
> Example:
>
> File names are:
>
> file with spaces 1.txt
> file with spaces 2.txt
>
> script.ps1:
>
> foreach($arg in $args) { write-host "[$arg]" }
>
> .BAT file:
>
> powershell .\script.ps1 %*
>
> If I drag and drop the files onto the batch file I get the following
> output:
>
> F:\PowerShell>powershell .\test.ps1 "F:\PowerShell\file with spaces
> 1.txt" "F:\PowerShell\file with spaces 2.txt"
>
> [F:\PowerShell\file]
> [with]
> [spaces]
> [1.txt]
> [F:\PowerShell\file]
> [with]
> [spaces]
> [2.txt]
>
>
> That is clearly not what I want. And in my opinion it's a bug in
> PowerShell.
>
> If I replace %* with "%*" I get the same results.
>
> If I replace %* with '%*' I get the following results:
>
> F:\PowerShell>powershell .\test.ps1 '"F:\PowerShell\file with spaces
> 1.txt" "F:\PowerShell\file with spaces 2.txt"'
>
> [F:\PowerShell\file with spaces 1.txt F:\PowerShell\file with spaces
> 2.txt]
>
> I'm running out of ideas here. Does anybody have any suggestion how to
> solve this problem?
>
> Thanks a lot for any help.
>
>
>
 

My Computer

W

wofat68

Thanks for the response.

I guess I provided a misleading example. Just for clarification:

1. PS's security restrictions are fine with me. That's the reason I
used the intermediate batch file.

2. I still claim that the way PS parses the command line is wrong
here. As you can see in the examples, CMD.EXE encapsulates the
arguments correctly within double quotes. PS seems to ignore the
quotes.

3. I was looking for a general purpose solution, not just moving
files. My example was bad. In general I want to select some files in
explorer, move (drag) the selected files and drop them onto a script/
batch. Then the script should do with the files whatever I want.
Examples: renaming the files, changing time stamps, calculating check
sums, and so on.

Anyway, your suggestion lead me to another idea: writing the arguments
into a temporary file and then reading the file line by line. Quotes
have to be removed explicitely.

..BAT file:

del /q /f %TEMP%\ps.args 2>NUL
for %%f in (%*) do echo %%f>>%TEMP%\ps.args
powershell .\script.ps1 %TEMP%\ps.args

script.ps1:

foreach($arg in get-content $args[0]) { write-host "[$
($arg.SubString(1, $arg.Length - 2))]" }


Thanks for the help.

--wofat68
 

My Computer

W

wofat68

This is pretty nice. Much better than the solution I finally came up
with.

Thanks a lot.

--wofat68
 

My Computer

A

Alex K. Angelopoulos

Comments on a couple of your points here...

"wofat68" <[email protected]> wrote in message
news:[email protected]

> Thanks for the response.

> 2. I still claim that the way PS parses the command line is wrong
> here. As you can see in the examples, CMD.EXE encapsulates the
> arguments correctly within double quotes. PS seems to ignore the
> quotes.
Although I agree the commandline parsing is poor, this is not PowerShell. It
appears that at a lower level the standard argument parsing done by Windows
APIs causes the double-quote stripping. The same thing happens with WSH;
there is simply no way to pass a literal " to a script within an argument.

I'm not sure about the precise details of how things are implemented in
PowerShell 2.0, but they do have a -File parameter for PowerShell in V2 that
correctly handles a script file and arguments with embedded spaces. I
believe what it does is to simply pass everything straight through to the
script as an argument array, which modifies how data is interpreted but
makes it more straightforward for wrapper scripts.

> 3. I was looking for a general purpose solution, not just moving
> files. My example was bad. In general I want to select some files in
> explorer, move (drag) the selected files and drop them onto a script/
> batch. Then the script should do with the files whatever I want.
> Examples: renaming the files, changing time stamps, calculating check
> sums, and so on.
Using the input stream as I suggested should do the trick. There are a
couple of other techniques, including using an intermediary WSH script that
reassembles arguments inserting SINGLE quotes where you need quoted
arguments, but I think the input stream command technique is easiest for
drag-and-drop. After all, you aren't going to be using the input stream
anyway in that context.

> Anyway, your suggestion lead me to another idea: writing the arguments
> into a temporary file and then reading the file line by line. Quotes
> have to be removed explicitely.
And that's yet another way :)
 

My Computer

W

wofat68

Alex,

Thanks for your detailed response. I read your first post after my
response to Shay. So it got a little bit confusing. Sorry for that.

Anyway. Your suggestion is better than mine. It doesn't involve any
temporary files.

Regarding the command line parsing: this would probably require more
research. But I won't go into that. Your solution works perfectly for
me, even if it's "only" a workaround.

--wofat68
 

My Computer

W

wofat68

On Aug 1, 4:11 pm, "Alex K. Angelopoulos" <aka(at)mvps.org> wrote:


> @echo off
> :: pswrapper.cmd
> echo.\script.ps1 %* | powershell -NoExit -Command -
After some testing, I found that this does not work on some strings,
unfortunately. If the argument does *not* contain any spaces, it isn't
quoted by CMD.EXE. This results in a problem, if the file name
contains parenthesis. Obviously, PS is trying to interpret that as an
expression.

This may be also a security problem, since code could be injected.

I just wanted to let you know.

Probably I have to use my solution using a temporary file.

--wofat68
 

My Computer

A

Alex K. Angelopoulos

Comments inline, and another solution with some "special" guard rails for
drag-and-drop specifically.

"wofat68" <[email protected]> wrote in message
news:[email protected]

> On Aug 1, 4:11 pm, "Alex K. Angelopoulos" <aka(at)mvps.org> wrote:
>
>

>> @echo off
>> :: pswrapper.cmd
>> echo.\script.ps1 %* | powershell -NoExit -Command -
>
> After some testing, I found that this does not work on some strings,
> unfortunately. If the argument does *not* contain any spaces, it isn't
> quoted by CMD.EXE. This results in a problem, if the file name
> contains parenthesis. Obviously, PS is trying to interpret that as an
> expression.
Yes, that wasn't universal. In any case, you can still encounter problems
even with doublequotes, since several characters that are allowed in file
and folder names can cause serious problems to PowerShell - (){}[]$`' are
all legal.

> This may be also a security problem, since code could be injected.
>
> I just wanted to let you know.
>
The other solution I have goes back to the through-argument passing, but I'm
not sure I see this. If anything was being piped in, it could theoretically
expose something, but since the batch wrapper itself invokes the
interactively-specified data explicitly as arguments, you should get no more
and no less vulnerability than you have to injection via the script
arguments.

This did suggest another point to me, however. Since this is explicitly
designed to be a drag-and-drop wrapper, items should _always_ be resolvable
as filesystem paths. Therefore, it makes sense for a wrapper to actually
_test_ for this condition first, and discard any input that isn't a valid
path. I've added that to a new solution.

> Probably I have to use my solution using a temporary file.
Possibly, but from what I can tell I think there will still be some issues.
The ideal technique is to pass the items in as single-quoted rather than
double-quoted strings. I've tried out another wrapper script, which is shown
below. This is actually a WSH script, and theoretically could be made more
generic, but for now it acts as a wrapper akin to the batch file concept.
Details:

(1) The script is totally generic, as long as you observe name and location
constraints. You may want to modify the -NoExit and possibly force the
PowerShell subscript to run hidden, but those are one-off modifications to
this script.

(2) As written, the WSH wrapper needs to be in the same location as the PS
script, with the same base name - e.g., if the PowerShell script is at
c:\apps\scripts\newdemo.ps1, then the WSH script needs to be
c:\apps\scripts\newdemo.vbs. You can of course put a shortcut to the WSH
script anywhere.

(3) The script is _explicitly_ designed for drag-and-drop. It tests every
single argument it was passed to see if it is an existing file or folder
path. If not, the item is dropped silently. Your comment about potential
code injection suggested this addition to me. Since we shouldn't get
anything _but_ file/folder paths as arguments, why not explicitly match the
argument data?

(4) The script works by explicitly single-quoting every argument, whether it
has embedded spaces or not. If an argument contains an embedded single-quote
(it's a legal filename character) then it is escaped by doubling. This
prevents use of any special tricks with filesystem names, but since it's
drag-and-drop, it won't matter.

(5) The only escaping done to the script name itself is escaping spaces in
the script name with a preceding backtick. This allows you to run PowerShell
scripts in situ from add-on installation folders that might be under Program
Files, or from per-user application folders.



' BEGIN WSH wrapper script
' Should have same basename as PS script it will run,
' and must be in same folder as PS script.
' ex: if c:\tmp\fred.ps1, this must be c:\tmp\fred.vbs

dim fso: Set fso = CreateObject("Scripting.FileSystemObject")
Dim WshScript: WshScript = WScript.ScriptFullName
Dim PsScript
PsScript = fso.BuildPath( _
fso.GetFile(WshScript).ParentFolder.Path, _
fso.GetBaseName(WshScript) & ".ps1")

' PowerShell scripters should probably know better than
' using a script name containing embedded spaces, but this
' may be unavoidable for some scripts.
' Just in case, we escape spaces with a backtick.
PsScript = Replace(PsScript, " ", "` ")


Dim i, arg
i = 0
Dim ArgSet: Set ArgSet = CreateObject("Scripting.Dictionary")
Argset(i) = PsScript

For each arg in WScript.Arguments
' EXPLICITLY ensure these resolve to file/folder paths
if fso.FileExists(arg) or fso.FolderExists(arg) then
i = i + 1
' Include escapes for single-quotes used in file/folder names
Argset(i) = "'" & Replace(arg, "'", "''") & "'"
End If
Next

' Remove -NoExit if you don't want PS to stay open when done.
Dim Command
Command = "powershell -NoLogo -NoExit -Command ""& {" _
& Join(ArgSet.Items) _
& "}"""

' WScript.Echo "command as passed to PowerShell:", Command
Dim WshShell: Set WshShell = CreateObject("WScript.Shell")

WshShell.Run Command
' Alternative operation (runs script hidden - ONLY use if
' you also remove -NoExit:
' WshShell.Run Command, 0

' END WSH Wrapper Script
 

My Computer

Top