In situations where input file location is not known until runtime, using a GUI for file selection input reduces the likelihood of user error.
Is there a way to invoke a file/folder chooser dialog from a Windows batch script?
Update 2016.3.20:
Since PowerShell is a native component of all modern Windows installations nowadays, I'm declaring the C# fallback as no longer necessary. If you still need it for Vista or XP compatibility, I moved it to a new answer. Starting with this edit, I'm rewriting the script as a Batch + PowerShell hybrid and incorporating the ability to perform multi-select. It's profoundly easier to read and to tweak as needed.
<# : 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 }
This results in a file chooser dialog.
The result of a selection outputs You chose C:\Users\me\Desktop\tmp.txt to the console. If you want to force single file selection, just change the $f.Multiselect property to $false.
PowerShell taken command from the Just Tinkering Blog. See the OpenFileDialog Class documentation for other properties you can set, such as Title and InitialDirectory.
Update 2015.08.10:
Since there is already a COM method for invoking a folder chooser, it's pretty easy to build a PowerShell one-liner that can open the folder chooser and output the path.
:: 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
In the BrowseForFolder() method, the fourth argument specifies the root of the hierarchy. See ShellSpecialFolderConstants for a list of valid values.
This results in a folder chooser dialog.
The result of a selection outputs You chose C:\Users\me\Desktop to the console.
See the FolderBrowserDialog class documentation for other properties you can set, such as RootFolder. My original .NET System.Windows.Forms PowerShell and C# solutions can be found in revision 4 of this answer if needed, but this COM method is much easier to read and maintain.
for /f... do set do for /f... do echo. Since mainstream support for Vista was dropped in 2012 and (at this moment) only about 1⅔% of web-browsing machines are Vista, I'm not particularly inclined to restore the C# failover code to this answer. I added it as a new answer for you, though.This should work from XP upwards and does'nt require an hibrid file, it just runs mshta with a long command line:
@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
Windows XP had a mysterious UserAccounts.CommonDialog WSH object which allowed VBScript and JScript to launch the file selection prompt. Apparently, that was deemed a security risk and removed in Vista.
However, the WSH Shell.Application object BrowseForFolder method will still allow the creation of a folder selection dialog. Here's a hybrid batch + JScript example. Save it with a .bat extension.
@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 : '');

