I know similar questions have been asked before but all the answers have been partial, for example suggesting I use AssocQueryString which I am doing. However I'm still failing to find some exe. For example the code below fails to find outlook.exe or firefox.exe - Typing outlook.exe into Windows Explorer address bar finds these almost instantley.
In the code below the exe fileName can be any local location on the users machine, it may or may not be on the users search path.
How can I improve this to find the files? (this is called from a 32 bit exe)
// for example, get actual full path to outlook.exe
string fullPath = FindPath("outlook.exe");
public static string FindPath(string fileName)
{
uint length = UnsafeMethods.MAX_PATH;
StringBuilder path = new StringBuilder((int)length);
if (Path.GetDirectoryName(fileName) == string.Empty)
{
if (UnsafeMethods.AssocQueryString(UnsafeMethods.AssocF.OpenByExeName, UnsafeMethods.AssocStr.Executable, fileName, null, path, ref length) != 0)
{
IntPtr filePart = IntPtr.Zero;
IntPtr wow64Value = IntPtr.Zero;
// try 64 bit path
UnsafeMethods.Wow64DisableWow64FsRedirection(ref wow64Value);
bool success = UnsafeMethods.SearchPath(null, fileName, null, path.Capacity, path, ref filePart) > 0;
UnsafeMethods.Wow64RevertWow64FsRedirection(wow64Value);
if (!success)
{
// try 32 bit path
success = UnsafeMethods.SearchPath(null, fileName, null, path.Capacity, path, ref filePart) > 0;
}
}
return path.ToString();
}
else
{
return fileName;
}
}
It seems there are many places to look to find the path to an exe. While the original code above works, it is not an exhaustive search and you also need to look in the registry key SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\ and also check the SpecialFolders Windows, System and SystemX86 (https://learn.microsoft.com/en-us/dotnet/api/system.environment.specialfolder?view=netframework-4.7.2)
Related
I have developed a c# code snippet to determine if the virtual (on-screen) keyboard was showing or not.
The code below worked fine in Windows 7, 8 and 8.1, but in Windows 10, the IsKeyboardVisible always returns true...
public static bool IsKeyboardVisible() {
Process keyboardProc;
UInt32 WS_DISABLED = 0x8000000;
UInt32 WS_VISIBLE = 0X94000000;
int GWL_STYLE = -16;
IntPtr keyboardHandle = GetKeyboardWindowHandle();
bool visible = false;
if (keyboardHandle != IntPtr.Zero) {
UInt32 style = GetWindowLong(keyboardHandle, GWL_STYLE);
// in Win10, this always returns "true", i.e. WS_DISABLED is
//
//visible = ((style & WS_DISABLED) != WS_DISABLED);
// UPDATE: I found this code helping here
visible = (style == WS_VISIBLE);
}
return visible;
}
I used a tutorial on SO, but it's a while ago so sorry for not crediting the author.
Does anyone know about a working code snippet for all recent Windows versions, so I don't have to check the actual OS to switch on the version...?
UPDATE
I found the original post here, which allowed me to correct the code. So now my problem is the same old Win10 issue - I can't show the virtual keyboard using
string progFiles = #"C:\Program Files\Common Files\Microsoft Shared\ink";
string keyboardPath = Path.Combine(progFiles, "TabTip.exe");
keyboardProc = Process.Start(keyboardPath);
... Again, is there any "all-platform" code I can use, or what is the suggested approach for Win10?
UPDATE 2
I found out about issues running a 32-bit application on a 64-bit os. That being said, the error occurs whether I try to run osk.exe in the System32 or the "sysWOW64` folder... Is there any other way than making a 64-bit release???
After much digging about TabTip.exe, osk.exe and x86 and x64 compatibility issues, I found a solution by searching the osk.exe on my system and trying to run each of them. I found 4 version the following folders:
C:\Windows\System32
C:\Windows\SysWOW64
C:\Windows\WinSxS\amd64_microsoft...
C:\Windows\WinSxS\wow64_microsoft...
It appears the one in C:\Windows\WinSxS\amd64_microsoft... works fine (not the other three though)...
Given the "amd64_...." folder might not be the same on different machines (I actually checked and they don't match, I didn't search whether this depends on the machine, the windows build or anything else...).
So basically I did a small routine to look into WinSxS folder and returning the very firs occurrence of osk.exe, which works just fine. I also made the code working on a 32-bit OS using a simple OS-architecture test:
string OSKpath64 = getOskPath(#"C:\Windows\WinSxS");
if (string.IsNullOrWhiteSpace(OSKpath64)) {
OSKpath64 = "osk.exe";
}
string OSKpath32 = #"C:\Windows\System32\osk.exe";
if (!File.Exists(OSKpath32)) {
OSKpath32 = #"osk.exe";
}
System.Diagnostics.Process.Start((Environment.Is64BitOperatingSystem) ? OSKpath64 : OSKpath32);
UPDATE:
The confusion with one working and one non-working version within the WinSxS folder made me nervous. It works just fine because the amd_.. folder is alphabetically before wow64_....
I therefore suggest to add a test in the getOskPath method to return the first native 64-bit osk.exe (not the emulated one).
Using the IsWin64Emulator method found here, the method looks like this:
static string getOskPath(string dir) {
string path = Path.Combine(dir, "osk.exe");
if (File.Exists(path)) {
Process p = System.Diagnostics.Process.Start(path);
if (p.IsWin64Emulator()) {
path = string.Empty;
}
p.Kill();
return path;
}
DirectoryInfo di = new DirectoryInfo(dir);
foreach (DirectoryInfo subDir in di.GetDirectories().Reverse()) {
path = getOskPath(Path.Combine(dir, subDir.Name));
if (!string.IsNullOrWhiteSpace(path)) {
return path;
}
}
return string.Empty;
}
Same problem with me, I try all answer here, but it not work.
After finding solution with google, this is is ok.
// Step 1: For Load On-Screen Keyboard
const string Kernel32dll = "Kernel32.Dll";
[DllImport(Kernel32dll, EntryPoint = "Wow64DisableWow64FsRedirection")]
public static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport(Kernel32dll, EntryPoint = "Wow64EnableWow64FsRedirection")]
public static extern bool Wow64EnableWow64FsRedirection(IntPtr ptr);
IntPtr wow64Value;
//---------------------------------------
// Step 2: Function-----
if (Environment.Is64BitOperatingSystem)
{
if (Wow64DisableWow64FsRedirection(ref wow64Value))
{
System.Diagnostics.Process.Start("osk.exe");
Wow64EnableWow64FsRedirection(wow64Value);
}
}
else
{
System.Diagnostics.Process.Start("osk.exe");
}
//----------------
I need to get a default file icon from shared file. I faced some problems with this and found the question "How to get the associated icon from a network share file".
But now I have another problem with shared files, it looks as if they did not exist. Here is my code:
public static Icon ExtractAssociatedIcon(String filePath)
{
int index = 0;
Uri uri;
if (filePath == null)
{
throw new ArgumentException(String.Format("'{0}' is not valid for '{1}'", "null", "filePath"), "filePath");
}
try
{
uri = new Uri(filePath);
}
catch (UriFormatException)
{
filePath = Path.GetFullPath(filePath);
uri = new Uri(filePath);
}
if (uri.IsFile)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException(filePath);
}
StringBuilder iconPath = new StringBuilder(260);
iconPath.Append(filePath);
IntPtr handle = SafeNativeMethods.ExtractAssociatedIcon(new HandleRef(null, IntPtr.Zero), iconPath, ref index);
if (handle != IntPtr.Zero)
{
return Icon.FromHandle(handle);
}
}
return null;
}
I always get FileNotFoundException and I don't now why. filePath is OK, and I can access these files through the Explorer. I tried to create FileInfo instance from filePath (I read somewhere that it may help) but still nothing.
What I'm missing?
IIRC, File.Exists and Directory.Exists have some problems resolving drive letters that are mapped to a network share. They might simply not see these drives, so they report false even though the path points to a valid file.
If you use UNC paths (\\server\share\file.name) instead of drive letters, it might work, so resolve a drive-letter-bassed file path to a UNC path first and pass the latter to File.Exists. See e.g. this answer to that question for an example how to do this.
This question already has answers here:
How to programmatically derive Windows Downloads folder "%USERPROFILE%/Downloads"?
(11 answers)
Closed 5 years ago.
I have made some code that will search directories and display files in a listbox.
DirectoryInfo dinfo2 = new DirectoryInfo(#"C:\Users\Hunter\Downloads");
FileInfo[] Files2 = dinfo2.GetFiles("*.sto");
foreach (FileInfo file2 in Files2)
{
listBox1.Items.Add(file2.Name);
}
However, where it says Users\Hunter - well, when people get my software, their name is not Hunter. So how can I automatically detect the user's Downloads folder?
I have tried this:
string path = Environment.SpecialFolder.UserProfile + #"\Downloads";
DirectoryInfo dinfo2 = new DirectoryInfo(Environment.SpecialFolder.UserProfile + path);
FileInfo[] Files2 = dinfo2.GetFiles("*.sto");
foreach (FileInfo file2 in Files2)
{
listBox1.Items.Add(file2.Name);
}
I get an error though.
The Downloads folder is a so called "known" folder, together with Documents, Videos, and others.
Do NOT:
combine hardcoded path segments to retrieve known folder paths
assume known folders are children of the user folder
abuse a long deprecated registry key storing outdated paths
Known folders can be redirected anywhere in their property sheets. I've gone into more detail on this several years ago in my CodeProject article.
Do:
use the WinAPI method SHGetKnownFolderPath as it is the intended and only correct method to retrieve those paths.
You can p/invoke it as follows (I've provided only a few GUIDs which cover the new user folders):
public enum KnownFolder
{
Contacts,
Downloads,
Favorites,
Links,
SavedGames,
SavedSearches
}
public static class KnownFolders
{
private static readonly Dictionary<KnownFolder, Guid> _guids = new()
{
[KnownFolder.Contacts] = new("56784854-C6CB-462B-8169-88E350ACB882"),
[KnownFolder.Downloads] = new("374DE290-123F-4565-9164-39C4925E467B"),
[KnownFolder.Favorites] = new("1777F761-68AD-4D8A-87BD-30B759FA33DD"),
[KnownFolder.Links] = new("BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968"),
[KnownFolder.SavedGames] = new("4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4"),
[KnownFolder.SavedSearches] = new("7D1D3A04-DEBB-4115-95CF-2F29DA2920DA")
};
public static string GetPath(KnownFolder knownFolder)
{
return SHGetKnownFolderPath(_guids[knownFolder], 0);
}
[DllImport("shell32",
CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
private static extern string SHGetKnownFolderPath(
[MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags,
nint hToken = 0);
}
Here's an example of retrieving the path of the Downloads folder:
string downloadsPath = KnownFolders.GetPath(KnownFolder.Downloads);
Console.WriteLine($"Downloads folder path: {downloadsPath}");
NuGet Package
If you don't want to p/invoke yourself, have a look at my NuGet package (note that the usage is different, please check its README).
The easiest way is:
Process.Start("shell:Downloads");
If you only need to get the current user's download folder path, you can use this:
I extracted it from #PacMani 's code.
// using Microsoft.Win32;
string GetDownloadFolderPath()
{
return Registry.GetValue(#"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders", "{374DE290-123F-4565-9164-39C4925E467B}", String.Empty).ToString();
}
Note:
SHGetKnownFolderPath will return the WRONG value if you changed the download-folder.
The only thing that will ever return you the correct value is reading the shell-folders registry-key 374DE290-123F-4565-9164-39C4925E467B on Windows.
Now you can either use the "!Do not use this registry key", or you can get the wrong value.
You decide which is better for you.
Cross-Platform version:
public static string GetHomePath()
{
// Not in .NET 2.0
// System.Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
if (System.Environment.OSVersion.Platform == System.PlatformID.Unix)
return System.Environment.GetEnvironmentVariable("HOME");
return System.Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
}
public static string GetDownloadFolderPath()
{
if (System.Environment.OSVersion.Platform == System.PlatformID.Unix)
{
string pathDownload = System.IO.Path.Combine(GetHomePath(), "Downloads");
return pathDownload;
}
return System.Convert.ToString(
Microsoft.Win32.Registry.GetValue(
#"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
,"{374DE290-123F-4565-9164-39C4925E467B}"
,String.Empty
)
);
}
string download = Environment.GetEnvironmentVariable("USERPROFILE")+#"\"+"Downloads";
http://msdn.microsoft.com/en-us//library/system.environment.specialfolder.aspx
There are the variables with the path to some special folders.
typically, your software shall have a configurable variable that stores the user's download folder, which can be assigned by the user, and provide a default value when not set. You can store the value in app config file or the registry.
Then in your code read the value from where it's stored.
How can I figure out if a file is in a folder that has been SUBST'ed or is located in a user folder using C#?
I think you need to P/Invoke QueryDosDevice() for the drive letter. Subst drives will return a symbolic link, similar to \??\C:\blah. The \??\ prefix indicates it is substituted, the rest gives you the drive+directory.
This is the code I use to get the information if a path is substed:
(Some parts come from pinvoke)
using System.Runtime.InteropServices;
[DllImport("kernel32.dll", SetLastError=true)]
static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);
public static bool IsSubstedPath(string path, out string realPath)
{
StringBuilder pathInformation = new StringBuilder(250);
string driveLetter = null;
uint winApiResult = 0;
realPath = null;
try
{
// Get the drive letter of the path
driveLetter = Path.GetPathRoot(path).Replace("\\", "");
}
catch (ArgumentException)
{
return false;
//<------------------
}
winApiResult = QueryDosDevice(driveLetter, pathInformation, 250);
if(winApiResult == 0)
{
int lastWinError = Marshal.GetLastWin32Error(); // here is the reason why it fails - not used at the moment!
return false;
//<-----------------
}
// If drive is substed, the result will be in the format of "\??\C:\RealPath\".
if (pathInformation.ToString().StartsWith("\\??\\"))
{
// Strip the \??\ prefix.
string realRoot = pathInformation.ToString().Remove(0, 4);
// add backshlash if not present
realRoot += pathInformation.ToString().EndsWith(#"\") ? "" : #"\";
//Combine the paths.
realPath = Path.Combine(realRoot, path.Replace(Path.GetPathRoot(path), ""));
return true;
//<--------------
}
realPath = path;
return false;
}
I think you have a few choices --
Via System.Management classes:
http://briancaos.wordpress.com/2009/03/05/get-local-path-from-unc-path/
Or
Via P/Invoking this MAPI function:
ScUNCFromLocalPath
http://msdn.microsoft.com/en-us/library/cc842520.aspx
If SUBST is run without parameters it produces a listing of all current substitutions. Get the list, and check your directory against the list.
There is also the issue of mapping a volume to a directory. I have never attempted to detect these, but the mount point directories do show up differently than regular directories, so they must have a different attribute of some kind, and that could be detected.
Is there a way to tell using C# if a file is real or a symbolic link?
I've dug through the MSDN W32 docs, and can't find anything for checking this. I'm using CreateSymbolicLink from here, and it's working fine.
private bool IsSymbolic(string path)
{
FileInfo pathInfo = new FileInfo(path);
return pathInfo.Attributes.HasFlag(FileAttributes.ReparsePoint);
}
I have some source code for symlinks posted on my blog that will allow you to:
create symlinks
check whether a path is a symlink
retrieve the target of a symlink
It also contains NUnit test cases, that you may wish to extend.
The meaty bit is:
private static SafeFileHandle getFileHandle(string path)
{
return CreateFile(path, genericReadAccess, shareModeAll, IntPtr.Zero, openExisting,
fileFlagsForOpenReparsePointAndBackupSemantics, IntPtr.Zero);
}
public static string GetTarget(string path)
{
SymbolicLinkReparseData reparseDataBuffer;
using (SafeFileHandle fileHandle = getFileHandle(path))
{
if (fileHandle.IsInvalid)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
int outBufferSize = Marshal.SizeOf(typeof(SymbolicLinkReparseData));
IntPtr outBuffer = IntPtr.Zero;
try
{
outBuffer = Marshal.AllocHGlobal(outBufferSize);
int bytesReturned;
bool success = DeviceIoControl(
fileHandle.DangerousGetHandle(), ioctlCommandGetReparsePoint, IntPtr.Zero, 0,
outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero);
fileHandle.Close();
if (!success)
{
if (((uint)Marshal.GetHRForLastWin32Error()) == pathNotAReparsePointError)
{
return null;
}
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
reparseDataBuffer = (SymbolicLinkReparseData)Marshal.PtrToStructure(
outBuffer, typeof(SymbolicLinkReparseData));
}
finally
{
Marshal.FreeHGlobal(outBuffer);
}
}
if (reparseDataBuffer.ReparseTag != symLinkTag)
{
return null;
}
string target = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer,
reparseDataBuffer.PrintNameOffset, reparseDataBuffer.PrintNameLength);
return target;
}
That is:
Open the file with CreateFile()
Call DeviceIoControl() to get the reparse point data (NOTE: it could be a junction point!)
Check out the returned data structure to inspect. The reparse tag will tell you if it is a junction point or symbolic link. This may be all you want to do.
Here is an example of differentiating files and directories from links to files and links to directories.
Links to either files or directories maintain their own attributes (creation date, permissions) separate from their targets.
File links can be deleted (e.g. using "del") without affecting the target file.
Directory links can be removed (e.g. "rmdir") without affecting the target directory. Take care when using "rd /s". This WILL remove the directory link target.
The key FileAttributes flag to check in both FileInfo and DirectoryInfo is FileAttributes.ReparsePoint.
static void Main( string[] args ) {
FileInfo file_info = new FileInfo(args[0]);
DirectoryInfo directory_info = new DirectoryInfo(args[0]);
bool is_file = file_info.Exists;
bool is_directory = directory_info.Exists;
if (is_file) {
Console.WriteLine(file_info.ToString() + " is a file");
if ( file_info.Attributes.HasFlag(FileAttributes.ReparsePoint) )
Console.WriteLine(args[0] + " is a Windows file link");
}
else if (is_directory) {
Console.WriteLine(directory_info.ToString() + " is a directory");
if ( directory_info.Attributes.HasFlag(FileAttributes.ReparsePoint) )
Console.WriteLine(args[0] + " is a Windows directory link");
}
It proves the above answers are not reliable.
Finally I got the right solution from MSDN:
To determine if a specified directory is a mounted folder, first call the GetFileAttributes function and inspect the FILE_ATTRIBUTE_REPARSE_POINT flag in the return value to see if the directory has an associated reparse point. If it does, use the FindFirstFile and FindNextFile functions to obtain the reparse tag in the dwReserved0 member of the WIN32_FIND_DATA structure. To determine if the reparse point is a mounted folder (and not some other form of reparse point), test whether the tag value equals the value IO_REPARSE_TAG_MOUNT_POINT. For more information, see Reparse Points.
Starting with .NET 6 you can use:
FileSystemInfo.LinkTarget Property
Property description:
Gets the target path of the link located in FullName, or null if this FileSystemInfo instance doesn't represent a link.
For example:
static bool IsSymbolicLink(string path)
{
FileInfo file = new FileInfo(path);
return file.LinkTarget != null;
}
GetFileInformationByHandle fills a BY_HANDLE_FILE_INFORMATION structure which has a field dwFileAttributes where bits are set with info about the file's attributes (details here). In particular, look at the bit at mask...:
FILE_ATTRIBUTE_REPARSE_POINT 1024 0x0400
A file or directory that has an
associated reparse point, or a file
that is a symbolic link.
According to this answer to Stack Overflow question Find out whether a file is a symbolic link in PowerShell, getting the System.IO.FileAttributes for the file (via File.GetAttributes), and testing for the ReparsePoint bit, works. If the bit is set, it is a symlink or a junction point. If not, it is a regular file (or hardlink).
The library MonoPosix provides API to check if a file is a symbolic link:
public bool IsSymlink(string filePath)
=> UnixFileSystemInfo.GetFileSystemEntry(filePath).IsSymbolicLink;
I know I am late to the party but found this discussion when researching same question
I found the below worked for me so thought I would post in case of use to anyone else
It works like this:-
var provider = ReparsePointFactory.Provider;
var link = provider.GetLink(#"c:\program files (x86)\common files\microsoft shared\vgx\vgx.dll");
MsgBox("Link Type: " + link.Type.ToString + " Link Target: " + link.Target + " Link Attributes: " + link.Attributes.ToString);
https://github.com/NCodeGroup/NCode.ReparsePoints https://www.nuget.org/packages/NCode.ReparsePoints/