Open file:// URI to specific # (hash) ID - c#

I'd like to open a local HTML file navigated to a specific headline with an ID.
string url = "file:///C:/myFile.html#myHeading"
If I paste the URL in the browser directly, it correctly navigates to the desired heading.
But when I try to have the system handle the URL, it omits the hash entirely.
ProcessStartInfo si = new ProcessStartInfo(url);
si.UseShellExecute = true;
Process.Start(si);
will simply open file:///C:/myFile.html.
Is there a way I can make this work?
Sadly, I don't know where this stripping happens, so my research so far turned up empty.
I tried replacing the hash symbol with its escaped form %23, which of course didn't work (could not find the file specified).
A work-around I'm using in the meantime:
From here, I wrote an extension which tells me the path to the executable set as the default app for opening files with a given extension.
public static string getDefaultAssociation(string extension)
{
if (extension.Substring(0, 1) != ".")
{
int os = extension.IndexOf('.');
if (os > 0)
{
extension = extension.Substring(os);
}
else
{
extension = "." + extension;
}
}
const int S_OK = 0;
const int S_FALSE = 1;
uint length = 0;
uint ret = AssocQueryString(AssocF.None, AssocStr.Executable, extension, null, null, ref length);
if (ret != S_FALSE)
{
//throw new InvalidOperationException("Could not determine associated string");
WriteLog("Could not determine associated string");
return "";
}
var sb = new StringBuilder((int)length); // (length-1) will probably work too as the marshaller adds null termination
ret = AssocQueryString(AssocF.None, AssocStr.Executable, extension, null, sb, ref length);
if (ret != S_OK)
{
//throw new InvalidOperationException("Could not determine associated string");
WriteLog("Could not determine associated string");
return "";
}
return sb.ToString();
}
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern uint AssocQueryString(
AssocF flags,
AssocStr str,
string pszAssoc,
string pszExtra,
[Out] StringBuilder pszOut,
ref uint pcchOut
);
[Flags]
public enum AssocF
{
None = 0,
Init_NoRemapCLSID = 0x1,
Init_ByExeName = 0x2,
Open_ByExeName = 0x2,
Init_DefaultToStar = 0x4,
Init_DefaultToFolder = 0x8,
NoUserSettings = 0x10,
NoTruncate = 0x20,
Verify = 0x40,
RemapRunDll = 0x80,
NoFixUps = 0x100,
IgnoreBaseClass = 0x200,
Init_IgnoreUnknown = 0x400,
Init_Fixed_ProgId = 0x800,
Is_Protocol = 0x1000,
Init_For_File = 0x2000
}
public enum AssocStr
{
Command = 1,
Executable,
FriendlyDocName,
FriendlyAppName,
NoOpen,
ShellNewValue,
DDECommand,
DDEIfExec,
DDEApplication,
DDETopic,
InfoTip,
QuickTip,
TileInfo,
ContentType,
DefaultIcon,
ShellExtension,
DropTarget,
DelegateExecute,
Supported_Uri_Protocols,
ProgID,
AppID,
AppPublisher,
AppIconReference,
Max
}
So I can use
string def = getDefaultAssociation("html");
ProcessStartInfo si = new ProcessStartInfo(def);
si.Arguments = url;
si.UseShellExecute = true;
Process.Start(si);
to directly tell my default browser to go to the file URL, INCLUDING the ID hash.

