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!
Related
what I want to achieve is convert the relationship between current selected object and it's specific parent to path pattern in hierarchy window.For example, from the screenshot, the Test_01 and Test_02 is the specific parent, and if the current selected obj is ABBB under Test_01, then I will have a path like Test_01/AB/ABB/ABBB, my code is below, my problem is because the function findPathInScence is recursive, the variable pt seems only can get the first value it return, like the pt always empty but scenePath always what I want. How to make the function return the value only when if is true? please help.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
public class Test
{
string tempPath;
private string findPathInScence(GameObject gs)
{
string scenePath = "";
if (gs.name.Contains("Test"))
{
scenePath = gs.name + tempPath;
Debug.Log("scenePath: " + scenePath);
tempPath = "";
}
else
{
tempPath = "/" + gs.name + tempPath;
findPathInScence(gs.transform.parent.gameObject);
}
return scenePath;
}
[MenuItem("GameObject/printPath", false, 20)]
static void printPathInScene()
{
Test t = new Test();
string pt = t.findPathInScence(Selection.activeGameObject);
Debug.Log("pt: " + pt);
}
}
Issue
In the case that your object does not match Test you set
scenePath = "";
and then regardless of the result of the recursive findPathInScence you simply return
return scenePath;
unlike the tempPath which is a field the scenePath is a local variable only existent during the individual run of the method.
The last recursive call for the object Test_01 actually returns the correct path, however you do not use it anywhere!
=> For any selected object that has not Test the return value is always empty!
Solution with recursion
If you really want to go recursive you have to properly get the path of the recursive run correctly. You could do it like e.g.
public static class TransformExtensions
{
public static string GetScenePath(this Transform transform, string stopIfContains, StringBuilder path = null)
{
if (path == null)
{
path = new StringBuilder();
}
if (gs.name.Contains(stopIfContains))
{
var scenePath = path.Insert(0, transform.name);
Debug.Log($"{nameof(scenePath)}: {scenePath}");
return scenePath.ToString();
}
path.Insert(0, gs.name).Insert(0, '/');
return GetScenePath(transform.parent, path);
}
}
and
Debug.Log("pt: " + Selection.activeGameObject.transform.GetScenePath("Test"));
Solution without recursion
But why even use recursion?
You can simply bubble up in a loop which is way easier to understand, maintain and debug.
like e.g. (source)
public static class TransformExtensions
{
public static string GetScenePath(this Transform transform)
{
string path = transform.name;
while (transform.parent != null)
{
transform = transform.parent;
path = transform.name + "/" + path;
}
return path;
}
}
or maybe without so many string concatenations
public static class TransformExtensions
{
public static string GetScenePath(this Transform transform)
{
var sb = new StringBuilder(transform.name);
while (transform.parent != null)
{
transform = transform.parent;
sb.Insert(0, '/').Insert(0, transform.name);
}
return sb.ToString();
}
}
now you can simply call
Debug.Log("pt: " + Selection.activeGameObject.transform.GetScenePath());
Or with your additional exit strategy (name match)
public static class TransformExtensions
{
public static string GetScenePath(this Transform transform, string stopIfContains)
{
var sb = new StringBuilder(transform.name);
while (transform.parent != null && !transform.name.Contains(stopIfContains))
{
transform = transform.parent;
sb.Insert(0, '/').Insert(0, transform.name);
}
return sb.ToString();
}
}
and
Debug.Log("pt: " + Selection.activeGameObject.transform.GetScenePath("Test"));
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
}
}
}
I want to programmatically find out if my application is running from a network drive. What is the simplest way of doing that? It should support both UNC paths (\\127.0.0.1\d$) and mapped network drives (Z:).
This is for mapped drive case. You can use the DriveInfo class to find out whether drive a is a network drive or not.
DriveInfo info = new DriveInfo("Z");
if (info.DriveType == DriveType.Network)
{
// Running from network
}
Complete method and Sample Code.
public static bool IsRunningFromNetwork(string rootPath)
{
try
{
System.IO.DriveInfo info = new DriveInfo(rootPath);
if (info.DriveType == DriveType.Network)
{
return true;
}
return false;
}
catch
{
try
{
Uri uri = new Uri(rootPath);
return uri.IsUnc;
}
catch
{
return false;
}
}
}
static void Main(string[] args)
{
Console.WriteLine(IsRunningFromNetwork(System.IO.Path.GetPathRoot(AppDomain.CurrentDomain.BaseDirectory))); }
if (new DriveInfo(Application.StartupPath).DriveType == DriveType.Network)
{
// here
}
This is my current method of doing this, but it feels like there should be a better way.
private bool IsRunningFromNetworkDrive()
{
var dir = AppDomain.CurrentDomain.BaseDirectory;
var driveLetter = dir.First();
if (!Char.IsLetter(driveLetter))
return true;
if (new DriveInfo(driveLetter.ToString()).DriveType == DriveType.Network)
return true;
return false;
}
In case using UNC path it is quitely simple - examine host name in UNC and test that it is localhost(127.0.0.1, ::1, hostname, hostname.domain.local, ip-addresses of workstation) or not.
If the path is not UNC - extract the drive letter from path and test the DriveInfo class for its type
I rearranged the solution of dotnetstep, which is in my opinion better because it avoids exceptions when a valid path is passed, and it throws an exception if there is a wrong path passed, which does not allow to make an assumption of true or false.
//----------------------------------------------------------------------------------------------------
/// <summary>Gets a boolean indicating whether the specified path is a local path or a network path.</summary>
/// <param name="path">Path to check</param>
/// <returns>Returns a boolean indicating whether the specified path is a local path or a network path.</returns>
public static Boolean IsNetworkPath(String path) {
Uri uri = new Uri(path);
if (uri.IsUnc) {
return true;
}
DriveInfo info = new DriveInfo(path);
if (info.DriveType == DriveType.Network) {
return true;
}
return false;
}
Test:
//----------------------------------------------------------------------------------------------------
/// <summary>A test for IsNetworkPath</summary>
[TestMethod()]
public void IsNetworkPathTest() {
String s1 = #"\\Test"; // unc
String s2 = #"C:\Program Files"; // local
String s3 = #"S:\"; // mapped
String s4 = "ljöasdf"; // invalid
Assert.IsTrue(RPath.IsNetworkPath(s1));
Assert.IsFalse(RPath.IsNetworkPath(s2));
Assert.IsTrue(RPath.IsNetworkPath(s3));
try {
RPath.IsNetworkPath(s4);
Assert.Fail();
}
catch {}
}
DriveInfo m = DriveInfo.GetDrives().Where(p => p.DriveType == DriveType.Network).FirstOrDefault();
if (m != null)
{
//do stuff
}
else
{
//do stuff
}
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
In my .NET 2.0 application, I need to check if sufficient permissions exist to create and write to files to a directory. To this end, I have the following function that attempts to create a file and write a single byte to it, deleting itself afterwards to test that permissions do exist.
I figured the best way to check was to actually try and do it, catching any exceptions that occur. I'm not particularly happy about the general Exception catch though, so is there a better or perhaps a more accepted way of doing this?
private const string TEMP_FILE = "\\tempFile.tmp";
/// <summary>
/// Checks the ability to create and write to a file in the supplied directory.
/// </summary>
/// <param name="directory">String representing the directory path to check.</param>
/// <returns>True if successful; otherwise false.</returns>
private static bool CheckDirectoryAccess(string directory)
{
bool success = false;
string fullPath = directory + TEMP_FILE;
if (Directory.Exists(directory))
{
try
{
using (FileStream fs = new FileStream(fullPath, FileMode.CreateNew,
FileAccess.Write))
{
fs.WriteByte(0xff);
}
if (File.Exists(fullPath))
{
File.Delete(fullPath);
success = true;
}
}
catch (Exception)
{
success = false;
}
}
Directory.GetAccessControl(path) does what you are asking for.
public static bool HasWritePermissionOnDir(string path)
{
var writeAllow = false;
var writeDeny = false;
var accessControlList = Directory.GetAccessControl(path);
if (accessControlList == null)
return false;
var accessRules = accessControlList.GetAccessRules(true, true,
typeof(System.Security.Principal.SecurityIdentifier));
if (accessRules ==null)
return false;
foreach (FileSystemAccessRule rule in accessRules)
{
if ((FileSystemRights.Write & rule.FileSystemRights) != FileSystemRights.Write)
continue;
if (rule.AccessControlType == AccessControlType.Allow)
writeAllow = true;
else if (rule.AccessControlType == AccessControlType.Deny)
writeDeny = true;
}
return writeAllow && !writeDeny;
}
(FileSystemRights.Write & rights) == FileSystemRights.Write is using something called "Flags" btw which if you don't know what it is you should really read up on :)
Deny takes precedence over Allow. Local rules take precedence over inherited rules. I have seen many solutions (including some answers shown here), but none of them takes into account whether rules are inherited or not. Therefore I suggest the following approach that considers rule inheritance (neatly wrapped into a class):
public class CurrentUserSecurity
{
WindowsIdentity _currentUser;
WindowsPrincipal _currentPrincipal;
public CurrentUserSecurity()
{
_currentUser = WindowsIdentity.GetCurrent();
_currentPrincipal = new WindowsPrincipal(_currentUser);
}
public bool HasAccess(DirectoryInfo directory, FileSystemRights right)
{
// Get the collection of authorization rules that apply to the directory.
AuthorizationRuleCollection acl = directory.GetAccessControl()
.GetAccessRules(true, true, typeof(SecurityIdentifier));
return HasFileOrDirectoryAccess(right, acl);
}
public bool HasAccess(FileInfo file, FileSystemRights right)
{
// Get the collection of authorization rules that apply to the file.
AuthorizationRuleCollection acl = file.GetAccessControl()
.GetAccessRules(true, true, typeof(SecurityIdentifier));
return HasFileOrDirectoryAccess(right, acl);
}
private bool HasFileOrDirectoryAccess(FileSystemRights right,
AuthorizationRuleCollection acl)
{
bool allow = false;
bool inheritedAllow = false;
bool inheritedDeny = false;
for (int i = 0; i < acl.Count; i++) {
var currentRule = (FileSystemAccessRule)acl[i];
// If the current rule applies to the current user.
if (_currentUser.User.Equals(currentRule.IdentityReference) ||
_currentPrincipal.IsInRole(
(SecurityIdentifier)currentRule.IdentityReference)) {
if (currentRule.AccessControlType.Equals(AccessControlType.Deny)) {
if ((currentRule.FileSystemRights & right) == right) {
if (currentRule.IsInherited) {
inheritedDeny = true;
} else { // Non inherited "deny" takes overall precedence.
return false;
}
}
} else if (currentRule.AccessControlType
.Equals(AccessControlType.Allow)) {
if ((currentRule.FileSystemRights & right) == right) {
if (currentRule.IsInherited) {
inheritedAllow = true;
} else {
allow = true;
}
}
}
}
}
if (allow) { // Non inherited "allow" takes precedence over inherited rules.
return true;
}
return inheritedAllow && !inheritedDeny;
}
}
However, I made the experience that this does not always work on remote computers as you will not always have the right to query the file access rights there. The solution in that case is to try; possibly even by just trying to create a temporary file, if you need to know the access right before working with the "real" files.
The answers by Richard and Jason are sort of in the right direction. However what you should be doing is computing the effective permissions for the user identity running your code. None of the examples above correctly account for group membership for example.
I'm pretty sure Keith Brown had some code to do this in his wiki version (offline at this time) of The .NET Developers Guide to Windows Security. This is also discussed in reasonable detail in his Programming Windows Security book.
Computing effective permissions is not for the faint hearted and your code to attempt creating a file and catching the security exception thrown is probably the path of least resistance.
The accepted answer by Kev to this question doesn't actually give any code, it just points to other resources that I don't have access to. So here's my best attempt at the function. It actually checks that the permission it's looking at is a "Write" permission and that the current user belongs to the appropriate group.
It might not be complete with regard to network paths or whatever, but it's good enough for my purpose, checking local configuration files under "Program Files" for writability:
using System.Security.Principal;
using System.Security.AccessControl;
private static bool HasWritePermission(string FilePath)
{
try
{
FileSystemSecurity security;
if (File.Exists(FilePath))
{
security = File.GetAccessControl(FilePath);
}
else
{
security = Directory.GetAccessControl(Path.GetDirectoryName(FilePath));
}
var rules = security.GetAccessRules(true, true, typeof(NTAccount));
var currentuser = new WindowsPrincipal(WindowsIdentity.GetCurrent());
bool result = false;
foreach (FileSystemAccessRule rule in rules)
{
if (0 == (rule.FileSystemRights &
(FileSystemRights.WriteData | FileSystemRights.Write)))
{
continue;
}
if (rule.IdentityReference.Value.StartsWith("S-1-"))
{
var sid = new SecurityIdentifier(rule.IdentityReference.Value);
if (!currentuser.IsInRole(sid))
{
continue;
}
}
else
{
if (!currentuser.IsInRole(rule.IdentityReference.Value))
{
continue;
}
}
if (rule.AccessControlType == AccessControlType.Deny)
return false;
if (rule.AccessControlType == AccessControlType.Allow)
result = true;
}
return result;
}
catch
{
return false;
}
}
IMO, you need to work with such directories as usual, but instead of checking permissions before use, provide the correct way to handle UnauthorizedAccessException and react accordingly. This method is easier and much less error prone.
Try working with this C# snippet I just crafted:
using System;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string directory = #"C:\downloads";
DirectoryInfo di = new DirectoryInfo(directory);
DirectorySecurity ds = di.GetAccessControl();
foreach (AccessRule rule in ds.GetAccessRules(true, true, typeof(NTAccount)))
{
Console.WriteLine("Identity = {0}; Access = {1}",
rule.IdentityReference.Value, rule.AccessControlType);
}
}
}
}
And here's a reference you could also look at. My code might give you an idea as to how you could check for permissions before attempting to write to a directory.
according to this link:
http://www.authorcode.com/how-to-check-file-permission-to-write-in-c/
it's easier to use existing class SecurityManager
string FileLocation = #"C:\test.txt";
FileIOPermission writePermission = new FileIOPermission(FileIOPermissionAccess.Write, FileLocation);
if (SecurityManager.IsGranted(writePermission))
{
// you have permission
}
else
{
// permission is required!
}
but it seems it's been obsoleted, it is suggested to use PermissionSet instead.
[Obsolete("IsGranted is obsolete and will be removed in a future release of the .NET Framework. Please use the PermissionSet property of either AppDomain or Assembly instead.")]
Since the static method 'GetAccessControl' seems to be missing from the present version of .Net core/Standard I had to modify #Bryce Wagner's answer (I went ahead and used more modern syntax):
public static class PermissionHelper
{
public static bool? CurrentUserHasWritePermission(string filePath)
=> new WindowsPrincipal(WindowsIdentity.GetCurrent())
.SelectWritePermissions(filePath)
.FirstOrDefault();
private static IEnumerable<bool?> SelectWritePermissions(this WindowsPrincipal user, string filePath)
=> from rule in filePath
.GetFileSystemSecurity()
.GetAccessRules(true, true, typeof(NTAccount))
.Cast<FileSystemAccessRule>()
let right = user.HasRightSafe(rule)
where right.HasValue
// Deny takes precedence over allow
orderby right.Value == false descending
select right;
private static bool? HasRightSafe(this WindowsPrincipal user, FileSystemAccessRule rule)
{
try
{
return user.HasRight(rule);
}
catch
{
return null;
}
}
private static bool? HasRight(this WindowsPrincipal user,FileSystemAccessRule rule )
=> rule switch
{
{ FileSystemRights: FileSystemRights fileSystemRights } when (fileSystemRights &
(FileSystemRights.WriteData | FileSystemRights.Write)) == 0 => null,
{ IdentityReference: { Value: string value } } when value.StartsWith("S-1-") &&
!user.IsInRole(new SecurityIdentifier(rule.IdentityReference.Value)) => null,
{ IdentityReference: { Value: string value } } when value.StartsWith("S-1-") == false &&
!user.IsInRole(rule.IdentityReference.Value) => null,
{ AccessControlType: AccessControlType.Deny } => false,
{ AccessControlType: AccessControlType.Allow } => true,
_ => null
};
private static FileSystemSecurity GetFileSystemSecurity(this string filePath)
=> new FileInfo(filePath) switch
{
{ Exists: true } fileInfo => fileInfo.GetAccessControl(),
{ Exists: false } fileInfo => (FileSystemSecurity)fileInfo.Directory.GetAccessControl(),
_ => throw new Exception($"Check the file path, {filePath}: something's wrong with it.")
};
}
private static void GrantAccess(string file)
{
bool exists = System.IO.Directory.Exists(file);
if (!exists)
{
DirectoryInfo di = System.IO.Directory.CreateDirectory(file);
Console.WriteLine("The Folder is created Sucessfully");
}
else
{
Console.WriteLine("The Folder already exists");
}
DirectoryInfo dInfo = new DirectoryInfo(file);
DirectorySecurity dSecurity = dInfo.GetAccessControl();
dSecurity.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), FileSystemRights.FullControl, InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, PropagationFlags.NoPropagateInherit, AccessControlType.Allow));
dInfo.SetAccessControl(dSecurity);
}