Avoid UAC prompts when calling another process using C# - c#

I'm developing an iventory software for my company that demands administrative rights (WMI calls, registry access, etc.). For convenience I do no want the UAC to prompt the user for clearance to execute the app (yes, I must force the app to run even if the the user doesn't want), and I can't disable the UAC via GPO (would be perfect but a pain in the ass). I first tried to pass an AD administrative account credentials to the inventory software using another process (Processinfo on C#) but the UAC prompts remained. After a few research I discovered that if use the local Administrator credential it wouldn't give me any annoying prompt but since my company's environment is a mess, there are many stations with different credentials other than the standardized. Does anyone have any idea of how I could do this? (Using .net C#).

I have accomplished this using the Task Scheduler Managed Wrapper. Be sure that you provide the local administrator group credential in setting up the task. Here's how I do it in my code:
using (TaskService ts = new TaskService())
{
try
{
//Create a new task definition and assign properties
TaskDefinition td = ts.NewTask();
td.Principal.RunLevel = TaskRunLevel.Highest;
td.RegistrationInfo.Description = "Paulos Task";
td.Triggers.Add(new TimeTrigger() { StartBoundary = Convert.ToDateTime("01-01-2003 00:00:01") });
// Create an action that will launch PauloApp whenever the trigger fires
td.Actions.Add(new ExecAction("PauloApp.exe", "", Environment.ExpandEnvironmentVariables(#"%ProgramFiles%\Paulo")));
td.Settings.DisallowStartIfOnBatteries = false;
td.Settings.StopIfGoingOnBatteries = false;
ts.RootFolder.RegisterTaskDefinition("PaulosTask", td,
TaskCreation.CreateOrUpdate, "Administrators", null,
TaskLogonType.Group);
// Register the task in the root folder
Microsoft.Win32.TaskScheduler.Task t = ts.FindTask("PaulosTask");
if (t != null)
t.Run();
else
//could not find PaulosTask
}//end try
catch (Exception e)
{
}
}//end using

Related

how to modify hosts file using c# with an admin rights and without manual intervention

I've been trying with number of ways but I'm unable to avoid the alert which says 'Do you want to open the application as administrator'. can some one suggest such piece of code which avoids/handles the alert to add new entry into hosts file.
Thanks in advance..
public bool ModifyHostsFile(string sEntryIPAddr, string sEntryURL)
{
try
{
WindowsPrincipal principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
bool administrativeMode = principal.IsInRole(WindowsBuiltInRole.Administrator);
if (!administrativeMode)
{
//ProcessStartInfo startInfo = new ProcessStartInfo();
//startInfo.Verb = "runas";
//startInfo.FileName = Application.ExecutablePath;
//Process.Start(startInfo);
//bool bStatus = GrantAccess(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), #"drivers\etc\hosts"));
using (StreamWriter w = File.AppendText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), #"drivers\etc\hosts")))
{
w.WriteLine(sEntryIPAddr + " " + sEntryURL);
}
Application.Exit();
}
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return false;
}
}
private bool GrantAccess(string fullPath)
{
DirectoryInfo dInfo = new DirectoryInfo(fullPath);
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);
return true;
}
No, you definitely need admin access to modify the file - Or else any virus could hijack the hosts file and redirect all browsers requests to a malicious site.
I don't have the rep to comment to ask some question I have but instead I will answer based on assumptions.
Your goal
To run an application with administrative rights
Why the UAC popup?
The popup you get asking for Admin Rights is to prevent applications from simply taking admin rights without your knowledge and thereby modifying your system's critical files
How to prevent it?
I HIGHLY recommend not taking this step but the first option that comes to mind is to disable UAC (User Account Controls) from your control panel
Alternative
An alternative could be to run the program as a scheduled task, set to run with the highest privileges. Then you can execute the program by running the scheduled task and have admin access without the UAC popup.
This can be done via command-line or via the GUI and still requires admin privileges for the creation of the schedules task

How can i stop windows service using C#, i want solution according to my code. Not another type [duplicate]

I have an application which consists of a service and an executable. Essentially it's a forms application that is responsible for starting and stopping a service under specific circumstances.
On Windows XP the application manages this fine using the following code:
ServiceController controller = new ServiceController();
controller.MachineName = ".";
controller.ServiceName = "XXXXXXXXXX";
controller.Stop();
controller.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 10));
controller.Start();
But on Windows 7, even though I've started the application as an administrator, I get the following exception:
System.InvalidOperationException: Cannot open XXXXXXXXXXXXX service on computer '.'. ---> System.ComponentModel.Win32Exception: Access is denied
--- End of inner exception stack trace ---
at System.ServiceProcess.ServiceController.GetServiceHandle(Int32 desiredAccess)
at System.ServiceProcess.ServiceController.Start(String[] args)
at System.ServiceProcess.ServiceController.Start()
Is there anything I can do programmatically to resolve this?
When you say that you started the application as Administrator, do you mean under an account in the Administrators group, or via a UAC prompt that requests administrator credentials? Without the UAC credentials prompt (or actually running as the Administrator account, not an account within the Administrators group), your application does not have permissions to modify services, so the exception you're seeing is correct.
This bit of example code can check if your application is running as an administrator, and if not, launch a UAC prompt.
public static class VistaSecurity
{
public static bool IsAdministrator()
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
if (null != identity)
{
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
return false;
}
public static Process RunProcess(string name, string arguments)
{
string path = Path.GetDirectoryName(name);
if (String.IsNullOrEmpty(path))
{
path = Environment.CurrentDirectory;
}
ProcessStartInfo info = new ProcessStartInfo
{
UseShellExecute = true,
WorkingDirectory = path,
FileName = name,
Arguments = arguments
};
if (!IsAdministrator())
{
info.Verb = "runas";
}
try
{
return Process.Start(info);
}
catch (Win32Exception ex)
{
Trace.WriteLine(ex);
}
return null;
}
}
FYI, if you don't understand why it's not working in Vista or 7 even if the current user is in the administrator group, here is what MSDN has to say
In Windows Vista, User Account Control (UAC) determines the privileges of a user. If you are a member of the Built-in Administrators group, you are assigned two run-time access tokens: a standard user access token and an administrator access token. By default, you are in the standard user role. When you attempt to perform a task that requires administrative privileges, you can dynamically elevate your role by using the Consent dialog box. The code that executes the IsInRole method does not display the Consent dialog box. The code returns false if you are in the standard user role, even if you are in the Built-in Administrators group. You can elevate your privileges before you execute the code by right-clicking the application icon and indicating that you want to run as an administrator.
I remember I was quite surprised at 1st when using 7 (I never used Vista).
You can also try setting the UAC for your application to "Run as Administrator" in code.