File scheme (file://) just doesn't support anchors or actually any parameters, according to RFC. So query string, anchors or anything else besides path to file can be ignored by whatever processes url with this scheme, which is what happens here. If you try file:///C:/myFile.html?x=1 - query string will also be stripped. If on the other hand you'll try http://google.com?x=1#test then it will open exactly this url in browser, because other scheme.
So I'd say it works as expected and your workaround is fine.

Related

QueryFullProcessImageName returns error 31

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.

Windows 10 gives error when calling ShellExecute with verb that is registered

We have run into a strange problem and it seems we are not the only ones (see note at bottom).
The problem is that we want to call ShellExecute with the shell verb printto. We are checking if it is registered and if so, start a process. The registered verbs can be retrieved in ProcessStartInfo.Verbs.
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = #"C:\test.jpg";
startInfo.Verb = "printto";
startInfo.Arguments = "MyPrinter";
startInfo.UseShellExecute = true;
if (!startInfo.Verbs.Contains("printto"))
throw new Exception("PrintTo is not supported!");
try
{
Process.Start(startInfo);
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 1155)
{
Console.WriteLine("Somehow printto is NOT registered...");
}
When running Windows 10 with the Photos UWP app as default viewer, the console will print that a Win32Exception with code 1155 was raised, which means that the file type is not registered (for the given verb). If the (old) windows picture viewer is default, this works.
Please also note, that we are checking if the verb is registered and only call this if it is. It seems that Microsoft does something different here.
The big question is: Why do these two MS APIs do not play together anymore and how can we circumvent that?
Note:
There is an old discussion with an answer that is not particularly correct, but also a slightly different problem description:
Windows 8 blows error on c# process for printing pdf file, how?
I therefore decided to start a new question and hope that it is aligned with the SO principles.
The ProcessStartInfo.Verbs property is somewhat broken as it does not consider the way how newer versions of Windows (Windows 8 and above afaik) retrieve the registered application. The property only checks the verbs that are registered for the ProgId defined under HKCR\.ext (as can be seen in the reference source) and does not consider other places such as below the Registry key HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ext or some other places, e.g. defined via Policy.
Getting the registered verbs
The best way is to not rely on checking the Registry directly (as done by the ProcessStartInfo class), but to use the appropriate Windows API function AssocQueryString to retrieve the associated ProgId:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32;
class Program
{
private static void Main(string[] args)
{
string fileName = #"E:\Pictures\Sample.jpg";
string progId = AssocQueryString(AssocStr.ASSOCSTR_PROGID, fileName);
var verbs = GetVerbsByProgId(progId);
if (!verbs.Contains("printto"))
{
throw new Exception("PrintTo is not supported!");
}
}
private static string[] GetVerbsByProgId(string progId)
{
var verbs = new List<string>();
if (!string.IsNullOrEmpty(progId))
{
using (var key = Registry.ClassesRoot.OpenSubKey(progId + "\\shell"))
{
if (key != null)
{
var names = key.GetSubKeyNames();
verbs.AddRange(
names.Where(
name =>
string.Compare(
name,
"new",
StringComparison.OrdinalIgnoreCase)
!= 0));
}
}
}
return verbs.ToArray();
}
private static string AssocQueryString(AssocStr association, string extension)
{
uint length = 0;
uint ret = AssocQueryString(
AssocF.ASSOCF_NONE, association, extension, "printto", null, ref length);
if (ret != 1) //expected S_FALSE
{
throw new Win32Exception();
}
var sb = new StringBuilder((int)length);
ret = AssocQueryString(
AssocF.ASSOCF_NONE, association, extension, null, sb, ref length);
if (ret != 0) //expected S_OK
{
throw new Win32Exception();
}
return sb.ToString();
}
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern uint AssocQueryString(
AssocF flags,
AssocStr str,
string pszAssoc,
string pszExtra,
[Out] StringBuilder pszOut,
ref uint pcchOut);
[Flags]
private enum AssocF : uint
{
ASSOCF_NONE = 0x00000000,
ASSOCF_INIT_NOREMAPCLSID = 0x00000001,
ASSOCF_INIT_BYEXENAME = 0x00000002,
ASSOCF_OPEN_BYEXENAME = 0x00000002,
ASSOCF_INIT_DEFAULTTOSTAR = 0x00000004,
ASSOCF_INIT_DEFAULTTOFOLDER = 0x00000008,
ASSOCF_NOUSERSETTINGS = 0x00000010,
ASSOCF_NOTRUNCATE = 0x00000020,
ASSOCF_VERIFY = 0x00000040,
ASSOCF_REMAPRUNDLL = 0x00000080,
ASSOCF_NOFIXUPS = 0x00000100,
ASSOCF_IGNOREBASECLASS = 0x00000200,
ASSOCF_INIT_IGNOREUNKNOWN = 0x00000400,
ASSOCF_INIT_FIXED_PROGID = 0x00000800,
ASSOCF_IS_PROTOCOL = 0x00001000,
ASSOCF_INIT_FOR_FILE = 0x00002000
}
private enum AssocStr
{
ASSOCSTR_COMMAND = 1,
ASSOCSTR_EXECUTABLE,
ASSOCSTR_FRIENDLYDOCNAME,
ASSOCSTR_FRIENDLYAPPNAME,
ASSOCSTR_NOOPEN,
ASSOCSTR_SHELLNEWVALUE,
ASSOCSTR_DDECOMMAND,
ASSOCSTR_DDEIFEXEC,
ASSOCSTR_DDEAPPLICATION,
ASSOCSTR_DDETOPIC,
ASSOCSTR_INFOTIP,
ASSOCSTR_QUICKTIP,
ASSOCSTR_TILEINFO,
ASSOCSTR_CONTENTTYPE,
ASSOCSTR_DEFAULTICON,
ASSOCSTR_SHELLEXTENSION,
ASSOCSTR_DROPTARGET,
ASSOCSTR_DELEGATEEXECUTE,
ASSOCSTR_SUPPORTED_URI_PROTOCOLS,
ASSOCSTR_PROGID,
ASSOCSTR_APPID,
ASSOCSTR_APPPUBLISHER,
ASSOCSTR_APPICONREFERENCE,
ASSOCSTR_MAX
}
}
Actually printing images
However, this does not solve your actual problem, i.e. printing an image on Windows 10. If your requirement is to just get the image printed out you can do so using the PrintDocument class from the System.Drawing.Printing namespace as described in this related post: Print images in C#:
PrintDocument pd = new PrintDocument();
pd.PrintPage += PrintPage;
pd.Print();
private void PrintPage(object o, PrintPageEventArgs e)
{
System.Drawing.Image img = System.Drawing.Image.FromFile("D:\\Foto.jpg");
Point loc = new Point(100, 100);
e.Graphics.DrawImage(img, loc);
}

Calculate relative Filepath [duplicate]

This question already has answers here:
How to get relative path from absolute path
(24 answers)
Closed 7 years ago.
I have 2 Files:
C:\Program Files\MyApp\images\image.png
C:\Users\Steve\media.jpg
Now i want to calculate the File-Path of File 2 (media.jpg) relative to File 1:
..\..\..\Users\Steve\
Is there a built-in function in .NET to do this?
Use:
var s1 = #"C:\Users\Steve\media.jpg";
var s2 = #"C:\Program Files\MyApp\images\image.png";
var uri = new Uri(s2);
var result = uri.MakeRelativeUri(new Uri(s1)).ToString();
There is no built-in .NET, but there is native function. Use it like this:
[DllImport("shlwapi.dll", CharSet=CharSet.Auto)]
static extern bool PathRelativePathTo(
[Out] StringBuilder pszPath,
[In] string pszFrom,
[In] FileAttributes dwAttrFrom,
[In] string pszTo,
[In] FileAttributes dwAttrTo
);
Or if you still prefer managed code then try this:
public static string GetRelativePath(FileSystemInfo path1, FileSystemInfo path2)
{
if (path1 == null) throw new ArgumentNullException("path1");
if (path2 == null) throw new ArgumentNullException("path2");
Func<FileSystemInfo, string> getFullName = delegate(FileSystemInfo path)
{
string fullName = path.FullName;
if (path is DirectoryInfo)
{
if (fullName[fullName.Length - 1] != System.IO.Path.DirectorySeparatorChar)
{
fullName += System.IO.Path.DirectorySeparatorChar;
}
}
return fullName;
};
string path1FullName = getFullName(path1);
string path2FullName = getFullName(path2);
Uri uri1 = new Uri(path1FullName);
Uri uri2 = new Uri(path2FullName);
Uri relativeUri = uri1.MakeRelativeUri(uri2);
return relativeUri.OriginalString;
}

Does Process.StartInfo.FileName accept long file names?

Looks like it's not.
If I convert the file name to its short value, then Process.Start() works.
Process runScripts = new Process();
runScripts.StartInfo.FileName = #"C:\long file path\run.cmd";
runScripts.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
runScripts.StartInfo.UseShellExecute = true;
runScripts.StartInfo.RedirectStandardOutput = false;
runScripts.Start();
The above code fails. But...
Process runScripts = new Process();
runScripts.StartInfo.FileName = #"C:\short\file\path\run.cmd";
runScripts.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
runScripts.StartInfo.UseShellExecute = true;
runScripts.StartInfo.RedirectStandardOutput = false;
runScripts.Start();
succeeds.
I managed to get around this by converting the long path name to a short path name.
But I am a bit surprised to find this.
Any reasons or background info on this?
Thanks.
Update 1
Microsoft .NET Framework Version 2.0.50727
It's curious, i reproduce the behavior, effectively, it doesn't execute as you stated, but modifying this, it worked:
System.Diagnostics.Process runScripts = new System.Diagnostics.Process();
runScripts.StartInfo.FileName = #"run.cmd";
// new
runScripts.StartInfo.WorkingDirectory = #"C:\long file path";
runScripts.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
runScripts.StartInfo.UseShellExecute = true;
runScripts.StartInfo.RedirectStandardOutput = false;
runScripts.Start();
I am unable to reproduce the behavior you're describing. The following code works for me.
string path = #"X:\Temp\long file path\run.cmd";
Process p = new Process();
p.StartInfo.FileName = path;
// Works with both true (default) and false.
p.StartInfo.UseShellExecute = true;
p.Start();
edit
I am able to reproduce with the following code
string path = #"X:\Temp\long file path";
string file = "run.cmd";
Process p = new Process();
p.StartInfo.FileName = file;
p.StartInfo.WorkingDirectory = path;
p.StartInfo.UseShellExecute = false; // only fails with false.
p.Start();
return;
This show (with Process Monitor) that ConsoleApplication.vshost.exe tries to find run.cmd in project\bin\Release, System32, System, Windows, System32\Wbem, and then further into some (I guess) path variables. It works, however, if I set UseShellExecute = true.
To reproduce your problem, I used the following program:
// file test.cs
using System;
using System.ComponentModel;
using System.Diagnostics;
public class Test
{
public static int Main()
{
string error;
try {
ProcessStartInfo i = new ProcessStartInfo();
i.FileName = #"C:\long file path\run.cmd";
i.WindowStyle = ProcessWindowStyle.Hidden;
i.UseShellExecute = true;
i.RedirectStandardOutput = false;
using (Process p = Process.Start(i)) {
error = "No process object was returned from Process.Start";
if (p != null) {
p.WaitForExit();
if (p.ExitCode == 0) {
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("OK");
Console.ResetColor();
return 0;
}
error = "Process exit code was " + p.ExitCode;
}
}
}
catch (Win32Exception ex) {
error = "(Win32Exception) " + ex.Message;
}
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Whooops: " + error);
Console.ResetColor();
return 1;
}
}
The code starts a new process (as per your code sample) and reports the different ways in which it may fail to execute. You can compile the program from command line:
c:\windows\Microsoft.NET\Framework\v2.0.50727\csc test.cs
(assuming test.cs is in the current directory; it will create test.exe in the same directory as test.cs)
As expected, when "C:\long file path\run.cmd" does not exist, the program fails with:
Whooops: (Win32Exception) The system cannot find the file specified
Now let's create a directory "C:\long file path" and put a very simple run.cmd in there:
rem file run.cmd
echo I ran at %Time% > "%~dp0\run.out.txt"
However, at this point I was unable to reproduce your failure. Once the above run.cmd is in place, test.exe runs successfully (i.e. run.cmd is executed correctly - you can verify this by looking for a newly created file "C:\long file path\run.out.txt".
I ran test.exe on Vista x64 (my main dev machine), Windows XP SP3 x86 (virtual machine), Windows Server 2008 x64 (virtual machine) and it works everywhere.
Could you try running the above code in your environment and report back whether it is failing for you? This way we will at least establish the same testing context (same .NET program will be trying to run the same batch file in the same location for you as it does for me).
I managed to actually run it by using following behavior.
private string StartProcessAndGetResult(string executableFile, string arguments)
//NOTE executable file should be passed as string with a full path
//For example: C:\Windows\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\ServiceModelReg.exe
//Without """ and # signs
{
var result = String.Empty;
var workingDirectory = Path.GetDirectoryName(executableFile);
var processStartInfo = new ProcessStartInfo(executableFile, arguments)
{
WorkingDirectory = workingDirectory,
UseShellExecute = false,
ErrorDialog = false,
CreateNoWindow = true,
RedirectStandardOutput = true
};
var process = Process.Start(processStartInfo);
if (process != null)
{
using (var streamReader = process.StandardOutput)
{
result = streamReader.ReadToEnd();
}
}
return result;
}
The solution with # or taking the string to quotes (""") did not wokred in my case.
Try
runScripts.StartInfo.FileName = #"""C:\long file path\run.cmd""";
Although I was sure it was done automatically for you by Process class. Are you sure you supplying the correct path?
I have a hypothesis.
Win32 has a limitation that strings used to descibe filenames cannot be longer then MAX_PATH (260 bytes) including terminating '\0'.
Maybe that problem has leaked into C#?
(The tests I've done indicates that, but I cannot confirm which bugs me.)
So, try to prefix your path with "\\?\". (backslash, backslash, questionmark, backslash)
I.e.
runScripts.StartInfo.FileName = #"\\?\C:\long file path\run.cmd";
For more details on MAX_PATH: http://msdn.microsoft.com/en-us/library/aa365247.aspx
/Leif
(Update)
I think I found the problem from reverse engineering the Process.Start method a bit more. I was along the right lines, it fails without having a WorkingDirectory set.
So in other words CreateProcess needs a valid workingdirectory, not .NET (it should really deduce it from the filename but obviously doesn't)
Here's the proof:
namespace SoTest
{
class Program
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreateProcess([MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, StringBuilder lpCommandLine, SECURITY_ATTRIBUTES lpProcessAttributes, SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, STARTUPINFO lpStartupInfo, PROCESS_INFORMATION lpProcessInformation);
static void Main(string[] args)
{
Process process = new Process();
process.StartInfo.FileName = #"C:\my test folder\my test.bat";
StringBuilder cmdLine = new StringBuilder();
cmdLine.Append(process.StartInfo.FileName);
STARTUPINFO lpStartupInfo = new STARTUPINFO();
PROCESS_INFORMATION lpProcessInformation = new PROCESS_INFORMATION();
// This fails
//string workingDirectory = process.StartInfo.WorkingDirectory;
string workingDirectory = #"C:\my test folder\";
CreateProcess(null, cmdLine, null, null, true, 0, IntPtr.Zero, workingDirectory, lpStartupInfo, lpProcessInformation);
}
}
[StructLayout(LayoutKind.Sequential)]
internal class PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
public PROCESS_INFORMATION()
{
this.hProcess = IntPtr.Zero;
this.hThread = IntPtr.Zero;
}
}
[StructLayout(LayoutKind.Sequential)]
internal class SECURITY_ATTRIBUTES
{
public int nLength;
public long lpSecurityDescriptor;
public bool bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
internal class STARTUPINFO
{
public int cb;
public IntPtr lpReserved;
public IntPtr lpDesktop;
public IntPtr lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
public STARTUPINFO()
{
this.lpReserved = IntPtr.Zero;
this.lpDesktop = IntPtr.Zero;
this.lpTitle = IntPtr.Zero;
this.lpReserved2 = IntPtr.Zero;
this.hStdInput = IntPtr.Zero;
this.hStdOutput = IntPtr.Zero;
this.hStdError = IntPtr.Zero;
this.cb = Marshal.SizeOf(this);
}
}
}
I've removed SafeFileHandle from the original code as it wasn't needed for what we're doing. Also no start flags were set, but these are needed for a windowless version.
Try this:
Process runScripts = new Process();
runScripts.StartInfo.FileName = #"""C:\long file path\run.cmd""";
runScripts.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
runScripts.StartInfo.UseShellExecute = true;
runScripts.StartInfo.RedirectStandardOutput = false;
runScripts.Start();
I.e. use a quoted string for FileName when FileName has spaces.
Your code can fail for various reasons which are unrelated to the long/short path issue. You should add the exact exception description (including the call stack) to your question.

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