【问题标题】:File / folder chooser dialog from a Windows batch scriptWindows 批处理脚本中的文件/文件夹选择器对话框
【发布时间】:2013-04-08 17:18:49
【问题描述】:

通常,要求用户为批处理脚本提供文件名是一件很麻烦的事情,需要没有拼写错误、在路径周围加上空格等等。不幸的是,用户并不以准确性着称。在直到运行时才知道输入文件位置的情况下,使用 GUI 进行文件选择输入可以减少用户出错的可能性。

有没有办法从 Windows 批处理脚本中调用 File... Open 样式的 gui 文件选择器或文件夹选择器?

如果脚本用户安装了 PowerShell 或 .NET,则可以。请参阅下面的答案。

我也很想看看其他人可以提供哪些其他解决方案。

【问题讨论】:

    标签: .net windows powershell batch-file cmd


    【解决方案1】:

    文件浏览器

    2016.3.20 更新:

    由于 PowerShell 是当今几乎所有现代 Windows 安装的本机组件,因此我声明不再需要 C# 回退。如果您仍然需要它来兼容 Vista 或 XP,我 moved it to a new answer。从这个编辑开始,我将脚本重写为 Batch + PowerShell 混合体,并结合执行多选的能力。它更容易阅读和根据需要进行调整。

    <# : chooser.bat
    :: launches a File... Open sort of file chooser and outputs choice(s) to the console
    :: https://stackoverflow.com/a/15885133/1683264
    
    @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 }
    

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

    选择的结果将You chose C:\Users\me\Desktop\tmp.txt 输出到控制台。如果要强制选择单个文件,只需将$f.Multiselect 属性更改为$false

    (PowerShell 命令无情地从Just Tinkering Blog 中窃取。)有关您可以设置的其他属性,请参阅OpenFileDialog Class 文档,例如TitleInitialDirectory


    文件夹浏览器

    2015.08.10 更新:

    由于invoking a folder chooser已经有了COM方法,所以很容易构建一个可以打开文件夹选择器并输出路径的PowerShell单行器。

    :: fchooser.bat
    :: launches a folder chooser and outputs choice to the console
    :: https://stackoverflow.com/a/15885133/1683264
    
    @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

    这会导致一个文件夹选择器对话框。

    选择的结果将You chose C:\Users\me\Desktop 输出到控制台。

    请参阅FolderBrowserDialog class 文档了解您可以设置的其他属性,例如RootFolder。如果需要,可以在此答案的revision 4 中找到我原来的 .NET System.Windows.Forms PowerShell 和 C# 解决方案,但这种 COM 方法更易于阅读和维护。

    【讨论】:

    • 非常感谢您的代码!效果很好!一个问题。有没有办法用这种方法选择多个文件?我添加了属性 f.Multiselect=true 并且能够通过 GUI 选择多个文件,但是它看起来只打印第一个选定的文件,所以我无法找到从对话框中读取所有选定文件的方法。你认为有可能吗?或者也许还有另一种方法可以使用 win GUI 选择多个文件?
    • 我现在觉得自己很蠢 :) 我必须更加注意代码。因此,只需在 Console.Write 行之前添加 foreach(String fileName in f.FileNames^) 即可遍历 f.FileNames 数组以打印所有必要的值。感谢您的代码!顺便说一句:打印 FileNames 数组值也适用于单选模式对话框,并且可以作为更通用的方式来处理对话框。再次感谢!
    • 很老的帖子,但我希望你能告诉我你是如何让它选择多个文件的。我希望完成同样的事情谢谢。
    • 非常感谢在 Windows 7 - 10 上完美运行。该脚本不适用于 Windows XP 和 Vista,我知道这是因为它不包括 powershell。我想知道您是否可以更改 C# 回退来完成新的 powershell 编辑。我尝试对其进行编辑,它的多项选择效果很好,但我卡在了 for 循环中,它只回显 1 个文件。
    • @xNightmare67x 而不是做for /f... do setfor /f... do echo。由于在 2012 年放弃了对 Vista 的主流支持,并且(此时)仅在 1⅔% of web-browsing machines are Vista 左右,我并不特别倾向于将 C# 故障转移代码恢复到这个答案。不过我added it as a new answer for you
    【解决方案2】:

    这应该可以从 XP 向上运行,并且不需要 hibrid 文件,它只是运行带有长命令行的 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
    

    【讨论】:

    • 请注意,它会打开一个常规的资源管理器文件选择器对话框。此方法无法更改对话框的标题。
    • 可以多选吗?
    • 用 powershell 找到它:stackoverflow.com/questions/52240766/…
    • 文件夹选择器是什么?
    【解决方案3】:

    批量直接运行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%
    

    【讨论】:

    【解决方案4】:

    批处理 + PowerShell + C# 多语言解决方案

    这与Batch + PowerShell hybrid 的解决方案相同,但重新添加了 C# 后备内容以兼容 XP 和 Vista。在xNightmare67x's request添加了多个文件选择。

    <# : chooser_XP_Vista.bat
    :: // launches a File... Open sort of file chooser and outputs choice(s) to the console
    :: // https://stackoverflow.com/a/36156326/1683264
    
    @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 solutionnpocmaka's HTA solution

    【讨论】:

      【解决方案5】:

      Windows 脚本宿主


      文件选择

      Windows XP 有一个神秘的UserAccounts.CommonDialog WSH 对象which allowed VBScript 和JScript 来启动文件选择提示。显然,那是 deemed a security risk 并在 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 : '');
      

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

      【讨论】:

        【解决方案6】:

        我什至会留下一个“回声”,以验证此代码中的多项选择是否有效

        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)exit /B:sum 之前和2)在:sum 的末尾有goto :eof。否则:sum 运行两次
        【解决方案7】:

        文件/文件夹选择可以用纯批处理来完成,如下所示。当然,手感和外观不如 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
        

        【讨论】:

        • 可以调整它以允许导航到外部硬盘驱动器吗?
        • 要更改目录,只需在该文件开头的某处添加 cd 语句即可。您可以将包含路径的变量传递给此 bat 文件。然后在这个 bat 文件的开头,添加一个 if else 语句,如:if "%~1" neq "" cd "%~1"
        • 或者这个if "%~1" neq "" if exist "%~1\*" (cd "%~1")来检查并确保在你cd它之前存在路径
        【解决方案8】:

        另外两种方法。

        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 提出的提交表单(参见 cmets):

        <!-- : 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);
        }
        

        【讨论】:

        • 对您的 HTA 混合的建议:将 id='file' 添加到 &lt;input&gt; 标签,将 onchange='pipeFile()' 添加到 &lt;input&gt; 标签,然后在 &lt;script&gt;document.getElementById('file').click();&lt;/script&gt; 上方添加 &lt;/body&gt;
        【解决方案9】:

        我已经编写了自己的便携式解决方案: https://sourceforge.net/p/contools/contools/HEAD/tree/trunk/Utilities/src/_gui/wxFileDialog/

        您可以从这里下载可执行文件: https://sourceforge.net/p/contools/contools/HEAD/tree/trunk/Utilities/bin/contools/wxFileDialog.exe

        该实用程序依赖于 wxWidgets 3.1.x,因此您实际上可以为其他操作系统构建它。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-11-29
          • 1970-01-01
          • 1970-01-01
          • 2011-07-29
          • 2011-10-04
          • 1970-01-01
          相关资源
          最近更新 更多