Editing registry value for newly created user

I have a .NET application that creates a new local user like so:
var principalContext = new PrincipalContext(ContextType.Machine);
var userPrincipal = new UserPrincipal(principalContext);
userPrincipal.Name = StandardUserName.Text;
userPrincipal.Description = "New local user";
userPrincipal.UserCannotChangePassword = true;
userPrincipal.PasswordNeverExpires = true;
userPrincipal.Save();
// Add user to the users group
var usersGroupPrincipal = GroupPrincipal.FindByIdentity(principalContext, UserGroupName.Text);
usersGroupPrincipal.Members.Add(userPrincipal);
usersGroupPrincipal.Save();
Next, I want to set some registry values for that user. For that, I need the user's SID:
private string GetSidForStandardUser()
{
var principalContext = new PrincipalContext(ContextType.Machine);
var standardUser = UserPrincipal.FindByIdentity(principalContext, StandardUserName.Text);
return standardUser.Sid.ToString();
}
And create a new subkey:
var key = string.Format("{0}{1}", GetSidForStandardUser(), keyString);
var subKey = Registry.Users.CreateSubKey(key, RegistryKeyPermissionCheck.ReadWriteSubTree);
However, I get an IOException on the call to CreateSubKey that tells me the Parameter is invalid. This happens because the subkey for that user does not exist yet until the user logs in for the first time. If I check regedit (under admin privileges) before logging in as the new user I can see that the SID does not exist under HKEY_Users. If I log in as the new user, then log out and back in as my original user and refresh regedit, the new SID exists.
My question is: is there a way to add subkeys for users that haven't logged in yet? I'd like to avoid having to log in as the new user and then back out halfway during the process.
I've since found a solution to the problem, but it's not pretty and it raises all sorts of new problems you have to deal with. Still, it works. I'm posting the solution here for my own reference and for others who may have need for it in the future.
The problem is that a user's registry hive is in their user profile folder (e.g. c:\users\username) in a file called NTUSER.DAT. However, a user's user profile folder isn't created until they log in, so when you create a new user there's no user profile yet and no NTUSER.DAT file containing their registry hive, so you can't edit any of their registry settings.
There's a trick, though: the user profile does get created when you run something under that user's credentials. There's an executable called runas.exe that lets you run a second executable under a specified user's credentials. If you create a new user and make it run, say, cmd.exe, like so:
runas /user:newuser cmd.exe
...it'll open a Cmd instance, but more importantly, create newuser's profile in the \users folder, including NTUSER.DAT.
Now, Cmd.exe leaves a command window open, which you can close manually but it's kind of clunky. https://superuser.com/a/389288 pointed me to rundll32.exe which, when run without any parameters, does nothing and exits immediately. Also, it's available on every Windows installation.
So, by calling runas and telling it to run rundll32.exe as the new user, we can create the user's profile without any further interaction:
Process.Start("runas", string.Format("/user:{0} rundll32", "newuser"));
Well... almost with no interaction. Runas opens a console window that requires you to enter the user's password, even if no password is set (it wants you to just press enter). This is annoying, but can be solved with some clever use of Pinvoke and optionally System.Windows.Forms to bring the window to the foreground and send it some keypresses:
var createProfileProcess = Process.Start("runas",
string.Format("/user:{0} rundll32",
"newuser"));
IntPtr hWnd;
do
{
createProfileProcess.Refresh();
hWnd = createProfileProcess.MainWindowHandle;
} while (hWnd.ToInt32() == 0);
SetForegroundWindow(hWnd);
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
This creates the profile, waits until the window has a handle, and then calls the Win32 function SetForegroundWindow() to bring it to the foreground. Then, it uses SendKeys.SendWait to send an enter key to that window. If you don't want to use a WinForms DLL, there are Win32 functions you can PInvoke for this, but for this particular scenario I found the winforms way quicker and easier.
This works, but reveals yet another problem: runas won't let you run stuff under an account that has no password. Superuser to the rescue again; https://superuser.com/a/470539 points out that there's a Local Policy called Limit local account use of blank passwords to console logon only that can be disabled to allow this exact scenario. I didn't want users to have to manually disable this policy, so I used the corresponding registry value LimitBlankPasswordUse in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa.
I now disable the policy by setting the registry value to 0, run the runas command to create the profile, then re-enable the policy by setting the value to 1 afterwards.(It would probably be cleaner to check the value first and only re-enable it if it was set in the first place, but for demonstration purposes this will do:
const string keyName = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Lsa";
Registry.SetValue(keyName, "LimitBlankPasswordUse", 0);
var createProfileProcess = Process.Start("runas",
string.Format("/user:{0} rundll32",
"newuser"));
IntPtr hWnd;
do
{
createProfileProcess.Refresh();
hWnd = createProfileProcess.MainWindowHandle;
} while (hWnd.ToInt32() == 0);
SetForegroundWindow(hWnd);
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
Registry.SetValue(keyName, "LimitBlankPasswordUse ", "1");
This works! However, the user's registry hive isn't loaded yet, so you still won't be able to read or write to it. For that, the process needs a couple of privileges, which you can again provide using some Win32 methods:
OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
out _myToken);
LookupPrivilegeValue(null, SE_RESTORE_NAME, out _restoreLuid);
LookupPrivilegeValue(null, SE_BACKUP_NAME, out _backupLuid);
_tokenPrivileges.Attr = SE_PRIVILEGE_ENABLED;
_tokenPrivileges.Luid = _restoreLuid;
_tokenPrivileges.Count = 1;
_tokenPrivileges2.Attr = SE_PRIVILEGE_ENABLED;
_tokenPrivileges2.Luid = _backupLuid;
_tokenPrivileges2.Count = 1;
AdjustTokenPrivileges(_myToken,
false,
ref _tokenPrivileges,
0,
IntPtr.Zero,
IntPtr.Zero);
AdjustTokenPrivileges(_myToken,
false,
ref _tokenPrivileges2,
0,
IntPtr.Zero,
IntPtr.Zero);
And finally load the hive using the new user's SID:
// Load the hive
var principalContext = new PrincipalContext(ContextType.Machine);
var standardUser = UserPrincipal.FindByIdentity(principalContext, "newuser");
var sid = standardUser.Sid.ToString();
StringBuilder path = new StringBuilder(4 * 1024);
int size = path.Capacity;
GetProfilesDirectory(path, ref size);
var filename = Path.Combine(path.ToString(), "newuser", "NTUSER.DAT");
Thread.Sleep(2000);
int retVal = RegLoadKey(HKEY_USERS, sid, filename);
I found most of this code in Load registry hive from C# fails.
RegLoadKey should return 0 on success. I noted that occasionally, it would fail to load the hive for no apparent reason. Reasoning that perhaps the necessary files in the user profile had not yet been created, I added a Thread.Sleep(2000) before loading the hive to give Windows time to create all the necessary files. There's probably a neater way to do this, but for now this'll work.
Now, you can load and set registry values for newuser using the newuser's SID, for instance:
var subKeyString = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced";
var keyString = string.Format("{0}{1}", sid, subKeyString);
var subKey = Registry.Users.CreateSubKey(keyString,
RegistryKeyPermissionCheck.ReadWriteSubTree);
subKey.SetValue("EnableBalloonTips", 0, RegistryValueKind.DWord);
Just to be sure, I also unloaded the registry hive when I was done. I'm not sure if it's required, but it seems like the neat thing to do:
var retVal = RegUnLoadKey(HKEY_USERS, GetSidForStandardUser());

