Windows批处理脚本中的文件/文件夹选择对话框

49
在运行时不知道输入文件位置的情况下,使用GUI进行文件选择输入可以减少用户错误的可能性。
有没有办法从Windows批处理脚本中调用文件/文件夹选择对话框?
10个回答

68

文件浏览器

更新于2016年3月20日:

由于PowerShell现在是所有现代Windows安装的本机组件,我宣布C#回退不再必要。如果您仍然需要它以实现与Vista或XP的兼容性,我已将其移至新答案。从这个版本开始,我将脚本重写为Batch + PowerShell混合,并加入了多选功能。这样做更易于阅读和根据需要进行调整。

<# : chooser.bat
:: launches a File... Open sort of file chooser and outputs choice(s) to the console

@echo off
setlocal

for /f "delims=" %%I in ('powershell -noprofile "iex (${%~f0} | out-string)"') do (
    echo You chose %%~I
)
goto :EOF

: end Batch portion / begin PowerShell hybrid chimera #>

Add-Type -AssemblyName System.Windows.Forms
$f = new-object Windows.Forms.OpenFileDialog
$f.InitialDirectory = pwd
$f.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*"
$f.ShowHelp = $true
$f.Multiselect = $true
[void]$f.ShowDialog()
if ($f.Multiselect) { $f.FileNames } else { $f.FileName }

这将导致一个文件选择对话框。

File chooser dialog

选择的结果将You chose C:\Users\me\Desktop\tmp.txt输出到控制台。如果您想强制只选择一个文件,只需将$f.Multiselect属性更改为$false
PowerShell从Just Tinkering Blog中获取命令。请参阅OpenFileDialog Class文档以了解其他可设置的属性,例如TitleInitialDirectory

文件夹浏览器

更新于2015.08.10:

由于已经有一种COM方法可以调用文件夹选择器,因此很容易构建一个PowerShell单行命令,可以打开文件夹选择器并输出路径。

:: fchooser.bat
:: launches a folder chooser and outputs choice to the console
:: https://dev59.com/X2Uo5IYBdhLWcg3woAuu#15885133

@echo off
setlocal

set "psCommand="(new-object -COM 'Shell.Application')^
.BrowseForFolder(0,'Please choose a folder.',0,0).self.path""

for /f "usebackq delims=" %%I in (`powershell %psCommand%`) do set "folder=%%I"

setlocal enabledelayedexpansion
echo You chose !folder!
endlocal

BrowseForFolder()方法中,第四个参数指定了层次结构的根。请参阅ShellSpecialFolderConstants以获取有效值列表。
这将导致一个文件夹选择对话框。

enter image description here

选择的结果输出 您选择了 C:\Users\me\Desktop 到控制台。

请查看FolderBrowserDialog 类的文档以获取其他可设置的属性,例如 RootFolder。如果需要,可以在此答案的第4个修订版本中找到我原来的 .NET System.Windows.Forms PowerShell 和 C# 解决方案,但这种 COM 方法更易于阅读和维护。


1
非常感谢您提供的代码!它运行得非常好!不过我有一个问题。是否有一种方法可以使用此方法选择多个文件?我已经添加了属性f.Multiselect=true,并能够通过GUI选择多个文件,但似乎它只打印第一个选定的文件,因此我无法找到一种从对话框中读取所有选定文件的方法。您认为这是可能的吗?或者也许有另一种方法可以在win GUI中选择多个文件? - vir us
我现在感觉很愚蠢 :) 我应该对代码更加注意一些。所以在 Console.Write 行之前添加 foreach(String fileName in f.FileNames^) 来遍历 f.FileNames 数组以打印所有必要的值。感谢您提供的代码!顺便说一句:打印 FileNames 数组值也适用于单选模式对话框,并且可以作为处理对话框的更通用方式。再次感谢! - vir us
非常老的帖子,但我希望你能向我展示如何选择多个文件。我希望能够实现同样的功能,谢谢。 - user1719826
非常感谢,能在 Windows 7 - 10 上完美运行。该脚本不适用于 Windows XP 和 Vista,我知道这是因为它没有包括 PowerShell。我想知道是否可以修改 C# 回退以完成新的 PowerShell 编辑。我尝试编辑它,它可以正常实现多选,但我卡在了 for 循环上,它只回显一个文件。 - user1719826
@xNightmare67x 不要使用 for /f... do set,而是使用 for /f... do echo。由于 Vista 的主流支持在2012年被放弃,(此时)只有约1⅔%的网络浏览机器是Vista,我不太想将C#故障转移代码恢复到这个答案中。但是,我已经为您添加了一个新答案 - rojo
显示剩余10条评论

15

这应该可以从XP及以上版本运行,而且不需要混合文件,只需使用一条长命令行运行 mshta:

