3

My app needs to set up a SQL alias when it runs if it detects the alias is not set up. Right now I have it generate a temp Reg File and and run it through regedit.exe, however because my app is 32 bit (it must be as I am interoping with some 32 bit dll's that I can not get 64 bit versions for) windows is doing redirection when I run regedit to the version %windir%\SysWow64\regedit.exe instead of %windir%\regedit.exe.

This causes the keys I attempt to write to [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\Client\ConnectTo] to be redirected to the 32 bit sub folder, and my explicit writes to the 32 bit sub-folder, [HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\MSSQLServer\Client\ConnectTo] I have no clue where they are going.

Normally to get around this you would just use %windir%\sysnative\xxxx.exe but sysnative redirects to the System32 folder not the root windows folder, which is where regedit resides.

Is there a way to solve this issue without writing a custom program to elevate and do it itself?


Here is my current code, that is failing.

static void CreateAliases()
{
    using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
    {
        using (var key = baseKey.OpenSubKey(@"SOFTWARE\Microsoft\MSSQLServer\Client\ConnectTo"))
        {
            CheckKeys(key);
        }
    }
    try
    {
        using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
        {
            using (var key = baseKey.OpenSubKey(@"SOFTWARE\Microsoft\MSSQLServer\Client\ConnectTo"))
            {
                CheckKeys(key);
            }
        }
    }
    catch
    {
        //Catch failues if it is 32 bit only.
    }
}

private static void CheckKeys(RegistryKey key)
{
    //check to see if the key exists.
    if (key == null)
    {
        AddKeys();
        return;
    }

    var value = key.GetValue(@"wi\sql2008");
    if (value == null || value.ToString() != String.Concat("DBMSSOCN,wi,", Properties.Settings.Default.wi_sql2008Port))
    {
        AddKeys();
        return;
    }

    value = key.GetValue(@"wi\sql2005");
    if (value == null || value.ToString() != String.Concat("DBMSSOCN,wi,", Properties.Settings.Default.wi_sql2005Port))
    {
        AddKeys();
        return;
    }
}
static private void AddKeys()
{

    string file = System.IO.Path.GetTempFileName();
    using(StreamWriter sw = new StreamWriter(file))
    {
        sw.WriteLine("Windows Registry Editor Version 5.00");
        sw.WriteLine();
        sw.WriteLine(@"[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\MSSQLServer\Client\ConnectTo]");
        sw.WriteLine(String.Concat("\"wi\\\\sql2005\"=\"DBMSSOCN,wi,", Properties.Settings.Default.wi_sql2005Port,'"'));
        sw.WriteLine(String.Concat("\"wi\\\\sql2008\"=\"DBMSSOCN,wi,", Properties.Settings.Default.wi_sql2008Port,'"'));
        sw.WriteLine();
        sw.WriteLine(@"[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\Client\ConnectTo]");
        sw.WriteLine(String.Concat("\"wi\\\\sql2005\"=\"DBMSSOCN,wi,", Properties.Settings.Default.wi_sql2005Port, '"'));
        sw.WriteLine(String.Concat("\"wi\\\\sql2008\"=\"DBMSSOCN,wi,", Properties.Settings.Default.wi_sql2008Port, '"'));
    }

    WindowsIdentity identity = WindowsIdentity.GetCurrent();
    WindowsPrincipal principal = new WindowsPrincipal(identity);
    bool IsAdmin = principal.IsInRole("BUILTIN\\Administrators");

    string regedit;

    if (Environment.Is64BitProcess)
    {
        regedit = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "regedit");
    }
    else
    {
        regedit = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "sysnative", "regedit"); //regedit.exe does not exist in sysnative.
    }

    if (IsAdmin)
    {
        var proc = Process.Start(new ProcessStartInfo(regedit, String.Concat("/s ", file)));
        proc.WaitForExit();
    }
    else
    {
        MessageBox.Show("Updating registry keys for WI alias, this must be run as administrator");
        var proc = Process.Start(new ProcessStartInfo(regedit, String.Concat("/s ", file)) { Verb = "runas", UseShellExecute = true });
        proc.WaitForExit();
    }

    File.Delete(file);

}

Here is the temp file that is being generated.

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\MSSQLServer\Client\ConnectTo]
"wi\\sql2005"="DBMSSOCN,wi,49224"
"wi\\sql2008"="DBMSSOCN,wi,49681"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\Client\ConnectTo]
"wi\\sql2005"="DBMSSOCN,wi,49224"
"wi\\sql2008"="DBMSSOCN,wi,49681"
competent_tech
  • 44,465
  • 11
  • 90
  • 113
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • The somewhat hacky solution proposed in the question How to start a 64-bit process from a 32-bit process - http://stackoverflow.com/questions/2003573/how-to-start-a-64-bit-process-from-a-32-bit-process might help here? – Theo Spears Dec 05 '11 at 18:36
  • Is it feasible for you to deploy with a setup? This is exactly the type of operation that a setup is good for (one time action that requires admin permissions). – JMarsch Dec 05 '11 at 19:16
  • Better principal.IsInRole(WindowsBuiltInRole.Administrator) and use regedit = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "SysWOW64", "regedit"); – Kiquenet Dec 18 '13 at 11:11