In the BrowseForFolder() method, the fourth argument specifies the root of the hierarchy. See ShellSpecialFolderConstants for a list of valid values.
A file / folder selection may be done with pure Batch, as shown below. Of course, the feel and look is not as pleasant as a GUI, but it works very well and in my opinion it is easier to use than the GUI version. The selection method is based on CHOICE command, so it would require to download it in the Windows versions that don't include it and slightly modify its parameters. Of course, the code may be easily modified in order to use SET /P instead of CHOICE, but this change would eliminate the very simple and fast selection method that only requires one keypress to navigate and select.
@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
if "%~1" neq "" cd "%~1"if "%~1" neq "" if exist "%~1\*" (cd "%~1") to check and make sure that path exists before you cd itOther solution with direct run PowerShell command in Batch
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%
This is the same solution as the Batch + PowerShell hybrid, but with the C# fallback stuff re-added for XP and Vista compatibility. Multiple file selection has been added at 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 }
For a folder chooser for XP or Vista, use either the WSH solution or npocmaka's HTA solution.
Two more ways.
1.Using a hybrid .bat/hta (must be saved as a bat) script .It can use vbscript or javascript but the example is with javascrtipt.Does not create temp files.Selecting folder is not so easy and will require an external javascript libraries , but selecting file is easy
<!-- : 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 - without submit form proposed by rojo (see comments):
<!-- : 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.As you already using powershell/net you can create selfcompiled jscript.net hybrid.It will not require temp cs file for compilation and will directly use the built-in jscrript.net compiler.There's no need of powershell too and the code is far more readable:
@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);
}
id='file' to the <input> tag, add onchange='pipeFile()' to the <input> tag, and add <script>document.getElementById('file').click();</script> just above </body>.I will leave an 'echo' even to verify that multiple choice works in this code
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
exit /B before :sum and 2) have goto :eof at the end of :sum. Or else :sum runs twicerojo, thanks for the code. It works.
A small note: For the "Browse for Folder" code, a user may click the X and NOT select a folder at all. For this case, I would add:
if not defined folder (goto :noFolderSelected)
Also, for the BrowseForFolder() method, I recommend the 4th argument to be 0x11. This will display all drives.
I has been wrote my own portable solution:
Source codeThe utility has dependency on wxWidgets 3.1.x, so you can actually build it for other operating systems.
OR
Use VBS script:
Source code''' Opens the Open File dialog window to request a file path to be returned
''' back into standard output.
''' USAGE:
''' file_dialog.vbs [--] [<FileFilter> [<FileTypes> [<StartFolder> [<Title>]]]]
''' DESCRIPTION:
''' --
''' Separator between flags and positional arguments to explicitly stop the
''' flags parser.
'''
''' <FileFilter>:
''' File filter in format "*.ext"
''' (default: "*.*")
'''
''' <FileTypes>:
''' File type(s) in format "description (*.ext)|*.ext" or just "*.ext"
''' (default: "All files (*.*)|*.*")
'''
''' <StartFolder>:
''' The Initial folder the dialog will show on opening
''' (default: current directory)
'''
''' <Title>:
''' The caption in the dialog's title bar
''' (default: "Open")
''' Error codes:
''' 255 - unspecified error
''' 1 - <StartFolder> is defined but not exists
''' 0 - Success
''' CAUTION:
''' Windows Scripting Host version 5.8 (Windows 7, 8, 8.1) has an issue
''' around a conditional expression:
''' `If Expr1 Or Expr2 ...`
''' , where `Expr2` does execute even if `Expr1` is `True`.
'''
''' Additionally, there is another issue, when the `Expr2` can trigger the
''' corruption of following code.
'''
''' The case is found in the `Expr2` expression, where a function does write
''' into it's input parameter.
'''
''' To workaround that we must declare a temporary parameter in the function
''' of the `Expr2` and write into a temporary variable instead of an input
''' parameter.
'''
''' Example of potentially corrupted code:
'''
''' Dim Expr1 : Expr1 = True ' or returned from a function call
''' Function Expr2(MyVar1)
''' MyVar1 = ... ' write into input parameter triggers the issue
''' End Function
''' If Expr1 Or Expr2 Then
''' ... ' code here is potentially corrupted
''' End If
'''
''' Example of workarounded code:
'''
''' Dim Expr1 : Expr1 = True ' or returned from a function call
''' Function Expr2(MyVar1)
''' Dim TempVar1 : TempVar1 = MyVar1
''' TempVar1 = ... ' write into temporary parameter instead
''' End Function
''' If Expr1 Or Expr2 Then
''' ... ' workarounded
''' End If
'''
''' Another workaround is to split the `Or` expression in a single `If` by a
''' sequence of `If`/`ElseIf` conditions.
'''
Function IsNothing(obj)
If IsEmpty(obj) Then
IsNothing = True
Exit Function
End If
If obj Is Nothing Then
IsNothing = True
Else
IsNothing = False
End If
End Function
Function IsEmptyArg(args, index)
''' Based on: https://stackoverflow.com/questions/4466967/how-can-i-determine-if-a-dynamic-array-has-not-be-dimensioned-in-vbscript/4469121#4469121
On Error Resume Next
Dim args_ubound : args_ubound = UBound(args)
If Err = 0 Then
If args_ubound >= index Then
' CAUTION:
' Must be a standalone condition.
' Must be negative condition in case of an invalid `index`
If Not (Len(args(index)) > 0) Then
IsEmptyArg = True
Else
IsEmptyArg = False
End If
Else
IsEmptyArg = True
End If
Else
' Workaround for `WScript.Arguments`
Err.Clear
Dim num_args : num_args = args.count
If Err = 0 Then
If index < num_args Then
' CAUTION:
' Must be a standalone condition.
' Must be negative condition in case of an invalid `index`
If Not (Len(args(index)) > 0) Then
IsEmptyArg = True
Else
IsEmptyArg = False
End If
Else
IsEmptyArg = True
End If
Else
IsEmptyArg = True
End If
End If
On Error Goto 0
End Function
Function FixStrToPrint(str)
Dim new_str : new_str = ""
Dim i, Char, CharAsc
For i = 1 To Len(str)
Char = Mid(str, i, 1)
CharAsc = Asc(Char)
' NOTE:
' `&H3F` - is not printable unicode origin character which can not pass through the stdout redirection.
If CharAsc <> &H3F Then
new_str = new_str & Char
Else
new_str = new_str & "?"
End If
Next
FixStrToPrint = new_str
End Function
Sub PrintOrEchoLine(str)
On Error Resume Next
WScript.stdout.WriteLine str
If err = 5 Then ' Access is denied
WScript.stdout.WriteLine FixStrToPrint(str)
ElseIf err = &h80070006& Then
WScript.Echo str
End If
On Error Goto 0
End Sub
ReDim cmd_args(WScript.Arguments.Count - 1)
Dim ExpectFlags : ExpectFlags = True
Dim arg
Dim j : j = 0
For i = 0 To WScript.Arguments.Count-1 : Do ' empty `Do-Loop` to emulate `Continue`
arg = WScript.Arguments(i)
If ExpectFlags Then
If arg <> "--" And Mid(arg, 1, 1) = "-" Then
'If arg = "-..." Or arg = "-." Then
'Else
PrintOrEchoErrorLine WScript.ScriptName & ": error: unknown flag: `" & arg & "`"
WScript.Quit 255
'End If
Else
ExpectFlags = False
If arg = "--" Then Exit Do
End If
End If
If Not ExpectFlags Then
cmd_args(j) = arg
j = j + 1
End If
Loop While False : Next
ReDim Preserve cmd_args(j - 1)
' MsgBox Join(cmd_args, " ")
Dim FileFilter, FileTypes, StartFolder, Title
If Not IsEmptyArg(cmd_args, 0) Then
FileFilter = cmd_args(0)
End If
If Not IsEmptyArg(cmd_args, 1) Then
FileTypes = cmd_args(1)
End If
If Not IsEmptyArg(cmd_args, 2) Then
StartFolder = cmd_args(2)
End If
If Not IsEmptyArg(cmd_args, 3) Then
Title = cmd_args(3)
End If
If Not Len(FileFilter) > 0 Then
FileFilter = "*.*"
End If
If Not Len(FileTypes) > 0 Then
FileTypes = "All files (*.*)|*.*"
End If
If Not Len(Title) > 0 Then
Title = "Open"
End If
If Len(StartFolder) > 0 Then
Dim objFS : Set objFS = CreateObject("Scripting.FileSystemObject")
Dim StartFolderAbs : StartFolderAbs = objFS.GetAbsolutePathName(StartFolder) ' CAUTION: can alter a path character case if path exists
' remove `\\?\` prefix
If Left(StartFolderAbs, 4) = "\\?\" Then
StartFolderAbs = Mid(StartFolderAbs, 5)
End If
' test on path existence including long path
Dim IsDirExist : IsDirExist = objFS.FolderExists("\\?\" & StartFolderAbs)
If Not IsDirExist Then
PrintOrEchoErrorLine _
WScript.ScriptName & ": error: directory does not exist:" & vbCrLf & _
WScript.ScriptName & ": info: StartFolder=`" & StartFolderAbs & "`"
WScript.Quit 1
End If
' test on long path existence
If Not objFS.FolderExists(StartFolderAbs) Then
' translate into short path
' WORKAROUND:
' We use `\\?\` to bypass `GetFolder` error: `Path not found`.
Dim Folder : Set Folder = objFS.GetFolder("\\?\" & StartFolderAbs & "\")
Dim FolderShortPath : FolderShortPath = Folder.ShortPath
If Left(FolderShortPath, 4) = "\\?\" Then
FolderShortPath = Mid(FolderShortPath, 5)
End If
StartFolderAbs = FolderShortPath
End If
End If
Dim objShell : Set objShell = WScript.CreateObject("WScript.Shell")
Function ShellExecMshtaFileDlg(FileFilter, FileTypes, StartFolder, Title)
Dim FileFilter_
If Len(StartFolder) > 0 Then
If Not Right(StartFolder, 1) = "\" Then
FileFilter_ = StartFolder & "\" & FileFilter
Else
FileFilter_ = StartFolder & FileFilter
End If
Else
FileFilter_ = FileFilter
End If
FileFilter_ = Replace(FileFilter_, "\", "\\")
' javascript shell code for stdin
Dim ShellCode : ShellCode = "var FileFilter='" & FileFilter_ & "';var FileTypes='" & FileTypes & "';var Title='" & Title & "';"
Dim objShellExec : Set objShellExec = _
objShell.Exec("""%SystemRoot%\System32\mshta.exe"" " & _
"""about:<object id=d classid=clsid:3050f4e1-98b5-11cf-bb82-00aa00bdce0b></object>" & _
"<script>moveTo(0,-9999);var objFS = new ActiveXObject('Scripting.FileSystemObject');" & _
"eval(objFS.GetStandardStream(0).Read(" & Len(ShellCode) & "));" & _
"function window.onload(){var p=/[^\0]*/;objFS.GetStandardStream(1).Write(p.exec(d.object.openfiledlg(FileFilter, null, FileTypes, Title)));close();}" & _
"</script><hta:application showintaskbar=no />""")
' write shell code
objShellExec.StdIn.Write ShellCode
ShellExecMshtaFileDlg = Array(objShellExec.ExitCode, objShellExec.StdOut.ReadAll)
End Function
Dim arrFileDlg : arrFileDlg = ShellExecMshtaFileDlg(FileFilter, FileTypes, StartFolder, Title)
PrintOrEchoLine(arrFileDlg(1))
WScript.Quit arrFileDlg(0)