@echo off
set dialog="about:<input type=file id=FILE><script>FILE.click();new ActiveXObject
set dialog=%dialog%('Scripting.FileSystemObject').GetStandardStream(1).WriteLine(FILE.value);
set dialog=%dialog%close();resizeTo(0,0);</script>"

for /f "tokens=* delims=" %%p in ('mshta.exe %dialog%') do set "file=%%p"
echo selected  file is : "%file%"
pause

请注意,它打开了一个常规的资源管理器文件选择对话框。该对话框的标题无法使用此方法更改。 - Antoni Gual Via
这个能否支持多选? - Dorian Grv
用 PowerShell 找到了它:https://stackoverflow.com/questions/52240766/how-to-select-multiple-files-using-the-batch-selector - Dorian Grv
文件夹选择器是什么? - 9pfs

11

Windows脚本宿主


文件选择

Windows XP拥有一个神秘的UserAccounts.CommonDialog WSH对象,它允许VBScript和JScript启动文件选择提示框。显然,这被认为是安全风险,因此在Vista中被删除了。


文件夹选择

然而,WSH Shell.Application 对象的 BrowseForFolder 方法仍然允许创建文件夹选择对话框。下面是一个混合批处理+JScript示例。将其保存为 .bat 扩展名。

@if (@a==@b) @end /*

:: fchooser2.bat
:: batch portion

@echo off
setlocal

for /f "delims=" %%I in ('cscript /nologo /e:jscript "%~f0"') do (
    echo You chose %%I
)

goto :EOF

:: JScript portion */

var shl = new ActiveXObject("Shell.Application");
var folder = shl.BrowseForFolder(0, "Please choose a folder.", 0, 0x00);
WSH.Echo(folder ? folder.self.path : '');

folder selection dialog

BrowseForFolder()方法中,第四个参数指定了层次结构的根。请参阅ShellSpecialFolderConstants以获取有效值列表。

8
可以使用纯批处理来选择文件/文件夹,如下所示。当然,感觉和外观不像GUI那样令人愉悦,但它的功能非常强大,在我看来比GUI版本更易于使用。选择方法基于CHOICE命令,因此在不包括该命令的Windows版本中需要下载它并略微修改其参数。当然,可以轻松修改代码以使用SET / P而不是CHOICE,但这种更改将消除只需要按一个键即可导航和选择的非常简单和快速的选择方法。
@echo off
setlocal

rem Select a file or folder browsing a directory tree
rem Antonio Perez Ayala

rem Usage examples of SelectFileOrFolder subroutine:

call :SelectFileOrFolder file=
echo/
echo Selected file from *.* = "%file%"
pause

call :SelectFileOrFolder file=*.bat
echo/
echo Selected Batch file = "%file%"
pause

call :SelectFileOrFolder folder=/F
echo/
echo Selected folder = "%folder%"
pause

goto :EOF


:SelectFileOrFolder resultVar [ "list of wildcards" | /F ]

setlocal EnableDelayedExpansion

rem Process parameters
set "files=*.*"
if "%~2" neq "" (
   if /I "%~2" equ "/F" (set "files=") else set "files=%~2"
)

rem Set the number of lines per page, max 34
set "pageSize=30"
set "char=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

rem Load current directory contents
set "name[1]=<DIR>  .."
:ProcessThisDir
set "numNames=1"
for /D %%a in (*) do (
   set /A numNames+=1
   set "name[!numNames!]=<DIR>  %%a"
)
for %%a in (%files%) do (
   set /A numNames+=1
   set "name[!numNames!]=       %%a"
)
set /A numPages=(numNames-1)/pageSize+1

rem Show directory contents, one page at a time
set start=1
:ShowPage
set /A page=(start-1)/pageSize+1, end=start+pageSize-1
if %end% gtr %numNames% set end=%numNames%
cls
echo Page %page%/%numPages% of %CD%
echo/
if %start% equ 1 (set base=0) else set "base=1"
set /A lastOpt=pageSize+base, j=base
for /L %%i in (%start%,1,%end%) do (
   for %%j in (!j!) do echo     !char:~%%j,1! -  !name[%%i]!
   set /A j+=1
)
echo/

rem Assemble the get option message
if %start% equ 1 (set "mssg=: ") else (set "mssg= (0=Previous page")
if %end% lss %numNames% (
   if "%mssg%" equ ": " (set "mssg= (") else set "mssg=%mssg%, "
   set "mssg=!mssg!Z=Next page"
)
if "%mssg%" neq ": " set "mssg=%mssg%): "

