Reputation: 127593
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"
Upvotes: 3
Views: 1831
Reputation: 44941
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.
Upvotes: 1
Reputation: 16260
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.
Upvotes: 1