Unlock Windows programmatically

In my current C# code I'm able to lock a Windows user session programmatically (same as Windows + L).
Since the app would still be running, is there any way to unlock the session from that C# program. User credentials are known. The app is running on Windows 7.
You'll need a custom windows credential provider to log in for you. Also, you'll need to save the user's credentials somewhere to log in. There are some samples in Windows SDK 7 https://www.microsoft.com/en-us/download/details.aspx?id=8279
There's a bunch of projects to get you started under Samples\security\credentialproviders.
To unlock the screen:
set the username / password in CSampleCredential::Initialize
set autologin to true in CSampleCredential::SetSelected
search the hardware provider sample for WM_TOGGLE_CONNECTED_STATUS message to see how to trigger the login
build some way to communicate with your app to trigger the unlock (local tcp server for example)
It's a pain in the ass, but it works.
Here is some hackery to do that: http://www.codeproject.com/Articles/16197/Remotely-Unlock-a-Windows-Workstation
Didn't test it myself though.
Not for .NET part, but you could also make your own custom Logon UI and inject some mechanism there. It can easily become security problem though.
var path = new ManagementPath();
path.NamespacePath = "\\ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption"; path.ClassName = "Win32_EncryptableVolume";
var scope = new ManagementScope(path, new ConnectionOptions() { Impersonation = ImpersonationLevel.Impersonate });
var management = new ManagementClass(scope, path, new ObjectGetOptions());
foreach (ManagementObject vol in management.GetInstances())
{
Console.WriteLine("----" + vol["DriveLetter"]);
switch ((uint)vol["ProtectionStatus"])
{
case 0:
Console.WriteLine("not protected by bitlocker");
break;
case 1:
Console.WriteLine("unlocked");
break;
case 2:
Console.WriteLine("locked");
break;
}
if ((uint)vol["ProtectionStatus"] == 2)
{
Console.WriteLine("unlock this driver ...");
vol.InvokeMethod("UnlockWithPassphrase", new object[] { "here your pwd" });
Console.WriteLine("unlock done.");
}
}
Note: this only works if you run Visual Studio as an administrator.
No, there is no way to do this, by design. What's your scenario and why do you need to lock/unlock the workstation?
Of course you can't unlock it. Unlocking a session requires the user physically be there to enter their account credentials. Allowing software to do this, even with saved credentials, would be a security issue for many of the other situations where workstation locking is used.

