Question How can I programmatically locate an EXE file's path?

RobertAlanGustafsonII

Active member
Joined
Sep 16, 2023
Messages
29
Programming Experience
10+
WHAT I HAVE:
Visual Basic 2019, .NET Framework 4.6+, WinForms

MY ISSUE:

I would like my program to programmatically search for an exe file on my system drive and return its full path (so my program knows if and where it exists before trying to start the Process), but I've realized that the file is all but certain to be in a folder that one needs special permission to search (or a subfolder thereof)--i.e., "System", "Program Files", "Program Files (x86)"--complicating my efforts to use GetFiles/GetDirectories to look for it. Even trying do a single GetFiles method call on entire system drive with SearchOptions.AllDirectories causes an exception--and a targeted search within strategic folders (and their subfolders) causes many exceptions. Is there a (relatively) simple way for my app to programmatically obtain the necessary permissions for parent folders where program files are typically stored--or, better yet, a simple 1-step (or few-step) way to simply find the path of an arbitrary exe file? I want a VB.NET procedure that takes a String like ExeFile and returns a fully-qualified string for its entire path. Please give it to me ASAP in VB.NET, and as simply as possible.
 
Solution
Here is my solution(!!): To recursively go through the folders and subfolders looking for a given file or folder (or type thereof), starting with a parent folder, and skipping over subfolders that trigger access violations.
VB.NET:
    ''' <summary>
    ''' Recursively search for a file or folder from a given parent folder
    ''' (similar to Directory.GetFiles and Directory.GetDirectories with
    '''  SearchOption.AllDirectories, except that access errors are ignored,
    '''  and there is an option to stop once any match is found)
    ''' </summary>
    ''' <param name="PathName">Parent drive or folder to search from</param>
    ''' <param name="PathPattern">File/folder name pattern to search for
    ''' (defaults to search...
The GetFiles method has been improved in .NET Core to be able to ignore exceptions on inaccessible folders but, as you're on .NET Framework, that's not an option, so you can't use GetFiles or the like for more than a single folder at a time. You have to write your own recursive file search code, so you can manually catch and then ignore those exceptions. There are plenty of examples already on the web so, rather than our providing another one here, I would suggest that you do a bit of searching and a bit of experimentation, then post back if you encounter any specific issue(s).
 
Maybe an over simplistic question, is the EXE you'll be looking for an application that creates an entry in the Start Menu?
Another option could be using Dir exename.exe /s > search_result.txt, it'll pipe the search results into text file where you can look at the results.
 

jmcilhinney:​

Please direct me to 1 more of those "many examples on the web" (i.e., provide links). I often find it difficult to set search-engine text for something very specific when it's programming related, and I don't like going on wild-goose chases.

Also, my program appears not to have permission to search the very folders (and therefore their subfolders) where an installed app's exe file is like to be (i.e., system folder, the 2 program-files folders)! How can I set up my app to have or obtain the necessary permission? (And would my program have to apply this recursively to these folders' subfolders?)

jdelano:​

When it comes to do piping the results of a "dir" search to a file, how do I properly parse it to see if and where the file exists? That seems like a very non-trivial task, given the format of a "dir" dump, and I'm looking for bottom-line data--where, if anywhere, the program lives.

==============================================
All in all, both of you, I need to get results fast.
 
Last edited:
I just typed "vb.net recursive file search" into a search engine and the first result was such an example. No trouble at all, but you have actually try.

The current user has to be an admin in order to see the contents of the Program Files folder. That's a Windows thing, not a .NET thing. I don't think that you have to actually run the app as an admin but I'm not 100% sure.
 
This is fairly straight forward and really quick (faster than use DIR /S) my old brain goes to the old style too quickly and I forgot about the WHERE command.

Search using 'dos' command:
Private Sub btnDirSearch_Click(sender As Object, e As EventArgs) Handles btnDirSearch.Click
       Dim processStartInfo As New ProcessStartInfo()
        processStartInfo.FileName = "cmd.exe"
        processStartInfo.Arguments = "/c where /R d:\vs_projects darkEV.csproj > f:\temp\searchresults.txt"
        processStartInfo.RedirectStandardOutput = True
        processStartInfo.UseShellExecute = False
        processStartInfo.CreateNoWindow = True

        btnDirSearch.Text = "Searching..."
        Application.DoEvents()

        Dim process As New Process()
        process.StartInfo = processStartInfo
        process.Start()
        Dim result As String = process.StandardOutput.ReadToEnd()
        process.WaitForExit()

        Dim resultFile As StreamReader
        Dim info As FileInfo
        Dim resultFileData As String

        info = New FileInfo("f:\temp\searchresults.txt")
        If info.Length = 0 Then
            MessageBox.Show("The file wasn't found")

            btnDirSearch.Text = "Search"
            Application.DoEvents()

            Exit Sub
        End If

        ' open the file and extract the path
        resultFile = New StreamReader("F:\Temp\SearchResults.txt")
        resultFileData = resultFile.ReadToEnd
        resultFileData = resultFileData.Replace(vbCrLf, "")
        resultFile.Close()

        btnDirSearch.Text = "Search"
        Application.DoEvents()

        MessageBox.Show($"The file was found in {Path.GetDirectoryName(resultFileData)}")
End Sub

edit: forgot the screenshot and the code didn't format correctly at first
edit2: add the button click part of the code to see if it will be formatted correctly
 

Attachments

  • Screenshot 2024-12-28 085708.png
    Screenshot 2024-12-28 085708.png
    108.5 KB · Views: 9
Last edited by a moderator:
You can also use LINQ to do a search (it isn't as fast as using the WHERE command)

it needs Imports System.Linq
VB.NET:
        Dim files As List(Of String) = Directory.EnumerateFiles("d:\vs_projects", "darkEV.csproj", SearchOption.AllDirectories).ToList()

        If files.Count = 0 Then
            MessageBox.Show("The file wasn't found")
            Exit Sub
        End If

        Dim foundInPaths As String = ""
        For Each file As String In files
            foundInPaths += Path.GetDirectoryName(file) & vbCrLf
        Next

        MessageBox.Show($"The file was found in {vbCrLf} {foundInPaths}")
 

Attachments

  • Screenshot 2024-12-28 093835.png
    Screenshot 2024-12-28 093835.png
    10.6 KB · Views: 11
You can also use LINQ to do a search (it isn't as fast as using the WHERE command)

it needs Imports System.Linq
VB.NET:
        Dim files As List(Of String) = Directory.EnumerateFiles("d:\vs_projects", "darkEV.csproj", SearchOption.AllDirectories).ToList()

        If files.Count = 0 Then
            MessageBox.Show("The file wasn't found")
            Exit Sub
        End If

        Dim foundInPaths As String = ""
        For Each file As String In files
            foundInPaths += Path.GetDirectoryName(file) & vbCrLf
        Next

        MessageBox.Show($"The file was found in {vbCrLf} {foundInPaths}")

jdelano​

The method abouve has 2 problems:
1. (minor problem) It looks for a .csproj file, not an .exe file (changing the extension will solve that)
2. (major problem) It looks in a specific directory (and its subdirectories), and I need to look in multiple parent directories. In particular, I need to look in the "Program Files" and "Program Files (x86)" folders (the most likely ancestor folder for an app's exe file) and their subfolders, and using GetFiles or GetDirectories there results in a permissions exception. How do I get around that issue?!

As for the previous example that uses the "cmd.exe" Process, I am dubious about using DoEvents, as it will allow other code to run in my program, including code that is dependent on the outcome of the search.
 
There is no major problem. You can only look in one folder at a time so you need a method that does that, which you have. If you need to look in multiple folders then write a method that calls the method you already have multiple times, once for each folder, either sequentially or in parallel. This is simple logic. Your complaint is like saying that you know how to crack an egg but your recipe requires multiple eggs and you don't know how to do that.

As for the permissions, if it's specific subfolders that are inaccessible then you need to simply catch the exception and move on, which is the whole point of using recursion explicitly. If you mean that the top-level folder is not accessible to the user running the app then you're basically out of luck. If you want to access a folder, the app needs to run as a user who can access that folder. You can't just circumvent Windows security because you feel like it. I think that running the app under an administrator account should be enough but it's possible you may need to actually run the app as an administrator. If you want to do that, search the Web for how, use what you find and then post back if you have any specific issues.
 
The method abouve has 2 problems:
1. (minor problem) It looks for a .csproj file, not an .exe file (changing the extension will solve that)
2. (major problem) It looks in a specific directory (and its subdirectories), and I need to look in multiple parent directories. In particular, I need to look in the "Program Files" and "Program Files (x86)" folders (the most likely ancestor folder for an app's exe file) and their subfolders, and using GetFiles or GetDirectories there results in a permissions exception. How do I get around that issue?!

1) You can replace the file that the code is looking for with anything you want, this was an example.
2) You can also change the starting folder it searches in. They are minor changes to this example
 
Here is my solution(!!): To recursively go through the folders and subfolders looking for a given file or folder (or type thereof), starting with a parent folder, and skipping over subfolders that trigger access violations.
VB.NET:
    ''' <summary>
    ''' Recursively search for a file or folder from a given parent folder
    ''' (similar to Directory.GetFiles and Directory.GetDirectories with
    '''  SearchOption.AllDirectories, except that access errors are ignored,
    '''  and there is an option to stop once any match is found)
    ''' </summary>
    ''' <param name="PathName">Parent drive or folder to search from</param>
    ''' <param name="PathPattern">File/folder name pattern to search for
    ''' (defaults to search for all files/folders)</param>
    ''' <param name="LookForFile">True (default) if looking for file(s),
    ''' False if looking for folder(s)</param>
    ''' <param name="StopAtFirstFind">True to stop once any match is found,
    ''' False (default) to search entire folder tree for all possible matches</param>
    ''' <returns>String array of matching files/folders, Nothing if none are found</returns>
    ''' <remarks>If StopAtFirstFind is True, then only the very FIRST matching
    ''' file/folder found is returned in array (any array of 1 element is returned),
    ''' even if multiple matches exist</remarks>
    Public Function SearchForPath(ByVal PathName As String, _
        Optional ByVal PathPattern As String = "*.*", _
        Optional ByVal LookForFile As Boolean = True, _
        Optional ByVal StopAtFirstFind As Boolean = False) As String()
    Dim PathList As List(Of String) = New List(Of String), _
        Paths As String() = Nothing, Folders() As String = Nothing
    '   look for file or subfolder in current folder
    Try
        If LookForFile Then
            Paths = Directory.GetFiles(PathName, PathPattern)
         Else
            Paths = Directory.GetDirectories(PathName, PathPattern)
        End If
     Catch ex As AccessViolationException
        '   ignore this error
     Catch ex As UnauthorizedAccessException
        '   ignore this error
    End Try
    '   add any matches found
    Dim AnyAreFound As Boolean = _
        Paths IsNot Nothing AndAlso Paths.Length > 0
    If AnyAreFound Then
        PathList.AddRange(Paths)
    End If
    '   do we need to look in subfolders?
    If Not (AnyAreFound AndAlso StopAtFirstFind) Then
        '   get list of subfolders
        Try
            Folders = Directory.GetDirectories(PathName)
         Catch ex As AccessViolationException
            '   ignore this error
         Catch ex As UnauthorizedAccessException
            '   ignore this error
        End Try
        '   search each subfolder for matches
        If Folders IsNot Nothing Then
            For Each SubFolder As String In Folders
                Paths = SearchForPath(SubFolder, PathPattern, LookForFile, StopAtFirstFind)
                '   add any matches found
                AnyAreFound = _
                    Paths IsNot Nothing AndAlso Paths.Length > 0
                If AnyAreFound Then
                    PathList.AddRange(Paths)
                    If StopAtFirstFind Then
                        Exit For 'quit at 1st find
                    End If
                End If
            Next SubFolder
        End If
    End If
    '   return array
    If PathList.Count > 0 Then
        If StopAtFirstFind Then
            Return {PathList(0)}    'return only 1st match
         Else
            Return PathList.ToArray 'return all matches
        End If
     Else
        Return Nothing             'no matches
    End If
    End Function