2 Answers2

1

I would look into creating a server alias using the SMO ServerAlias class instead, then you don't have to deal with the registry access yourself.

Pondlife
  • 15,992
  • 6
  • 37
  • 51
  • I did, however, that is just a wrapper for WMI, and for WMI to add a alias you need to be a elevated administrator. If I can not get this to work with regedit I am planning on writing a custom program that will be launched and elevate itself and use that, but I was preferring not having to deploy two executables. – Scott Chamberlain Dec 05 '11 at 16:34
  • Giving you the answer as I am just going to write a second exe that this program launches and it will just use the SMO libs. – Scott Chamberlain Dec 07 '11 at 17:10
1

Why don't you just use the .Net Framework Registry classes?

If you use RegistryKey.OpenBaseKey and target the 64 bit RegistryView, it will open the 32-bit portion on 32-bit machines and the 64-bit portion on 64-bit machines.

Update

In order to handle the need to update the registry keys as a member of the administrators group, you could simply prompt the user for an administrator user name and password, impersonate that user while you perform your work.

For example, here is the class that we have for logging on the user:

public class SecurityGeneral
{
    [System.Runtime.InteropServices.DllImport("advapi32.dll", EntryPoint = "LogonUser", ExactSpelling = false, CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)]
    private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

    [System.Runtime.InteropServices.DllImport("kernel32.dll", EntryPoint = "CloseHandle", ExactSpelling = false, CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)]
    private static extern bool CloseHandle(IntPtr handle);

    private const int LOGON32_PROVIDER_DEFAULT = 0;
    //This parameter causes LogonUser to create a primary token.
    private const int LOGON32_LOGON_INTERACTIVE = 2;

    public static IntPtr LogonAsUser(string sUserName, string sPassword)
    {

        IntPtr tokenHandle = new IntPtr(0);

        tokenHandle = IntPtr.Zero;

        string[] asNameParts = null;
        string sName = null;
        string sDomain = "";

        asNameParts = sUserName.Split('\\');
        if (asNameParts.Length == 2)
        {
            sDomain = asNameParts[0];
            sName = asNameParts[1];
        }
        else
        {
            sName = asNameParts[0];
        }
        // Call LogonUser to obtain a handle to an access token.
        if (LogonUser(sName, sDomain, sPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref tokenHandle))
        {
            return tokenHandle;
        }
        else
        {
            return IntPtr.Zero;
        }
    }

    public static void LogonAsUserEnd(IntPtr oToken)
    {
        try
        {
            if (!((oToken == IntPtr.Zero)))
            {
                CloseHandle(oToken);
            }
        }
        catch
        {
        }
    }
}

Once you get the user name and password from the user, you can call this method, inserting your registry update code at the todo location:

    public void UpdateRegistryAsUser(string sUser, string sPassword)
    {
        IntPtr tokenHandle = default(IntPtr);

        tokenHandle = SecurityGeneral.LogonAsUser(sUser, sPassword);
        if (!((tokenHandle == IntPtr.Zero)))
        {
            WindowsImpersonationContext oImpersonatedUser = null;
            try
            {
                // Use the token handle returned by LogonUser.
                WindowsIdentity oNewIdentity = new WindowsIdentity(tokenHandle);

                oImpersonatedUser = oNewIdentity.Impersonate();

                // ToDo: add your registry updates here
            }
            finally
            {
                // Stop impersonating the user.
                if (oImpersonatedUser != null)
                {
                    oImpersonatedUser.Undo();
                }
                SecurityGeneral.LogonAsUserEnd(tokenHandle);
            }
        }
    }

This will cause the code to be executed in the context of the user that was supplied.

competent_tech
  • 44,465
  • 11
  • 90
  • 113
  • I don't do that because I would need to close my program and elevate it as a administrator to do the write, then I would need to close and open again to go back to a normal user. If you look at my code I am already doing your suggestions for the read, I just need elevated permissions to do the write, and if I am going to write a custom program to elevate I might as well just do the SMO library. – Scott Chamberlain Dec 06 '11 at 17:02
  • @ScottChamberlain: Alternatively, you could just prompt the user for an administrator user name and password, impersonate that user, then update the registry. I have updated the answer to include the impersonation code that we use in setup apps to secure directories, etc. – competent_tech Dec 06 '11 at 19:34