How to set "interact with desktop" in windows service installer

I have a windows service which runs under system account and executes some programs from time to time (yeah,yeah, I know that's a bad practice, but that's not my decision). I need to set the "interact with desktop" check, to see the gui of that executed programs, after the service is installed. I've tried several ways, putting the code below in AfterInstall or OnCommited event handlers of my service installer:
ConnectionOptions coOptions = new ConnectionOptions();
coOptions.Impersonation = ImpersonationLevel.Impersonate;
ManagementScope mgmtScope = new System.Management.ManagementScope(#"root\CIMV2", coOptions);
mgmtScope.Connect();
ManagementObject wmiService = new ManagementObject("Win32_Service.Name='" + ServiceMonitorInstaller.ServiceName + "'");
ManagementBaseObject InParam = wmiService.GetMethodParameters("Change");
InParam["DesktopInteract"] = true;
ManagementBaseObject OutParam = wmiService.InvokeMethod("Change", InParam, null);
or
RegistryKey ckey = Registry.LocalMachine.OpenSubKey(
#"SYSTEM\CurrentControlSet\Services\WindowsService1", true);
if(ckey != null)
{
if(ckey.GetValue("Type") != null)
{
ckey.SetValue("Type", ((int)ckey.GetValue("Type") | 256));
}
}
both of these methods "work". They set the check, but after I start the service it launches the exe - and gui isn't shown! So, if I stop the service, recheck and start it again - bingo! everything starts and is shown. The second way to achieve the result is to reboot - after it the gui is also shown.
So the question is: Is there a correct way to set "interact with desktop" check, so it'll start working without rechecks and reboots?
OS: Windows XP (haven't tried Vista and 7 yet...)
private static void SetInterActWithDeskTop()
{
var service = new System.Management.ManagementObject(
String.Format("WIN32_Service.Name='{0}'", "YourServiceName"));
try
{
var paramList = new object[11];
paramList[5] = true;
service.InvokeMethod("Change", paramList);
}
finally
{
service.Dispose();
}
}
And finally after searching the internet for a week - I've found a great working solution:
http://asprosys.blogspot.com/2009/03/allow-service-to-interact-with-desktop.html
Find the desktop to launch into. This
may seem facetious but it isn't as
simple as it seems. With Terminal
Services and Fast User Switching there
can be multiple interactive users
logged on to the computer at the same
time. If you want the user that is
currently sitting at the physical
console then you're in luck, the
Terminal Services API call
WTSGetActiveConsoleSessionId will get
you the session ID you need. If your
needs are more complex (i.e. you need
to interact with a specific user on a
TS server or you need the name of the
window station in a non-interactive
session) you'll need to enumerate the
Terminal Server sessions with
WTSEnumerateSessions and check the
session for the information you need
with WTSGetSessionInformation.
Now you know what session you need to
interact with and you have its ID.
This is the key to the whole process,
using WTSQueryUserToken and the
session ID you can now retrieve the
token of the user logged on to the
target session. This completely
mitigates the security problem of the
'interact with the desktop' setting,
the launched process will not be
running with the LOCAL SYSTEM
credentials but with the same
credentials as the user that is
already logged on to that session! No
privilege elevation.
Using CreateProcessAsUser and the
token we have retrieved we can launch
the process in the normal way and it
will run in the target session with
the target user's credentials. There
are a couple of caveats, both
lpCurrentDirectory and lpEnvironment
must point to valid values - the
normal default resolution methods for
these parameters don't work for
cross-session launching. You can use
CreateEnvironmentBlock to create a
default environment block for the
target user.
There is source code of the working project attached.
Same as Heisa but with WMI. (code is Powershell, but can be easily ported to C#)
if ($svc = gwmi win32_service|?{$_.name -eq $svcname})
{
try {
$null = $svc.change($svc.displayname,$svc.pathname,16,1,`
"Manual",$false,$svc.startname,$null,$null,$null,$null)
write-host "Change made"
catch { throw "Error: $_" }
} else
{ throw "Service $svcname not installed" }
See MSDN: Service Change() method for param description.

Categories