This tends to work for me!
 
Solution
AccessViolationException has no place here, that is about exceptions regarding unmanaged memory, not file system access.

You may have noticed File and Directory have iterator methods, like EnumerateFiles and EnumerateDirectories, the point of them is to return results as they are discovered and not wait until search is completed before returning. This has several benefits; it saves memory, consumer can process items early, and can also stop the search at any time when satisfied with what has been returned even if the iterator has not completed the source. See Iterators - Visual Basic about implementation details.

This is more a proof of concept, since as mentioned .Net has build in support now to ignore inacccessible, but if you're limited to .Net Framework you could do something like this. It is also an example of iterators. Since you intend to have a utility function for both files and directories the FileSystemInfo could be used like here:

VB.NET:
Private Iterator Function EnumFileSystemInfos(info As FileSystemInfo, lookForFile As Boolean) As IEnumerable(Of FileSystemInfo)
    If TypeOf info Is FileInfo Then
        If lookForFile Then Yield info
    Else
        If Not lookForFile Then Yield info

        'recursive
        Dim folder = DirectCast(info, DirectoryInfo)
        Try
            For Each entry In folder.EnumerateFileSystemInfos
                For Each info In EnumFileSystemInfos(entry, lookForFile)
                    Yield info
                Next
            Next
        Catch ex As UnauthorizedAccessException
            'IgnoreInaccessible
        End Try
    End If
