Browse Source

Bug 1421: WinSCP .NET assembly cannot find WinSCP executable in its installation folder not the default installation folder when used in 64-bit process

https://winscp.net/tracker/1421

Source commit: 99d20e9f06f4db56100ef1129595a124a64e8f87
Martin Prikryl 9 years ago
parent
commit
9db4b4392e

+ 2 - 0
dotnet/GlobalSuppressions.cs

@@ -159,3 +159,5 @@ using System.Diagnostics.CodeAnalysis;
 [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "WinSCP.Logger.#CreateCounters()")]
 [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "WinSCP.SessionLogReader.#LogContents()")]
 [assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "WinSCP.Session.#SessionOptionsToSwitches(WinSCP.SessionOptions,System.Boolean)")]
+[assembly: SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Scope = "member", Target = "WinSCP.UnsafeNativeMethods.#RegGetValue(System.UIntPtr,System.String,System.String,WinSCP.RegistryFlags,WinSCP.RegistryType&,System.IntPtr,System.UInt32&)")]
+[assembly: SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "WinSCP.ExeSessionProcess.#GetInstallationPath(Microsoft.Win32.RegistryHive)")]

+ 36 - 6
dotnet/internal/ExeSessionProcess.cs

@@ -771,8 +771,8 @@ namespace WinSCP
                 else
                 {
                     if (!TryFindExecutableInPath(GetAssemblyPath(), out executablePath) &&
-                        !TryFindExecutableInPath(GetInstallationPath(Registry.CurrentUser), out executablePath) &&
-                        !TryFindExecutableInPath(GetInstallationPath(Registry.LocalMachine), out executablePath) &&
+                        !TryFindExecutableInPath(GetInstallationPath(RegistryHive.CurrentUser), out executablePath) &&
+                        !TryFindExecutableInPath(GetInstallationPath(RegistryHive.LocalMachine), out executablePath) &&
                         !TryFindExecutableInPath(GetDefaultInstallationPath(), out executablePath))
                     {
                         throw new SessionLocalException(_session,
@@ -787,13 +787,43 @@ namespace WinSCP
 
         private static string GetDefaultInstallationPath()
         {
-            return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WinSCP");
+            string programFiles;
+            if (IntPtr.Size == 8)
+            {
+                // In .NET 4 we can use Environment.SpecialFolder.ProgramFilesX86
+                programFiles = Environment.GetEnvironmentVariable("ProgramFiles(x86)");
+            }
+            else
+            {
+                programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
+            }
+            return Path.Combine(programFiles, "WinSCP");
         }
 
-        private static string GetInstallationPath(RegistryKey rootKey)
+        private static string GetInstallationPath(RegistryHive hive)
         {
-            RegistryKey key = rootKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\winscp3_is1");
-            return (key != null) ? (string)key.GetValue("Inno Setup: App Path") : null;
+            // In .NET 4 we can use RegistryKey.OpenBaseKey(hive, RegistryView.Registry32); 
+            const string uninstallKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\winscp3_is1";
+            const string appPathValue = @"Inno Setup: App Path";
+
+            string result = null;
+
+            IntPtr data = IntPtr.Zero;
+            RegistryType type;
+            uint len = 0;
+            RegistryFlags flags = RegistryFlags.RegSz | RegistryFlags.SubKeyWow6432Key;
+            UIntPtr key = (UIntPtr)((uint)hive);
+
+            if (UnsafeNativeMethods.RegGetValue(key, uninstallKey, appPathValue, flags, out type, data, ref len) == 0)
+            {
+                data = Marshal.AllocHGlobal((int)len);
+                if (UnsafeNativeMethods.RegGetValue(key, uninstallKey, appPathValue, flags, out type, data, ref len) == 0)
+                {
+                    result = Marshal.PtrToStringUni(data);
+                }
+            }
+
+            return result;
         }
 
         private bool TryFindExecutableInPath(string path, out string result)

+ 14 - 0
dotnet/internal/UnsafeNativeMethods.cs

@@ -146,6 +146,17 @@ namespace WinSCP
             StandardRights.Required
     }
 
+    internal enum RegistryFlags
+    {
+        RegSz = 0x02,
+        SubKeyWow6432Key = 0x00020000,
+    }
+
+    internal enum RegistryType
+    {
+        RegNone = 0,
+    }
+
     internal static class UnsafeNativeMethods
     {
         public const int ERROR_ALREADY_EXISTS = 183;
@@ -178,5 +189,8 @@ namespace WinSCP
 
         [DllImport("kernel32.dll", SetLastError = true)]
         public static extern int GetCurrentThreadId();
+
+        [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
+        public static extern int RegGetValue(UIntPtr hkey, string lpSubKey, string lpValue, RegistryFlags dwFlags, out RegistryType pdwType, IntPtr pvData, ref uint pcbData);
     }
 }