QueryFullProcessImageName returns error 31 - c#

I'm working on an application that should be able to show and retrieve information from all processes in the system.
I try to get the process full path using the function QueryFullProcessImageName but it fails with error 31 (A device attached to the system is not functioning), this happens only with new processes created after the program's first enumeration of the running processes.
This is the method that I use to get the full path:
public static string GetProcessFullPath(SafeProcessHandle Handle)
{
uint MaxPathLength = 1024;
StringBuilder ExePath = new StringBuilder(1024);
if (NativeMethods.Win32ProcessFunctions.QueryFullProcessImageName(Handle.DangerousGetHandle(), 0, ExePath, ref MaxPathLength))
{
return ExePath.ToString();
}
else
{
Win32Exception ex = new Win32Exception(Marshal.GetLastWin32Error());
Logger.WriteEntry(new LogEntry("Non è stato possibile recuperare il percorso completo dell'eseguibile di un processo, codice di errore: " + ex.NativeErrorCode + " (" + ex.Message + ")", EventSource.WindowsAPI, EventSeverity.Error));
return "Non disponibile";
}
}
This is the declaration of the function:
[DllImport("Kernel32.dll", EntryPoint = "QueryFullProcessImageNameW", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool QueryFullProcessImageName(IntPtr ProcessHandle, uint PathType, StringBuilder ExePath, ref uint Characters);
I don't know what could be causing this error, I get notifications of new processes from WMI (Win32_ProcessStartTrace).
This is how I get the process handle that is used in the GetProcessFullPath method (the PID is from the WMI notification):
public static SafeProcessHandle GetProcessHandle(uint ProcessID)
{
IntPtr UnsafeProcessHandle = NativeMethods.Win32ProcessFunctions.OpenProcess(NativeMethods.Win32Enumerations.ProcessAccessRights.PROCESS_ALL_ACCESS, false, ProcessID);
if (UnsafeProcessHandle == IntPtr.Zero)
{
UnsafeProcessHandle = NativeMethods.Win32ProcessFunctions.OpenProcess(NativeMethods.Win32Enumerations.ProcessAccessRights.PROCESS_QUERY_LIMITED_INFORMATION, false, ProcessID);
if (UnsafeProcessHandle == IntPtr.Zero)
{
Win32Exception ex = new Win32Exception(Marshal.GetLastWin32Error());
Logger.WriteEntry(new LogEntry("Non è stato possibile aprire un processo, codice di errore: " + ex.NativeErrorCode + " (" + ex.Message + ")", EventSource.WindowsAPI, EventSeverity.Error));
return new SafeProcessHandle(UnsafeProcessHandle, true);
}
else
{
return new SafeProcessHandle(UnsafeProcessHandle, true);
}
}
else
{
return new SafeProcessHandle(UnsafeProcessHandle, true);
}
}
Edit:
Further testing indicated that the problem occurs even if the process whose full path I'm trying to get was already started when the function is called.

GetProcessImageFileName works but it returns the path in device form
and I need a Win32 path.
You can use GetLogicalDriveStrings and QueryDosDevice convert dos file path to windows file path.
Some code:
// dos file path => windows file path
BOOL DosPathToNtPath(LPTSTR pszDosPath, LPTSTR pszNtPath)
{
TCHAR szDriveStr[500];
TCHAR szDrive[3];
TCHAR szDevName[100];
INT cchDevName;
INT i;
//Check the parameters
if (!pszDosPath || !pszNtPath)
return FALSE;
//Get local disk string
if (GetLogicalDriveStrings(sizeof(szDriveStr), szDriveStr))
{
for (i = 0; szDriveStr[i]; i += 4)
{
if (!lstrcmpi(&(szDriveStr[i]), _T("A:\\")) || !lstrcmpi(&(szDriveStr[i]), _T("B:\\"))) { continue; }
szDrive[0] = szDriveStr[i];
szDrive[1] = szDriveStr[i + 1];
szDrive[2] = '\0';
// Query Dos device name
if (!QueryDosDevice(szDrive, szDevName, 100)) { return FALSE; }
// Hit
cchDevName = lstrlen(szDevName);
if (_tcsnicmp(pszDosPath, szDevName, cchDevName) == 0) {
// Copy drive
lstrcpy(pszNtPath, szDrive);
// Copy path
lstrcat(pszNtPath, pszDosPath + cchDevName);
return TRUE;
}
}
}
lstrcpy(pszNtPath, pszDosPath);
return FALSE;
}
Then simply call,
GetProcessImageFileName(hProcess, szImagePath, MAX_PATH);
DosPathToNtPath(szImagePath,pszFullPath);
You can convert it to C# code.

The QueryFullProcessImageName function fails with ERROR_GEN_FAILURE (31 or 0x1f, "A device attached to the system is not functioning") if the process is a "zombie" process, i.e. the process terminated but not all handles to it were closed. In this case, you can still use QueryFullProcessImageNameW with the PROCESS_NAME_NATIVE flag to get the native path, but you probably just want to skip it since it's no longer running.

Related

Trying to check processes for digital signatures, having an error

Hi i've wrote this method in C# that checks all windows processes for digital signatures. Howver, it tells me that File doesn't contain a definiton for GetDigitalSignatures.
void DriverCheck()
{
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
try
{
// Check if the process has a main module
if (process.MainModule != null)
{
// Check if the main module has a digital signature
bool isSigned = File.GetDigitalSignatures(process.MainModule.FileName).Length > 0;
if (isSigned)
{
// The main module is signed
// You can also get the certificate that was used to sign the file using the following code:
}
else
{
// The main module is not signed
}
}
}
catch (System.ComponentModel.Win32Exception)
{
// The process does not have a main module
}
}
}
can someone help me?
I tried finding a namespace that contains those but didn't suceed.
You can try something like this.
using System;
using System.IO;
using System.Windows.Forms; // I have this here, because I wanted to make a WinForm app
using System.Security.Cryptography.X509Certificates;
// rest of the code - form.load, checking the signature
// on button click, displaying the info in a multiline textbox, etc
string curFile = txtPath.Text.Trim(); // I'm reading the path from a textbox
if(File.Exists(curFile))
{
try
{
byte[] fileBytes = File.ReadAllBytes(curFile);
X509Certificate cert = new X509Certificate(fileBytes);
byte[] signature = cert.GetRawCertData();
string extraInfo = "Subject: " + cert.Subject + "\r\n------\r\nIssuer: " + cert.Issuer;
txtResult.Text = extraInfo;
} catch (Exception ex)
{
txtResult.Text = DateTime.Now.ToString("HH:mm:ss") + "\r\nException: " + ex.Message;
}
} else
{
txtResult.Text = "Signature not found";
}
This is how it would look in a tiny WinForm app, made just for this.
The case when the file doesn't have a digital signature is handled in the Exception. You might want to change that for your specific use case, as you would for the way you get the file path (get all the processes, loop through them, check them individually, do something if a signature is not found, etc). For simplicity's sake, I went with a textbox solution and a GUI.
You could call signtool as external tool to validate the process signatures.
// Compile as x64 executable to be able to access 64-bit processes.
// Execute tool as Administrator to get access to more processes.
[DllImport("Kernel32.dll")]
static extern uint QueryFullProcessImageName(IntPtr hProcess, uint flags, StringBuilder text, out uint size);
// Get the path to a process excutable
// https://stackoverflow.com/a/46671939/1911064
private static string GetPathToApp(Process process)
{
string pathToExe = string.Empty;
if (null != process)
{
uint nChars = 256;
StringBuilder Buff = new StringBuilder((int)nChars);
uint success = QueryFullProcessImageName(process.Handle, 0, Buff, out nChars);
if (0 != success)
{
pathToExe = Buff.ToString();
}
else
{
int error = Marshal.GetLastWin32Error();
pathToExe = ("Error = " + error + " when calling GetProcessImageFileName");
}
}
return pathToExe;
}
static bool CheckIfSigned(string fileName)
{
bool ret = false;
if (File.Exists(fileName))
{
// https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool
// https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk
// signtool.exe is part of Windows 10 SDK
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "signtool.exe",
Arguments = $"verify /all /pa /v {fileName}",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
process.Start();
while (!process.StandardOutput.EndOfStream)
{
var line = process.StandardOutput.ReadLine();
// Console.WriteLine(line);
if (line.StartsWith("Successfully verified:"))
{
Console.WriteLine("success!");
return true;
}
if (line.StartsWith("SignTool Error:"))
{
Console.WriteLine("error!");
return false;
}
if (line.StartsWith("Number of errors: 0"))
{
Console.WriteLine("should have announced success!");
return false;
}
if (line.StartsWith("Number of errors:"))
{
Console.WriteLine("Signtool found errors!");
return false;
}
}
Console.WriteLine($"Could not recognized signtool output for {fileName}");
}
else
{
Console.WriteLine($"File not found: {fileName}");
}
return ret;
}
static void DriverCheck()
{
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
try
{
if (!process.HasExited)
{
string processPath = GetPathToApp(process);
// Check if the process has a main module
if (null != processPath)
{
// Check if the main module has a digital signature
bool isSigned = CheckIfSigned(processPath);
if (isSigned)
{
Console.WriteLine($"signed: {process.MainModule.FileName}");
}
else
{
Console.WriteLine($"**NOT** signed: {process.MainModule.FileName}");
}
}
}
}
catch (System.ComponentModel.Win32Exception ex)
{
// The process does not have a main module?
Console.WriteLine($"Win32Exception for ID={process.Id} {process.ProcessName}: {ex.Message}");
}
catch(Exception ex)
{
Console.WriteLine($"Exception for ID={process.Id} {process.ProcessName}: {ex.Message}");
}
}
}

