Is there a way to execute the command for a progid verb without having to go digging in the registry and doing string manipulation?
Using ShObjIdl.idl I can run the following command to get the ProgId for the default browser:
var reg = new ShellObjects.ApplicationAssociationRegistration();
string progID;
reg.QueryCurrentDefault("http", ShellObjects.ASSOCIATIONTYPE.AT_URLPROTOCOL, ShellObjects.ASSOCIATIONLEVEL.AL_EFFECTIVE, out progID);
This gives me "ChromeHTML.FHXQEQDDJYXVQSFWM2SVMV5GNA". In the registry I can see this progid has the following shell/open/command:
"C:\Users\Paul\AppData\Local\Google\Chrome\Application\chrome.exe" -- "%1"
Is there an API that I can pass the ProgId to, along with the verb and argument and it will run it?
One route I went down is using ShellExecuteEx:
var shellExecuteInfo = new SHELLEXECUTEINFO();
shellExecuteInfo.cbSize = Marshal.SizeOf(shellExecuteInfo);
shellExecuteInfo.fMask = SEE_MASK_CLASSNAME;
shellExecuteInfo.hwnd = IntPtr.Zero;
shellExecuteInfo.lpVerb = "open";
shellExecuteInfo.lpFile = "google.com";
shellExecuteInfo.nShow = SW_SHOWNORMAL;
shellExecuteInfo.lpClass = "http";
ShellExecuteEx(ref shellExecuteInfo);
However this fails with a 'Windows cannot find' error due to Window's doing checking on lpFile which I don't want to happen as it isn't relevant for a URL (from: http://blogs.msdn.com/b/oldnewthing/archive/2010/07/01/10033224.aspx )
This is the solution I have come up with:
private static void Main(string[] args)
{
if (!OpenUrlInDefaultBrowser("google.com"))
Console.WriteLine("An error happened");
}
[DllImport("Shlwapi.dll")]
private static extern int AssocQueryString(ASSOCF flags, ASSOCSTR str, string pszAssoc, string pszExtra, StringBuilder pszOut, ref uint pcchOut);
private enum ASSOCF
{
ASSOCF_NONE = 0x00000000
}
private enum ASSOCSTR
{
ASSOCSTR_COMMAND = 1
}
[DllImport("Shell32.dll", CharSet=CharSet.Auto)]
private static extern int SHEvaluateSystemCommandTemplate(string pszCmdTemplate, out string ppszApplication, out string ppszCommandLine, out string ppszParameters);
private static bool OpenUrlInDefaultBrowser(string url)
{
string browserProgId;
if (!GetDefaultBrowserProgId(out browserProgId))
return false;
string browserCommandTemplate;
if (!GetCommandTemplate(browserProgId, out browserCommandTemplate))
return false;
string browserExecutable;
string parameters;
if (!EvaluateCommandTemplate(browserCommandTemplate, out browserExecutable, out parameters))
return false;
parameters = ReplaceSubstitutionParameters(parameters, url);
try
{
Process.Start(browserExecutable, parameters);
}
catch (InvalidOperationException) { return false; }
catch (Win32Exception) { return false; }
catch (FileNotFoundException) { return false; }
return true;
}
private static bool GetDefaultBrowserProgId(out string defaultBrowserProgId)
{
try
{
// midl "C:\Program Files (x86)\Windows Kits\8.0\Include\um\ShObjIdl.idl"
// tlbimp ShObjIdl.tlb
var applicationAssociationRegistration = new ApplicationAssociationRegistration();
applicationAssociationRegistration.QueryCurrentDefault("http", ShellObjects.ASSOCIATIONTYPE.AT_URLPROTOCOL, ShellObjects.ASSOCIATIONLEVEL.AL_EFFECTIVE, out defaultBrowserProgId);
}
catch (COMException)
{
defaultBrowserProgId = null;
return false;
}
return !string.IsNullOrEmpty(defaultBrowserProgId);
}
private static bool GetCommandTemplate(string defaultBrowserProgId, out string commandTemplate)
{
var commandTemplateBufferSize = 0U;
AssocQueryString(ASSOCF.ASSOCF_NONE, ASSOCSTR.ASSOCSTR_COMMAND, defaultBrowserProgId, "open", null, ref commandTemplateBufferSize);
var commandTemplateStringBuilder = new StringBuilder((int)commandTemplateBufferSize);
var hresult = AssocQueryString(ASSOCF.ASSOCF_NONE, ASSOCSTR.ASSOCSTR_COMMAND, defaultBrowserProgId, "open", commandTemplateStringBuilder, ref commandTemplateBufferSize);
commandTemplate = commandTemplateStringBuilder.ToString();
return hresult == 0 && !string.IsNullOrEmpty(commandTemplate);
}
private static bool EvaluateCommandTemplate(string commandTemplate, out string application, out string parameters)
{
string commandLine;
var hresult = SHEvaluateSystemCommandTemplate(commandTemplate, out application, out commandLine, out parameters);
return hresult == 0 && !string.IsNullOrEmpty(application) && !string.IsNullOrEmpty(parameters);
}
private static string ReplaceSubstitutionParameters(string parameters, string replacement)
{
// Not perfect but good enough for this purpose
return parameters.Replace("%L", replacement)
.Replace("%l", replacement)
.Replace("%1", replacement);
}
The explicit class does not remove the requirement that lpFile refer to a valid resource (file or URL). The class specifies how the resource should be executed (rather than inferring the class from the file type or URL protocol), but you still have to pass a valid resource. google.com is treated as a file name since it is not a URL, and the file does not exist, so you get the "not found" error.
The general case of what you're trying to do is more complicated than just extracting a command line, because most browsers use DDE rather than command lines as their primary invoke. (The command line is a fallback when DDE fails.)
But if you really want to execute a command line, you can use AssocQueryString to get the ASSOCSTR_COMMAND, and then perform the insertion via SHEvaluateSystemCommandTemplate to get the command line to execute.
The fundamental error here is that you omitted http:// from the FileName. Add that and all will be well.
shellExecuteInfo.lpFile = "http://google.com";
You don't need to set lpClass at all. The fact that lpFile begins with http:// determines the class.
Rather than calling ShellExecuteEx yourself, you may as well the Process to do it for you:
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = #"http://google.com";
psi.UseShellExecute = true;
Process.Start(psi);
Or even:
Process.Start(#"http://google.com");
My code that include check to prevent from some common errors... Hope it helps :-)
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace HQ.Util.Unmanaged
{
/// <summary>
/// Usage: string executablePath = FileAssociation.GetExecFileAssociatedToExtension(pathExtension, "open");
/// Usage: string command FileAssociation.GetExecCommandAssociatedToExtension(pathExtension, "open");
/// </summary>
public static class FileAssociation
{
/// <summary>
///
/// </summary>
/// <param name="ext"></param>
/// <param name="verb"></param>
/// <returns>Return null if not found</returns>
public static string GetExecCommandAssociatedToExtension(string ext, string verb = null)
{
if (ext[0] != '.')
{
ext = "." + ext;
}
string executablePath = FileExtentionInfo(AssocStr.Command, ext, verb);
// Ensure to not return the default OpenWith.exe associated executable in Windows 8 or higher
if (!string.IsNullOrEmpty(executablePath) && File.Exists(executablePath) &&
!executablePath.ToLower().EndsWith(".dll"))
{
if (executablePath.ToLower().EndsWith("openwith.exe"))
{
return null; // 'OpenWith.exe' is th windows 8 or higher default for unknown extensions. I don't want to have it as associted file
}
return executablePath;
}
return executablePath;
}
/// <summary>
///
/// </summary>
/// <param name="ext"></param>
/// <param name="verb"></param>
/// <returns>Return null if not found</returns>
public static string GetExecFileAssociatedToExtension(string ext, string verb = null)
{
if (ext[0] != '.')
{
ext = "." + ext;
}
string executablePath = FileExtentionInfo(AssocStr.Executable, ext, verb); // Will only work for 'open' verb
if (string.IsNullOrEmpty(executablePath))
{
executablePath = FileExtentionInfo(AssocStr.Command, ext, verb); // required to find command of any other verb than 'open'
// Extract only the path
if (!string.IsNullOrEmpty(executablePath) && executablePath.Length > 1)
{
if (executablePath[0] == '"')
{
executablePath = executablePath.Split('\"')[1];
}
else if (executablePath[0] == '\'')
{
executablePath = executablePath.Split('\'')[1];
}
}
}
// Ensure to not return the default OpenWith.exe associated executable in Windows 8 or higher
if (!string.IsNullOrEmpty(executablePath) && File.Exists(executablePath) &&
!executablePath.ToLower().EndsWith(".dll"))
{
if (executablePath.ToLower().EndsWith("openwith.exe"))
{
return null; // 'OpenWith.exe' is th windows 8 or higher default for unknown extensions. I don't want to have it as associted file
}
return executablePath;
}
return executablePath;
}
[DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint AssocQueryString(AssocF flags, AssocStr str, string pszAssoc, string pszExtra, [Out] StringBuilder pszOut, [In][Out] ref uint pcchOut);
private static string FileExtentionInfo(AssocStr assocStr, string doctype, string verb)
{
uint pcchOut = 0;
AssocQueryString(AssocF.Verify, assocStr, doctype, verb, null, ref pcchOut);
Debug.Assert(pcchOut != 0);
if (pcchOut == 0)
{
return "";
}
StringBuilder pszOut = new StringBuilder((int)pcchOut);
AssocQueryString(AssocF.Verify, assocStr, doctype, verb, pszOut, ref pcchOut);
return pszOut.ToString();
}
[Flags]
public enum AssocF
{
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
}
public enum AssocStr
{
Command = 1,
Executable,
FriendlyDocName,
FriendlyAppName,
NoOpen,
ShellNewValue,
DDECommand,
DDEIfExec,
DDEApplication,
DDETopic
}
}
}
Related
I am trying to open and print files with the ProcessStartInfo class. (File can be anything but let`s assume it is a PDF File)
ProcessStartInfo pi = new ProcessStartInfo(file);
pi.Arguments = Path.GetFileName(file);
pi.WorkingDirectory = Path.GetDirectoryName(file);
pi.Verb = "OPEN";
Process.Start(pi);
this works well for the pi.Verb = "OPEN";. Some applications register themselves also with the verb "PRINT" but only some do. In my case (Windows PDF Viewer) I get an exception when trying to execute with the pi.Verb = "PRINT";
Is there a way to see all the verbs available for a specific type in C# at runtime?
Thx a lot
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
}
}
From MSDN: https://msdn.microsoft.com/en-us/library/65kczb9y(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-2
startInfo = new ProcessStartInfo(fileName);
if (File.Exists(fileName))
{
i = 0;
foreach (String verb in startInfo.Verbs)
{
// Display the possible verbs.
Console.WriteLine(" {0}. {1}", i.ToString(), verb);
i++;
}
}
I have a requirement witch is to execute a vbscript located in a shared network drive. ie: \SERVER1\shared$\path\script.vbs
To connect to this shared folder I need to pass credentials ie:
DOMAIN\AdminShare
1234
This script has to run with local admin credentials. ie:
.\Administrator
1234
The user witch will execute the exe also has it's own credentials, ie:
DOMAIN\User
1234
How can I manage this scenario?
I've successfully connected to the smb with proper credentials with this class:
using System;
using System.Runtime.InteropServices;
using BOOL = System.Boolean;
using DWORD = System.UInt32;
using LPWSTR = System.String;
using NET_API_STATUS = System.UInt32;
namespace blah
{
class UNCAccess
{
// FROM: https://ericwijaya.wordpress.com/2013/02/06/access-remote-file-share-with-username-and-password-in-c/
//
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct USE_INFO_2
{
internal LPWSTR ui2_local;
internal LPWSTR ui2_remote;
internal LPWSTR ui2_password;
internal DWORD ui2_status;
internal DWORD ui2_asg_type;
internal DWORD ui2_refcount;
internal DWORD ui2_usecount;
internal LPWSTR ui2_username;
internal LPWSTR ui2_domainname;
}
[DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern NET_API_STATUS NetUseAdd(
LPWSTR UncServerName,
DWORD Level,
ref USE_INFO_2 Buf,
out DWORD ParmError);
[DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern NET_API_STATUS NetUseDel(
LPWSTR UncServerName,
LPWSTR UseName,
DWORD ForceCond);
private string sUNCPath;
private string sUser;
private string sPassword;
private string sDomain;
private int iLastError;
public UNCAccess()
{
}
public UNCAccess(string UNCPath, string User, string Domain, string Password)
{
login(UNCPath, User, Domain, Password);
}
public int LastError
{
get { return iLastError; }
}
/// <summary>
/// Logs in to the shared network with the provided credentials
/// </summary>
/// <param name="UNCPath">unc</param>
/// <param name="User">user</param>
/// <param name="Domain">domain</param>
/// <param name="Password">password</param>
/// <returns>TRUE OK, ELSE FALSE</returns>
public bool login(string UNCPath, string User, string Domain, string Password)
{
sUNCPath = UNCPath;
sUser = User;
sPassword = Password;
sDomain = Domain;
return NetUseWithCredentials();
}
private bool NetUseWithCredentials()
{
uint returncode;
try
{
USE_INFO_2 useinfo = new USE_INFO_2();
useinfo.ui2_remote = sUNCPath;
useinfo.ui2_username = sUser;
useinfo.ui2_domainname = sDomain;
useinfo.ui2_password = sPassword;
useinfo.ui2_asg_type = 0;
useinfo.ui2_usecount = 1;
uint paramErrorIndex;
returncode = NetUseAdd(null, 2, ref useinfo, out paramErrorIndex);
iLastError = (int)returncode;
return returncode == 0;
}
catch
{
iLastError = Marshal.GetLastWin32Error();
return false;
}
}
///
/// Closes the UNC share
///
/// True if closing was successful
public bool NetUseDelete()
{
uint returncode;
try
{
returncode = NetUseDel(null, sUNCPath, 2);
iLastError = (int)returncode;
return (returncode == 0);
}
catch
{
iLastError = Marshal.GetLastWin32Error();
return false;
}
}
}
}
Then I did a process start to run the script as admin:
Process p = new Process();
p.StartInfo.FileName = "cscript.exe";
p.StartInfo.WorkingDirectory = #"c:\";
p.StartInfo.Arguments = "//B //Nologo " + script.FullName
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.Verb = "runas";
p.StartInfo.UserName = localAdminAccount;
System.Security.SecureString pwd = new System.Security.SecureString();
foreach (char c in localAdminPasswd) { pwd.AppendChar(c); }
p.StartInfo.Password = pwd;
The problem is that when the process starts with the new credentials (localAdmin) I'm not able to find the script (the user has changed so no access to the shared network).
I though it was because of the user, so I've also tried to create a launcher to elevate the privileges of the execution of the main application without user interaction (another process.start from the launcher), which works fine, but then the same thing happens (not found).
Any help on this? Thanks
I've solved it with two launchers to get the privileges and a runas in the process start, so now i can run the script with the necesary credentials =)
Is there a way to have a case sensitive Directory.Exists / File.Existssince
Directory.Exists(folderPath)
and
Directory.Exists(folderPath.ToLower())
both return true?
Most of the time it doesn't matter but I'm using a macro which seems not to work if the path doesn't match cases 100%.
Since Directory.Exists uses FindFirstFile which is not case-sensitive, no. But you can PInvoke FindFirstFileEx with an additionalFlags parameter set to FIND_FIRST_EX_CASE_SENSITIVE
Try this function:
public static bool FileExistsCaseSensitive(string filename)
{
string name = Path.GetDirectoryName(filename);
return name != null
&& Array.Exists(Directory.GetFiles(name), s => s == Path.GetFullPath(filename));
}
Update:
As stated in comments, this only check cases in filename, not in the path. This is because GetFullPath method doesn't return the Windows original path with original cases, but a copy of the path from the parameter.
Ex:
GetFullPath("c:\TEST\file.txt") -> "c:\TEST\file.txt"
GetFullPath("c:\test\file.txt") -> "c:\test\file.txt"
All methods I tried work the same way: Fileinfo, DirectoryInfo.
Here is a solution using a kernel32.dll method:
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern int GetLongPathName(
string path,
StringBuilder longPath,
int longPathLength
);
/// <summary>
/// Return true if file exists. Non case sensitive by default.
/// </summary>
/// <param name="filename"></param>
/// <param name="caseSensitive"></param>
/// <returns></returns>
public static bool FileExists(string filename, bool caseSensitive = false)
{
if (!File.Exists(filename))
{
return false;
}
if (!caseSensitive)
{
return true;
}
//check case
StringBuilder longPath = new StringBuilder(255);
GetLongPathName(Path.GetFullPath(filename), longPath, longPath.Capacity);
string realPath = Path.GetDirectoryName(longPath.ToString());
return Array.Exists(Directory.GetFiles(realPath), s => s == filename);
}
Based on the solution of this question, I wrote the code below which is case sensitive for the whole path except the Windows Drive letter:
static void Main(string[] args)
{
string file1 = #"D:\tESt\Test.txt";
string file2 = #"d:\Test\test.txt";
string file3 = #"d:\test\notexists.txt";
bool exists1 = Case_Sensitive_File_Exists(file1);
bool exists2 = Case_Sensitive_File_Exists(file2);
bool exists3 = Case_Sensitive_File_Exists(file3);
Console.WriteLine("\n\nPress any key...");
Console.ReadKey();
}
static bool Case_Sensitive_File_Exists(string filepath)
{
string physicalPath = GetWindowsPhysicalPath(filepath);
if (physicalPath == null) return false;
if (filepath != physicalPath) return false;
else return true;
}
I copied the code for GetWindowsPhysicalPath(string path) from the question
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint GetLongPathName(string ShortPath, StringBuilder sb, int buffer);
[DllImport("kernel32.dll")]
static extern uint GetShortPathName(string longpath, StringBuilder sb, int buffer);
protected static string GetWindowsPhysicalPath(string path)
{
StringBuilder builder = new StringBuilder(255);
// names with long extension can cause the short name to be actually larger than
// the long name.
GetShortPathName(path, builder, builder.Capacity);
path = builder.ToString();
uint result = GetLongPathName(path, builder, builder.Capacity);
if (result > 0 && result < builder.Capacity)
{
//Success retrieved long file name
builder[0] = char.ToLower(builder[0]);
return builder.ToString(0, (int)result);
}
if (result > 0)
{
//Need more capacity in the buffer
//specified in the result variable
builder = new StringBuilder((int)result);
result = GetLongPathName(path, builder, builder.Capacity);
builder[0] = char.ToLower(builder[0]);
return builder.ToString(0, (int)result);
}
return null;
}
Note the only problem I found with this function is, the drive letter seems to be always in lowercase. Example: The physical path on Windows is: D:\Test\test.txt, the GetWindowsPhysicalPath(string path)function returns d:\Test\test.txt
Here's a relatively simple way to check if a directory actually exists as named. I needed this because I have an option to rename a folder, and it checks that there won't be a clash first. So, for example, if I want to rename the folder "cars" to "Cars".
It's pretty straight-foward really. If the system reports that the folder exists, then I just call GetDirectories on the parent folder, and passing the directory name as the wildcard (thus returning exactly 1 result). Just a simple comparison gives me the answer.
static public bool DirExistsMatchCase(string path)
{
// If it definitely doesn't return false
if (!Directory.Exists(path)) return false;
// Figure out if the case (of the final part) is the same
string thisDir = Path.GetFileName(path);
string actualDir = Path.GetFileName(Directory.GetDirectories(Path.GetDirectoryName(path), thisDir)[0]);
return thisDir == actualDir;
}
Try these 2 simpler options that do not need to use PInvoke and return a nullable Boolean (bool?). I am not a subject expert so I do know if this is the most efficient code but it works for me.
Simply pass in a path and if the result is null (HasValue = false) no match is found, if the result is false there is an exact match, otherwise if true there is a match with a difference case.
The methods GetFiles, GetDirectories and GetDrives all return the exact case as saved on your file system so you can use a case sensitive compare method.
NB: for the case where the path is an exact drive (e.g. #"C:\") I have to use a slightly different approach.
using System.IO;
class MyFolderFileHelper {
public static bool? FileExistsWithDifferentCase(string fileName)
{
bool? result = null;
if (File.Exists(fileName))
{
result = false;
string directory = Path.GetDirectoryName(fileName);
string fileTitle = Path.GetFileName(fileName);
string[] files = Directory.GetFiles(directory, fileTitle);
if (String.Compare(files[0], fileName, false) != 0)
result = true;
}
return result;
}
public static bool? DirectoryExistsWithDifferentCase(string directoryName)
{
bool? result = null;
if (Directory.Exists(directoryName))
{
result = false;
directoryName = directoryName.TrimEnd(Path.DirectorySeparatorChar);
int lastPathSeparatorIndex = directoryName.LastIndexOf(Path.DirectorySeparatorChar);
if (lastPathSeparatorIndex >= 0)
{
string baseDirectory = directoryName.Substring(lastPathSeparatorIndex + 1);
string parentDirectory = directoryName.Substring(0, lastPathSeparatorIndex);
string[] directories = Directory.GetDirectories(parentDirectory, baseDirectory);
if (String.Compare(directories[0], directoryName, false) != 0)
result = true;
}
else
{
//if directory is a drive
directoryName += Path.DirectorySeparatorChar.ToString();
DriveInfo[] drives = DriveInfo.GetDrives();
foreach(DriveInfo driveInfo in drives)
{
if (String.Compare(driveInfo.Name, directoryName, true) == 0)
{
if (String.Compare(driveInfo.Name, directoryName, false) != 0)
result = true;
break;
}
}
}
}
return result;
}
}
If the (relative or absolute) path of your file is:
string AssetPath = "...";
The following ensures that the file both exists and has the correct casing:
if(File.Exists(AssetPath) && Path.GetFullPath(AssetPath) == Directory.GetFiles(Path.GetDirectoryName(Path.GetFullPath(AssetPath)), Path.GetFileName(Path.GetFullPath(AssetPath))).Single())
{
}
Enjoy!
We are using the following code for retrieving active MAC address of a windows pc.
private static string macId()
{
return identifier("Win32_NetworkAdapterConfiguration", "MACAddress", "IPEnabled");
}
private static string identifier(string wmiClass, string wmiProperty, string wmiMustBeTrue)
{
string result = "";
System.Management.ManagementClass mc = new System.Management.ManagementClass(wmiClass);
System.Management.ManagementObjectCollection moc = mc.GetInstances();
foreach (System.Management.ManagementObject mo in moc)
{
if (mo[wmiMustBeTrue].ToString() == "True")
{
//Only get the first one
if (result == "")
{
try
{
result = mo[wmiProperty].ToString();
break;
}
catch
{
}
}
}
}
return result;
}
//Return a hardware identifier
private static string identifier(string wmiClass, string wmiProperty)
{
string result = "";
System.Management.ManagementClass mc = new System.Management.ManagementClass(wmiClass);
System.Management.ManagementObjectCollection moc = mc.GetInstances();
foreach (System.Management.ManagementObject mo in moc)
{
//Only get the first one
if (result == "")
{
try
{
result = mo[wmiProperty].ToString();
break;
}
catch
{
}
}
}
return result;
}
It works fine to retrieve the MAC address. The problem is when the MAC address is spoofed then it returns the spoofed MAC address. We want to somehow retrieve the original MAC address which is unique and assigned at the factory. Is there any way to do so?
I wish to give an alternative. I don't know if it really answer to 'a way to uniquely identify any computer'.
However, this method query the Win32_BIOS class in System.Management and return a string with high chances to be unique. (Waiting to be disavowed!!)
/// <summary>
/// BIOS IDentifier
/// </summary>
/// <returns></returns>
public static string BIOS_ID()
{
return GetFirstIdentifier("Win32_BIOS", "Manufacturer")
+ GetFirstIdentifier("Win32_BIOS", "SMBIOSBIOSVersion")
+ GetFirstIdentifier("Win32_BIOS", "IdentificationCode")
+ GetFirstIdentifier("Win32_BIOS", "SerialNumber")
+ GetFirstIdentifier("Win32_BIOS", "ReleaseDate")
+ GetFirstIdentifier("Win32_BIOS", "Version");
}
/// <summary>
/// ManagementClass used to read the first specific properties
/// </summary>
/// <param name="wmiClass">Object Class to query</param>
/// <param name="wmiProperty">Property to get info</param>
/// <returns></returns>
private static string GetFirstIdentifier(string wmiClass, string wmiProperty)
{
string result = string.Empty;
ManagementClass mc = new System.Management.ManagementClass(wmiClass);
ManagementObjectCollection moc = mc.GetInstances();
foreach (ManagementObject mo in moc)
{
//Only get the first one
if (string.IsNullOrEmpty(result))
{
try
{
if (mo[wmiProperty] != null) result = mo[wmiProperty].ToString();
break;
}
catch
{
}
}
}
return result.Trim();
}
There can be two alternatives.
You can get the MAC address using the code snippet you gave before and check if that MAC address belongs to any NIC (Network Interface Card). If it doesn't belong to one, then the MAC address is obviously spoofed. Here is the code that Locates the NIC using a MAC adress
using System.Net.Sockets;
using System.Net;
using System.Net.NetworkInformation;
string localNicMac = "00:00:00:11:22:33".Replace(":", "-"); // Parse doesn't like colons
var mac = PhysicalAddress.Parse(localNicMac);
var localNic =
NetworkInterface.GetAllNetworkInterfaces()
.Where(nic => nic.GetPhysicalAddress().Equals(mac)) // Must use .Equals, not ==
.SingleOrDefault();
if (localNic == null)
{
throw new ArgumentException("Local NIC with the specified MAC could not be found.");
}
var ips =
localNic.GetIPProperties().UnicastAddresses
.Select(x => x.Address);
Get the network card address directly.
a. NWIF = dotnetClass "System.Net.NetworkInformation.NetworkInterface"
b. the_Mac_array = NWIF.GetAllNetworkInterfaces() -- this is an array of all the Networks
c. the_PhysicalAddress_Array = #()
d. for net in the_Mac_array where (net.NetworkInterfaceType.toString()) == "Ethernet" do append the_PhysicalAddress_Array ((net.GetPhysicalAddress()).toString())
e. print the_PhysicalAddress_Array
(( I found it here http://snipplr.com/view/23006/ ))
I had to write something similar a little while ago because I was using a number of hardware parameters for "activation" of my software.
Have a look at, DeviceIoControl & OID_802_3_PERMANENT_ADDRESS.
Its a lot of interop code (my class for handling it is approximatley 200 lines), but it gets me the hardware code guaranteed.
Some code snippets to get you going,
private const uint IOCTL_NDIS_QUERY_GLOBAL_STATS = 0x170002;
[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool DeviceIoControl(
SafeFileHandle hDevice,
uint dwIoControlCode,
ref int InBuffer,
int nInBufferSize,
byte[] OutBuffer,
int nOutBufferSize,
out int pBytesReturned,
IntPtr lpOverlapped);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern SafeFileHandle CreateFile(
string lpFileName,
EFileAccess dwDesiredAccess,
EFileShare dwShareMode,
IntPtr lpSecurityAttributes,
ECreationDisposition dwCreationDisposition,
EFileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
[Flags]
internal enum EFileAccess : uint
{
Delete = 0x10000,
ReadControl = 0x20000,
WriteDAC = 0x40000,
WriteOwner = 0x80000,
Synchronize = 0x100000,
StandardRightsRequired = 0xF0000,
StandardRightsRead = ReadControl,
StandardRightsWrite = ReadControl,
StandardRightsExecute = ReadControl,
StandardRightsAll = 0x1F0000,
SpecificRightsAll = 0xFFFF,
AccessSystemSecurity = 0x1000000, // AccessSystemAcl access type
MaximumAllowed = 0x2000000, // MaximumAllowed access type
GenericRead = 0x80000000,
GenericWrite = 0x40000000,
GenericExecute = 0x20000000,
GenericAll = 0x10000000
}
// Open a file handle to the interface
using (SafeFileHandle handle = FileInterop.CreateFile(deviceName,
FileInterop.EFileAccess.GenericRead | FileInterop.EFileAccess.GenericWrite,
0, IntPtr.Zero, FileInterop.ECreationDisposition.OpenExisting,
0, IntPtr.Zero))
{
int bytesReturned;
// Set the OID to query the permanent address
// http://msdn.microsoft.com/en-us/library/windows/hardware/ff569074(v=vs.85).aspx
int OID_802_3_PERMANENT_ADDRESS = 0x01010101;
// Array to capture the mac address
var address = new byte[6];
if (DeviceIoControl(handle, IOCTL_NDIS_QUERY_GLOBAL_STATS,
ref OID_802_3_PERMANENT_ADDRESS, sizeof(uint),
address, 6, out bytesReturned, IntPtr.Zero))
{
// Attempt to parse the MAC address into a string
// any exceptions will be passed onto the caller
return BitConverter.ToString(address, 0, 6);
}
}
Well, I wouldn't bet all my money on the order in which NetworkInterface class lists NetworkInterfaces.
My mainboard has 2 adapters and the order seems to switch every time I reboot.
So here is a suggestion, which worked for me (BTW : credits goes probably to another awesome stackoverflow contributer, ty) :
public static string GetMACAddress()
{
NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
//for each j you can get the MAC
PhysicalAddress address = nics[0].GetPhysicalAddress();
byte[] bytes = address.GetAddressBytes();
string macAddress = "";
for (int i = 0; i < bytes.Length; i++)
{
// Format the physical address in hexadecimal.
macAddress += bytes[i].ToString("X2");
// Insert a hyphen after each byte, unless we are at the end of the address.
if (i != bytes.Length - 1)
{
macAddress += "-";
}
}
return macAddress;
}
I have a C# program where I have to get the product code of an installed msi. I have only the msi name as the input. Can this be done programmatically?
Do the answers to this question help? They want to get the product name, but maybe it works for the product code, too?
EDIT
If you do not have the MSI file itself to access the database (as suggested by the above link to the other question), you may try to search the following registry path for the name of your MSI file:
HKEY_CLASSES_ROOT\Installer\Products\*\SourceList
There are many entries under the Products branch. Each of them is a product key. Every branch should contain the SourceList node, which in turn should contain the value PackageName. That value holds the name of the MSI file.
So what I'd do is:
for each key in Products
{
open SourceList subkey
read PackageName value
if name equals my msi file name
{
return key-name formatted as GUID
}
}
This is the code I used to get the UninstallString of any MSI.
private string GetUninstallString(string msiName)
{
Utility.WriteLog("Entered GetUninstallString(msiName) - Parameters: msiName = " + msiName);
string uninstallString = string.Empty;
try
{
string path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\S-1-5-18\\Products";
RegistryKey key = Registry.LocalMachine.OpenSubKey(path);
foreach (string tempKeyName in key.GetSubKeyNames())
{
RegistryKey tempKey = key.OpenSubKey(tempKeyName + "\\InstallProperties");
if (tempKey != null)
{
if (string.Equals(Convert.ToString(tempKey.GetValue("DisplayName")), msiName, StringComparison.CurrentCultureIgnoreCase))
{
uninstallString = Convert.ToString(tempKey.GetValue("UninstallString"));
uninstallString = uninstallString.Replace("/I", "/X");
uninstallString = uninstallString.Replace("MsiExec.exe", "").Trim();
uninstallString += " /quiet /qn";
break;
}
}
}
return uninstallString;
}
catch (Exception ex)
{
throw new ApplicationException(ex.Message);
}
}
This will give a result like this:
MsiExec.exe /I{6BB09011-69E1-472F-ACAD-FA0E7DA3E2CE}
From this string, you can take the substring within the braces {}, which will be 6BB09011-69E1-472F-ACAD-FA0E7DA3E2CE. I hope this might be the product code.
There is most fast and simply way - to use WMI with conditional query string.
public string GetProductCode(string productName)
{
string query = string.Format("select * from Win32_Product where Name='{0}'", productName);
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
{
foreach (ManagementObject product in searcher.Get())
return product["IdentifyingNumber"].ToString();
}
return null;
}
This code obtains the product code directly from an MSI file. So this allows reading the code without installing the file.
class MsiHandle : SafeHandleMinusOneIsInvalid
{
public MsiHandle()
: base(true)
{ }
protected override bool ReleaseHandle()
{
return NativeMethods.MsiCloseHandle(handle) == 0;
}
}
class NativeMethods
{
const string MsiDll = "Msi.dll";
[DllImport(MsiDll, CharSet = CharSet.Unicode, ExactSpelling = true)]
public extern static uint MsiOpenPackageW(string szPackagePath, out MsiHandle product);
[DllImport(MsiDll, ExactSpelling=true)]
public extern static uint MsiCloseHandle(IntPtr hAny);
[DllImport(MsiDll, CharSet = CharSet.Unicode, ExactSpelling = true)]
static extern uint MsiGetProductPropertyW(MsiHandle hProduct, string szProperty, StringBuilder value, ref int length);
[DllImport(MsiDll, ExactSpelling = true)]
public static extern int MsiSetInternalUI(int value, IntPtr hwnd);
public static uint MsiGetProductProperty(MsiHandle hProduct, string szProperty, out string value)
{
StringBuilder sb = new StringBuilder(1024);
int length = sb.Capacity;
uint err;
value = null;
if(0 == (err = MsiGetProductPropertyW(hProduct, szProperty, sb, ref length)))
{
sb.Length = length;
value = sb.ToString();
return 0;
}
return err;
}
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static int Main(string[] args)
{
string msiFile = args[0];
NativeMethods.MsiSetInternalUI(2, IntPtr.Zero); // Hide all UI. Without this you get a MSI dialog
MsiHandle msi;
uint err;
if (0 != (err = NativeMethods.MsiOpenPackageW(args[0], out msi)))
{
Console.Error.WriteLine("Can't open MSI, error {0}", err);
return 1;
}
// Strings available in all MSIs
string productCode;
using (msi)
{
if (0 != NativeMethods.MsiGetProductProperty(msi, "ProductCode", out productCode))
throw new InvalidOperationException("Can't obtain product code");
Console.WriteLine(productCode);
return 0;
}
}
}
Full example in Subversion on http://ankhsvn.open.collab.net/svn/ankhsvn/trunk/src/tools/Ankh.Chocolatey/
Use username 'guest' and no password.
private static bool GetUninstallString(string ProductName)
{
try
{
RegistryKey localKey = RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, RegistryView.Registry64);
var key = localKey.OpenSubKey(#"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") ??
localKey.OpenSubKey(
#"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall");
if (key == null)
return false;
return key.GetSubKeyNames()
.Select(keyName => key.OpenSubKey(keyName))
.Select(subkey => subkey.GetValue("DisplayName") as string)
.Any(displayName => displayName != null && displayName.Contains(ProductName));
}
catch
{
// Log message
return false;
}
}
This is very usefull for search string by productname