End Function

This method does not filter by pattern, but it will return either files or directories. So how to filter by pattern? This is called "globbing", see File globbing - .NET, and there exist a Nuget package for this called Microsoft.Extensions.FileSystemGlobbing. Then writing the SearchForPath method (also as iterator) becomes much simpler. It uses the previous EnumFileSystemInfos and "globs" the names, be it file or directory.

VB.NET:
Public Iterator Function SearchForPath(pathName As String,
    Optional pathPattern As String = "*.*",
    Optional lookForFile As Boolean = True,
    Optional stopAtFirstFind As Boolean = False) As IEnumerable(Of String)

    Dim info As New DirectoryInfo(pathName)
    Dim globber As New Matcher
    globber.AddInclude(pathPattern)
    
    For Each entry In EnumFileSystemInfos(info, lookForFile)
        Dim result = globber.Match(entry.Name)
        If result.HasMatches Then
            Yield entry.FullName
            If stopAtFirstFind Then Return
        End If
    Next
End Function

It is not as fast as native file system filtering, but it does perform decently. It think it was also interesting to learn about "file globbing", imagine you have list of paths stored somewhere and want to filter them in memory like file system does, then that package is a simple solution. It also allows for more advanced filters with multiple inclusions and/or exclusions.
 
Back
Top