CreateProcessAsUser return success but process exit with error 3228369022

I have 3 applications running:
Application A:
Local server running as a background service under Local System with permission to interact with the desktop ( Atleast that is what Advanced Installer says )
Application B:
A desktop capture software
Application C:
A tray icon running as the logged on user.
What i am trying to achieve is to be able to capture the desktop UAC prompts and logon screen, so here is how i am trying to achieve it:
1. Application C calls WTSGetActiveConsoleSessionId() ( which return 2),
Grab the desktop pointer:
OpenInputDesktop(0, false, (uint)(ACCESS_MASK.DESKTOP_CREATEMENU | ACCESS_MASK.DESKTOP_CREATEWINDOW | ACCESS_MASK.DESKTOP_ENUMERATE | ACCESS_MASK.DESKTOP_HOOKCONTROL | ACCESS_MASK.DESKTOP_WRITEOBJECTS | ACCESS_MASK.DESKTOP_READOBJECTS | ACCESS_MASK.DESKTOP_SWITCHDESKTOP))
and sends both to the application A ( local server )
2. The server runs the following code:
static void CreateApplicationBProcess()
{
IntPtr hToken = IntPtr.Zero;
IntPtr P = IntPtr.Zero;
/*if (!OpenProcessToken(OpenProcess(Process.GetProcessesByName("winlogon").First(), ProcessAccessFlags.All), 0x2000000, out hToken))
{
throw new Exception("OpenProcessToken error #" + Marshal.GetLastWin32Error());
}*/
if (!WTSQueryUserToken(WTSGetActiveConsoleSessionId(), out hToken))
{
throw new Exception("WTSQueryUserToken error #" + Marshal.GetLastWin32Error());
}
IntPtr duplicatedTokenHandle = IntPtr.Zero;
if(!DuplicateTokenEx(hToken, 0, IntPtr.Zero, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, ref duplicatedTokenHandle))
{
throw new Exception("DuplicateTokenEx error #" + Marshal.GetLastWin32Error());
}
uint sessionId = SessionId;
if(!SetTokenInformation(duplicatedTokenHandle, TOKEN_INFORMATION_CLASS.TokenSessionId, ref sessionId, (uint)IntPtr.Size))
{
throw new Exception("SetTokenInformation error #" + Marshal.GetLastWin32Error());
}
if (CreateEnvironmentBlock(out P, duplicatedTokenHandle, false))
{
Win32.PROCESS_INFORMATION processInfo = new Win32.PROCESS_INFORMATION();
Win32.STARTUPINFO startInfo = new Win32.STARTUPINFO();
bool bResult = false;
uint uiResultWait = Win32.WAIT_FAILED;
try
{
// Create process
startInfo.cb = Marshal.SizeOf(startInfo);
startInfo.lpDesktop = "winsta0\\default";
startInfo.dwFlags = 0x00000001;
startInfo.wShowWindow = (short)SW.SW_HIDE;
SetCurrentDirectory(GetDirectory());
Environment.CurrentDirectory = GetDirectory();
bResult = Win32.CreateProcessAsUser(duplicatedTokenHandle, Path.Combine(GetDirectory(), "ApplicationB.exe"), string.Format("{0}", (uint)DesktopPtr), IntPtr.Zero, IntPtr.Zero, false,
CREATE_UNICODE_ENVIRONMENT | CREATE_NO_WINDOW, P, GetDirectory(), ref startInfo, out processInfo);
if (!bResult)
{
throw new Exception("CreateProcessAsUser error #" + Marshal.GetLastWin32Error());
}
else
{
//IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)processInfo.dwThreadId);
ResumeThread(processInfo.hThread);
}
// Wait for process to end
uiResultWait = Win32.WaitForSingleObject(processInfo.hProcess, Win32.INFINITE);
if (uiResultWait == Win32.WAIT_FAILED)
{
throw new Exception("WaitForSingleObject error #" + Marshal.GetLastWin32Error());
}
GetExitCodeProcess(processInfo.hProcess, out uint ExitCode);
Console.WriteLine("Exit code: " + ExitCode);
}
finally
{
// Close all handles
Win32.CloseHandle(hToken);
Win32.CloseHandle(processInfo.hProcess);
Win32.CloseHandle(processInfo.hThread);
DestroyEnvironmentBlock(P);
}
}
else
{
throw new Exception("CreateEnvironmentBlock error #" + Marshal.GetLastWin32Error());
}
}
private static string GetDirectory()
{
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}
3. Now the CreateProcessAsUser returns a success but the process is exiting with the error code 3228369022 (C06D007E).
I monitored with ProcMon and i dont have any error with Load Image, however not all of my dlls are loaded.
source of error was
startInfo.lpDesktop = "winsta0\\default";
line, because CreateProcessAsUserW used (not visible from question code)
so must be
startInfo.lpDesktop = L"winsta0\\default";
also code contains several another errors:
we not need open winlogon.exe (or another hardcoded exe name token) instead we can open self process token and use it here, if we want system token use.
not need DuplicateTokenEx +set TokenSessionId for token returned by WTSQueryUserToken - because the WTSGetActiveConsoleSessionId() already was in this token (we get it by this session id)
ResumeThread senseless call here - because thread created suspended when and only when CREATE_SUSPENDED flag used in CreateProcess