:GetOption
choice /C "%char%" /N /M "Select desired item%mssg%"
if %errorlevel% equ 1 (
   rem "0": Previous page or Parent directory
   if %start% gtr 1 (
      set /A start-=pageSize
      goto ShowPage
   ) else (
      cd ..
      goto ProcessThisDir
   )
)
if %errorlevel% equ 36 (
   rem "Z": Next page, if any
   if %end% lss %numNames% (
      set /A start+=pageSize
      goto ShowPage
   ) else (
      goto GetOption
   )
)
if %errorlevel% gtr %lastOpt% goto GetOption
set /A option=start+%errorlevel%-1-base
if %option% gtr %numNames% goto GetOption
if defined files (
   if "!name[%option%]:~0,5!" neq "<DIR>" goto endSelect
) else (
   choice /C OS /M "Open or Select '!name[%option%]:~7!' folder"
   if errorlevel 2 goto endSelect
)
cd "!name[%option%]:~7!"
goto ProcessThisDir

:endSelect
rem Return selected file/folder
for %%a in ("!name[%option%]:~7!") do set "result=%%~Fa"
endlocal & set "%~1=%result%
exit /B

这个能调整一下让它支持浏览外接硬盘吗? - aerodavo
要更改目录,只需在此文件的开头添加cd语句。您可以将包含路径的变量传递给此批处理文件。然后,在此批处理文件的开头添加一个if else语句,如:if "%~1" neq "" cd "%~1" - Felicity
请使用以下代码 if "%~1" neq "" if exist "%~1\*" (cd "%~1") 来检查并确保路径存在,然后再执行 cd 命令。 - Felicity

7

使用Batch直接运行PowerShell命令的其他解决方案

rem preparation command
set pwshcmd=powershell -noprofile -command "&{[System.Reflection.Assembly]::LoadWithPartialName('System.windows.forms') | Out-Null;$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog; $OpenFileDialog.ShowDialog()|out-null; $OpenFileDialog.FileName}"

rem exec commands powershell and get result in FileName variable
for /f "delims=" %%I in ('%pwshcmd%') do set "FileName=%%I"

echo %FileName%

这个能否支持多选? - Dorian Grv
1
找到了: https://stackoverflow.com/questions/52240766/how-to-select-multiple-files-using-the-batch-selector - Dorian Grv

6

Batch + PowerShell + C# 多语言解决方案

这个解决方案与 Batch + PowerShell hybrid 相同,但已重新添加了 C# 回退功能以实现 XP 和 Vista 的兼容性。在 xNightmare67x 的要求下,添加了多文件选择功能。

<# : chooser_XP_Vista.bat
:: // launches a File... Open sort of file chooser and outputs choice(s) to the console
:: // https://dev59.com/X2Uo5IYBdhLWcg3woAuu#36156326

@echo off
setlocal enabledelayedexpansion

rem // Does powershell.exe exist within %PATH%?

for %%I in ("powershell.exe") do if "%%~$PATH:I" neq "" (
    set chooser=powershell -noprofile "iex (${%~f0} | out-string)"
) else (

    rem // If not, compose and link C# application to open file browser dialog

    set "chooser=%temp%\chooser.exe"

    >"%temp%\c.cs" (
        echo using System;
        echo using System.Windows.Forms;
        echo class dummy {
        echo    public static void Main^(^) {
        echo        OpenFileDialog f = new OpenFileDialog^(^);
        echo        f.InitialDirectory = Environment.CurrentDirectory;
        echo        f.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*";
        echo        f.ShowHelp = true;
        echo        f.Multiselect = true;
        echo        f.ShowDialog^(^);
        echo        foreach ^(String filename in f.FileNames^) {
        echo            Console.WriteLine^(filename^);
        echo        }
        echo    }
        echo }
    )
    for /f "delims=" %%I in ('dir /b /s "%windir%\microsoft.net\*csc.exe"') do (
        if not exist "!chooser!" "%%I" /nologo /out:"!chooser!" "%temp%\c.cs" 2>NUL
    )
    del "%temp%\c.cs"
    if not exist "!chooser!" (
        echo Error: Please install .NET 2.0 or newer, or install PowerShell.
        goto :EOF
    )
)

rem // Do something with the chosen file(s)
for /f "delims=" %%I in ('%chooser%') do (
    echo You chose %%~I
)

rem // comment this out to keep chooser.exe in %temp% for faster subsequent runs
del "%temp%\chooser.exe" >NUL 2>NUL

goto :EOF
:: // end Batch portion / begin PowerShell hybrid chimera #>

Add-Type -AssemblyName System.Windows.Forms
$f = new-object Windows.Forms.OpenFileDialog
$f.InitialDirectory = pwd
$f.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*"
$f.ShowHelp = $true
$f.Multiselect = $true
[void]$f.ShowDialog()
if ($f.Multiselect) { $f.FileNames } else { $f.FileName }

如果需要在XP或Vista中使用文件夹选择器,请使用WSH解决方案npocmaka的HTA解决方案