How to fast delete many files

I have a folder in Windows Server with subfolders and ≈50000 files. When I click the right mouse button and choose delete (or shift+delete) – all files are deleted in 10-20 seconds.
When I delete files using code – 1500-4000 seconds.
Delete large number of files – don't work for me.
My code:
string folderPath = #"C://myFolder";
DirectoryInfo folderInfo = new DirectoryInfo(folderPath);
folderInfo.Delete(true); // true - recursive, with sub-folders
How to delete files faster?
A much faster way to delete files is to use the Windows functions instead of the .NET ones.
You will need to first import the function:
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DeleteFile(string lpFileName);
And then you can do this:
string[] files = Directory.EnumerateFiles(path, "*". SearchOption.AllDirectories);
foreach (string file in files)
{
DeleteFile(file);
}
Once the files are deleted, which is the slowest part by using the managed APIs, you can call Directory.DeleteFolder(path, true) to delete the empty folders.
Since the question is actually about deleting network shared folders and it's stated that the explorer based delete is much faster than the C# internal delete mechanism, it might help to just invoke a windows shell based delete.
ProcessStartInfo Info = new ProcessStartInfo();
Info.Arguments = "/C rd /s /q \"<your-path>\"";
Info.WindowStyle = ProcessWindowStyle.Hidden;
Info.CreateNoWindow = true;
Info.FileName = "cmd.exe";
Process.Start(Info);
Ofcourse, you have to replace <your-path>.
However, I don't have the infrastructure and files available to test the performance myself right now.
Not quite sure why the method DirectoryInfo.Delete() takes too much time when deleting folders that have a lot of files and sub-folders. I suspect that the method may also do quite a few things that are unnecessary.
I write a small class to to use Win API without doing too many unnecessary things to test my idea. It takes about 40 seconds to delete a folder that have 50,000 files and sub-folders. So, hope it helps.
I use this PowerScript to generate the testing files.
$folder = "d:\test1";
For ($i=0; $i -lt 50000; $i++)
{
New-Item -Path $folder -Name "test$i.txt" -ItemType "file" -Value $i.ToString();
}
The following is the code in C#.
using System;
using System.Collections.Generic;
//
using System.Runtime.InteropServices;
using System.IO;
//
namespace TestFileDelete
{
class FileDelete
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct WIN32_FIND_DATAW
{
public FileAttributes dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public UInt32 nFileSizeHigh; // DWORD
public UInt32 nFileSizeLow; // DWORD
public UInt32 dwReserved0; // DWORD
public UInt32 dwReserved1; // DWORD
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public String cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public String cAlternateFileName;
};
static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr FindFirstFileW(String lpFileName, out WIN32_FIND_DATAW lpFindFileData);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern Boolean FindNextFileW(IntPtr hFindFile, out WIN32_FIND_DATAW lpFindFileData);
[DllImport("kernel32.dll")]
private static extern Boolean FindClose(IntPtr handle);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern Boolean DeleteFileW(String lpFileName); // Deletes an existing file
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern Boolean RemoveDirectoryW(String lpPathName); // Deletes an existing empty directory
// This method check to see if the given folder is empty or not.
public static Boolean IsEmptyFolder(String folder)
{
Boolean res = true;
if (folder == null && folder.Length == 0)
{
throw new Exception(folder + "is invalid");
}
WIN32_FIND_DATAW findFileData;
String searchFiles = folder + #"\*.*";
IntPtr searchHandle = FindFirstFileW(searchFiles, out findFileData);
if (searchHandle == INVALID_HANDLE_VALUE)
{
throw new Exception("Cannot check folder " + folder);
}
do
{
if ((findFileData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
{
// found a sub folder
if (findFileData.cFileName != "." && findFileData.cFileName != "..")
{
res = false;
break;
}
} // if ((findFileData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
else
{
// found a file
res = false;
break;
}
} while (FindNextFileW(searchHandle, out findFileData));
FindClose(searchHandle);
return res;
} // public static Boolean IsEmptyFolder(String folder)
// This method deletes the given folder
public static Boolean DeleteFolder(String folder)
{
Boolean res = true;
// keep non-empty folders to delete later (after we delete everything inside)
Stack<String> nonEmptyFolder = new Stack<String>();
String currentFolder = folder;
do
{
Boolean isEmpty = false;
try
{
isEmpty = IsEmptyFolder(currentFolder);
}
catch (Exception ex)
{
// Something wrong
res = false;
break;
}
if (!isEmpty)
{
nonEmptyFolder.Push(currentFolder);
WIN32_FIND_DATAW findFileData;
IntPtr searchHandle = FindFirstFileW(currentFolder + #"\*.*", out findFileData);
if (searchHandle != INVALID_HANDLE_VALUE)
{
do
{ // for each folder, find all of its sub folders and files
String foundPath = currentFolder + #"\" + findFileData.cFileName;
if ((findFileData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
{
// found a sub folder
if (findFileData.cFileName != "." && findFileData.cFileName != "..")
{
if (IsEmptyFolder(foundPath))
{ // found an empty folder, delete it
if (!(res = RemoveDirectoryW(foundPath)))
{
Int32 error = Marshal.GetLastWin32Error();
break;
}
}
else
{ // found a non-empty folder
nonEmptyFolder.Push(foundPath);
}
} // if (findFileData.cFileName != "." && findFileData.cFileName != "..")
} // if ((findFileData.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
else
{
// found a file, delete it
if (!(res = DeleteFileW(foundPath)))
{
Int32 error = Marshal.GetLastWin32Error();
break;
}
}
} while (FindNextFileW(searchHandle, out findFileData));
FindClose(searchHandle);
} // if (searchHandle != INVALID_HANDLE_VALUE)
}// if (!IsEmptyFolder(folder))
else
{
if (!(res = RemoveDirectoryW(currentFolder)))
{
Int32 error = Marshal.GetLastWin32Error();
break;
}
}
if (nonEmptyFolder.Count > 0)
{
currentFolder = nonEmptyFolder.Pop();
}
else
{
currentFolder = null;
}
} while (currentFolder != null && res);
return res;
} // public static Boolean DeleteFolder(String folder)
};
class Program
{
static void Main(string[] args)
{
DateTime t1 = DateTime.Now;
try
{
Boolean b = FileDelete.DeleteFolder(#"d:\test1");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
DateTime t2 = DateTime.Now;
TimeSpan ts = t2 - t1;
Console.WriteLine(ts.Seconds);
}
}
}

C# programmatic install of a filter driver?

I'm currently using setup.dll to install a filter driver in a programmatic way.
the following is my code:
protected bool INFSetup(string path_to_inf, bool Install)
{
string exe = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "rundll32.exe");
if (File.Exists(exe) && File.Exists(path_to_inf))
{
try
{
Process proc = new Process();
proc.EnableRaisingEvents = true;
string FileName = exe;
string Arguments = #"SETUPAPI.DLL,InstallHinfSection " + (Install ? "DefaultInstall" : "DefaultUninstall") + " 128 " + path_to_inf;
Debug.Writeline("Executing: '" + FileName + "' with arguments: " + Arguments);
ProcessStartInfo StartInfo = new ProcessStartInfo(FileName, Arguments);
StartInfo.CreateNoWindow = true;
StartInfo.UseShellExecute = false;
proc.StartInfo = StartInfo;
if (proc.Start())
{
if (proc.WaitForExit(10000))
{
return (proc.ExitCode == 0);
}
else
{
Debug.Writeline("INFSetup: proc.WaitForExit() returned false");
}
}
else
{
Debug.Writeline("INFSetup: proc.Start() returned false");
}
}
catch (Exception e)
{
Debug.Writeline("Caught Execption while installing INF: " + e.ToString());
return false;
}
}
return false;
}
although the code works fine, I was wondering if there is a way to do the same with native Win32 calls?
it would be great if anyone has a sample c# code?
Thanks
Henry
As the rundll command line suggests InstallHinfSection is also exported from SETUPAPI.DLL and is thus p/invokable. An MSFT bod posted a p/invoke signature here:
using System.Runtime.InteropServices;
[DllImport("Setupapi.dll", EntryPoint="InstallHinfSection", CallingConvention=CallingConvention.StdCall)]
public static extern void InstallHinfSection(
[In] IntPtr hwnd,
[In] IntPtr ModuleHandle,
[In, MarshalAs(UnmanagedType.LPWStr)] string CmdLineBuffer,
int nCmdShow);
InstallHinfSection(IntPtr.Zero, IntPtr.Zero, "my path", 0);

How to find the installation directory of a third-party application, such as Google Earth, using C#?

I have the following code fragment that starts a Google Earth process using a hardcoded path:
var process =
new Process
{
StartInfo =
{
//TODO: Get location of google earth executable from registry
FileName = #"C:\Program Files\Google\Google Earth\googleearth.exe",
Arguments = "\"" + kmlPath + "\""
}
};
process.Start();
I want to programmatically fetch the installation location of googleearth.exe from somewhere (most likely the registry).
Obviously if you're opening a specific file associated with the program then launching it via the file is preferable (for instance, the user might have a program associated with the file type they prefer to use).
Here is a method I've used in the past to launch an application associated with a particular file type, but without actually opening a file. There may be a better way to do it.
static Regex pathArgumentsRegex = new Regex(#"(%\d+)|(""%\d+"")", RegexOptions.ExplicitCapture);
static string GetPathAssociatedWithFileExtension(string extension)
{
RegistryKey extensionKey = Registry.ClassesRoot.OpenSubKey(extension);
if (extensionKey != null)
{
object applicationName = extensionKey.GetValue(string.Empty);
if (applicationName != null)
{
RegistryKey commandKey = Registry.ClassesRoot.OpenSubKey(applicationName.ToString() + #"\shell\open\command");
if (commandKey != null)
{
object command = commandKey.GetValue(string.Empty);
if (command != null)
{
return pathArgumentsRegex.Replace(command.ToString(), "");
}
}
}
}
return null;
}
Sometimes though there are cases when you want to launch a specific program without opening a file. Usually (hopefully) the program has a registry entry with the install location. Here is an example of how to launch Google Earth in such a manner.
private static string GetGoogleEarthExePath()
{
RegistryKey googleEarthRK = Registry.CurrentUser.OpenSubKey(#"Software\Google\Google Earth Plus\");
if (googleEarthRK != null)
{
object rootDir = googleEarthRK.GetValue("InstallLocation");
if (rootDir != null)
{
return Path.Combine(rootDir.ToString(), "googleearth.exe");
}
}
return null;
}
From the example given you can gauge that I'm actually trying to pass a KML file to Google Earth. Because of this, the simplest way of resolving this problem is relying on the file association of KML with Google Earth and using the following as a replacement for the entire example:
Process.Start(kmlPath);
This was found by reviewing the answers to this question.
This would also work: (C# code)
Type type = Type.GetTypeFromProgID("WindowsInstaller.Installer");
Installer msi = (Installer)Activator.CreateInstance(type);
foreach (string productcode in msi.Products)
{
string productname = msi.get_ProductInfo(productcode, "InstalledProductName");
if (productname.Contains("Google Earth"))
{
string installdir = msi.get_ProductInfo(productcode, "InstallLocation");
Console.WriteLine("{0}: {1} #({2})", productcode, productname, installdir);
}
}
Here's a C++ version I just had to write. Taken directly from ICR's C# version.
void PrintString(CString string)
{
std::wcout << static_cast<LPCTSTR>(string) << endl;
}
CString GetClassesRootKeyValue(const wchar_t * keyName)
{
HKEY hkey;
TCHAR keyNameCopy[256] = {0};
_tcscpy_s(keyNameCopy, 256, keyName);
BOOL bResult = SUCCEEDED(::RegOpenKey(HKEY_CLASSES_ROOT, keyNameCopy, &hkey));
CString hkeyValue = CString("");
if (bResult) {
TCHAR temporaryValueBuffer[256];
DWORD bufferSize = sizeof (temporaryValueBuffer);
DWORD type;
bResult = SUCCEEDED(RegQueryValueEx(hkey, _T(""), NULL, &type, (BYTE*)temporaryValueBuffer, &bufferSize)) && (bufferSize > 1);
if (bResult) {
hkeyValue = CString(temporaryValueBuffer);
}
RegCloseKey(hkey);
return hkeyValue;
}
return hkeyValue;
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
_tprintf(_T("Fatal Error: MFC initialization failed\n"));
nRetCode = 1;
}
else
{
CString dwgAppName = GetClassesRootKeyValue(_T(".dwg"));
PrintString(dwgAppName);
dwgAppName.Append(_T("\\shell\\open\\command"));
PrintString(dwgAppName);
CString trueViewOpenCommand = GetClassesRootKeyValue(static_cast<LPCTSTR>(dwgAppName));
PrintString(trueViewOpenCommand);
// Shell open command usually ends with a "%1" for commandline params. We don't want that,
// so strip it off.
int firstParameterIndex = trueViewOpenCommand.Find(_T("%"));
PrintString(trueViewOpenCommand.Left(firstParameterIndex).TrimRight('"').TrimRight(' '));
cout << "\n\nPress <enter> to exit...";
getchar();
}
}

Categories

Resources