3

另外两种方法:

1.使用混合的.bat/hta(必须保存为bat)脚本。它可以使用vbscript或javascript,但示例是使用javascrtipt。不会创建临时文件。选择文件夹不太容易,需要外部javascript库,但选择文件很容易。

<!-- : starting html comment

:: FileSelector.bat
@echo off
for /f "tokens=* delims=" %%p in ('mshta.exe "%~f0"') do (
    set "file=%%~fp"
)
echo/
if not "%file%" == "" (
    echo selected  file is : %file%
)
echo/
exit /b
-->
<Title>== FILE SELECTOR==</Title>
<body>
    <script language='javascript'>
    function pipeFile() {

         var file=document.getElementById('file').value;
         var fso= new ActiveXObject('Scripting.FileSystemObject').GetStandardStream(1);
         close(fso.Write(file));

    }
    </script>
<input type='file' name='file' size='30'>
</input><hr><button onclick='pipeFile()'>Submit</button>
</body>

1.1 - 不采用 Rojo 提出的无提交表单方案(请参见评论):

<!-- : starting html comment

:: FileSelector.bat
@echo off
for /f "tokens=* delims=" %%p in ('mshta.exe "%~f0"') do (
    set "file=%%~fp"
)
echo/
if not "%file%" == "" (
    echo selected  file is : "%file%"
)
echo/
exit /b
-->
<Title>== FILE SELECTOR==</Title>
<body>
    <script language='javascript'>
    function pipeFile() {

         var file=document.getElementById('file').value;
         var fso= new ActiveXObject('Scripting.FileSystemObject').GetStandardStream(1);
         close(fso.Write(file));

    }
    </script>
<input id='file' type='file' name='file' size='30' onchange='pipeFile()' >
</input>
<hr>
<button onclick='pipeFile()'>Submit</button>
<script>document.getElementById('file').click();</script>
</body>

2.由于您已经在使用PowerShell/Net,因此可以创建自编译的jscript.net混合体。它不需要用于编译的临时cs文件,而是直接使用内置的jscrript.net编译器。无需使用PowerShell,代码更易读:

@if (@X)==(@Y) @end /* JScript comment
@echo off

:: FolderSelectorJS.bat
setlocal

for /f "tokens=* delims=" %%v in ('dir /b /s /a:-d  /o:-n "%SystemRoot%\Microsoft.NET\Framework\*jsc.exe"') do (
   set "jsc=%%v"
)

if not exist "%~n0.exe" (
    "%jsc%" /nologo /out:"%~n0.exe" "%~dpsfnx0"
)

for /f "tokens=* delims=" %%p in ('"%~n0.exe"') do (
    set "folder=%%p"
)
if not "%folder%" == "" ( 
    echo selected folder  is %folder%
)

endlocal & exit /b %errorlevel%

*/

import System;
import System.Windows.Forms;

var  f=new FolderBrowserDialog();
f.SelectedPath=System.Environment.CurrentDirectory;
f.Description="Please choose a folder.";
f.ShowNewFolderButton=true;
if( f.ShowDialog() == DialogResult.OK ){
    Console.Write(f.SelectedPath);
}

1
您的HTA混合应用程序建议:将id ='file'添加到<input>标记中,将onchange ='pipeFile()'添加到<input>标记中,并在</body>上方添加<script>document.getElementById('file').click();</script> - rojo

1

我会留下一个“echo”来验证这段代码中的多选项是否有效

echo off
set cmd=Add-Type -AssemblyName System.Windows.Forms;$f=new-object                 Windows.Forms.OpenFileDialog;$f.InitialDirectory=        [environment]::GetFolderPath('Desktop');$f.Filter='Text Files(*.txt)^|*.txt^|All         Files(*.*)^|*.*';$f.Multiselect=$true;[void]$f.ShowDialog();if($f.Multiselect)        {$f.FileNames}else{$f.FileName}
set pwshcmd=powershell -noprofile -command "&{%cmd%}"
for /f "tokens=* delims=" %%I in ('%pwshcmd%') do call :sum "%%I" ret
echo =========
echo --%ret%--
pause
exit /B
:sum [mud] [ret]
echo "%~1"
set FileName=%FileName% "%~1"
set ret=%FileName%
exit /B

1
虽然我建议在 :sum 前加上 exit /B 并在 :sum 的结尾处加上 goto :eof。否则 :sum 将会运行两次。 - Felicity

0

rojo,感谢您提供的代码。它很有效。

小提示: 对于“浏览文件夹”代码,用户可能会单击X并根本不选择文件夹。对于这种情况,我会添加:

if not defined folder (goto :noFolderSelected)

另外,对于 BrowseForFolder() 方法,我建议将第四个参数设置为0x11。这样将显示所有驱动器。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接