Correct way to deal with UAC in C# - c#

I have an application (Windows service) that is installed into a directory in the Program Files folder. Alongside this application is another WinForms application that is used to configure the service (amongst other things). When it does configuration, it saves changes to a config file that lives alongside the service.
When running on Vista/Win7, UAC prevents the user from saving to the config file. What I would like to do is:
put the shield icon next to the menu item used to configure
prompt for UAC permissions when this item is chosen
only show the icon/prompt when on an OS that requires it
only show the icon/prompt when permissions are required (eg. if the application is installed somewhere that does not require UAC permission)
I don't really want to run the whole application as an administrator, as it is also used for other purposes that do not require UAC permissions (so setting an application manifest file is not the correct solution). I'm also assuming (correct me if I'm wrong) that once UAC permissions have been granted, my existing process cannot perform the action and that I will need to start a new process.
How can I best achieve this?

This is fairly easy. Put a shield icon on the button that saves changes to the configuration file, instead of the menu item. This follows the Windows behavior of not requesting UAC permissions until the last moment. The button actually will launch your executable again as administrator with a special command line (that you decide) to perform the configuration file saving. Use a named pipe (be sure to give it the correct permissions) to pass the configuration data to your second instance if your don't want to use the command line for data passing.
For launching your executable:
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "YOUR EXE";
info.UseShellExecute = true;
info.Verb = "runas"; // Provides Run as Administrator
info.Arguments = "YOUR SPECIAL COMMAND LINE";
if (Process.Start(info) != null)
{
// The user accepted the UAC prompt.
}
This works also when UAC doesn't exist (Windows XP), because it will simply run as administrator if possible, or prompt for credentials. You can check whether the OS requires UAC by simply doing Environment.OSVersion.Version.Major == 6. 6 is both Windows Vista and 7. You can make sure you're using Windows by looking at Environment.OSVersion.Platform.
For detecting whether you're application is already admin, you can do this:
public static bool IsAdministrator()
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
if (identity != null)
{
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
return false;
}

Matthew Ferreira's answer goes in to the details on why you need to restart the whole application and what to do when to restart it, however he did not cover how to show the shield icon. Here is some code I use (I think I originally got it from another answer somewhere on this site) that will only show the shield icon when the program is not elevated
/// <summary>
/// Is a button with the UAC shield
/// </summary>
public partial class ElevatedButton : Button
{
/// <summary>
/// The constructor to create the button with a UAC shield if necessary.
/// </summary>
public ElevatedButton()
{
FlatStyle = FlatStyle.System;
if (!IsElevated()) ShowShield();
}
[DllImport("user32.dll")]
private static extern IntPtr
SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
private uint BCM_SETSHIELD = 0x0000160C;
private bool IsElevated()
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
private void ShowShield()
{
IntPtr wParam = new IntPtr(0);
IntPtr lParam = new IntPtr(1);
SendMessage(new HandleRef(this, Handle), BCM_SETSHIELD, wParam, lParam);
}
}
The button checks when it is being constructed if it is in a administrative context and if it is not it draws the shield icon on the button.
If you want the shield icon windows uses, here is a sneaky trick that returns the shield icon as a Bitmap object.

Related

How to set up a rule for a Store app using the Windows Firewall API?

So I already have a pretty good idea how to set up firewall rules programmatically using the INetFwPolicy2 and INetFwRule COM interface. However, how can I use the COM interop to set up a rule for a specific "Modern App"/"Metro App"/"Store App"?
If I use the Firewall MMC, I can go to:
rule -> Properties -> Programs and Services -> Application Packages
and allow/block specified packages there. But I have no idea how to do this in code. I have found the INetFwRule3 interface which provides LocalAppPackageId property, which is what I assume does all the magic. But the LocalAppPackageId contains an SID of the package rather than its name like microsoft.windows.photos_8wekyb3d8bbwe for example. So how can I block the package I want, when all I know is it's name? I guess I have to get the SID, so how do I find that? Is the SID's scope local (unique per machine), or can I just hard-code the SID once I find it and not bother looking up the SID dynamically?
SID for an app container can be found using the NetworkIsolationEnumAppContainers and ConvertSidToStringSid APIs. This is what Fiddler does in their AppContainer Loopback Exemption Utility (which is how I found the API).
If you care just about SID and nothing else, it's easier to use DeriveAppContainerSidFromAppContainerName/ConvertSidToStringSid combo. You don't even have to use ConvertSidToStringSid, .NET framework already provides the conversion:
private static string SidToString(IntPtr sid)
{
return new SecurityIdentifier(sid).Value;
}
Curiously enough, the DeriveAppContainerSidFromAppContainerName does not check whether the app container exists on the system, it seems to just take whatever input you throw at it and generate the SID from that information alone (like a hash function).
So the complete code:
public static string AppContainerNameToSid(string appContainerName)
{
var sid = IntPtr.Zero;
try
{
if (DeriveAppContainerSidFromAppContainerName(appContainerName, out sid) == 0)
return new SecurityIdentifier(sid).Value;
else
return null;
}
finally
{
if (sid != IntPtr.Zero)
FreeSid(sid);
}
}
[DllImport("userenv.dll", SetLastError = false, CharSet = CharSet.Unicode)]
private static extern int DeriveAppContainerSidFromAppContainerName(string appContainerName, out IntPtr sid);
[DllImport("advapi32.dll", SetLastError = false)]
private static extern IntPtr FreeSid(IntPtr sid);
Blocking internet connection for an UWP app is just like a traditional Desktop software.
Type "advanced security" in Windows 8/10 search then select "Windows Defender Firewall with Advanced Security".
In the newly opened window and in the left panel, go to "Inbound Rules" then in the right panel, select "New Rule..." then tap "Next" when "Program" is selected. Then click on "Browse..." and go to this address:
*C:\Program Files\WindowsApps*
Now find your app's name and enter its folder then select the file with "Application" type.
You'll be able to find the right "Application" type file just in a few seconds.
As an example, the folder name for the "Google" app is this:
GoogleInc.GoogleSearch_2.1.19.0_x64__yfg5n0ztvskxp
And the name of its app "Application" file type is this:
GoogleSearchUniversal
However, after choosing the correct "Application" file type click on "Open" for it.
Then tap "Next" and then choose "Block the connection" then "Next" then marking all types of connections then "Next" then call it and "Finish".
Just like a traditional Desktop software.

Change wallpaper with service

I'm trying to use a service installed to run as a specific user (me in this case) to change the wallpaper.
Here is my Wallpaper class which has a SetWallpaper function:
public sealed class Wallpaper
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError=true)]
private static extern Int32 SystemParametersInfo(
UInt32 action, UInt32 uParam, String vParam, UInt32 winIni);
private static readonly UInt32 SPI_SETDESKWALLPAPER = 0x14;
private static readonly UInt32 SPIF_UPDATEINIFILE = 0x01;
private static readonly UInt32 SPIF_SENDWININICHANGE = 0x02;
public static void SetWallpaper(String path)
{
System.IO.Stream s = new System.Net.WebClient().OpenRead(path.ToString());
System.Drawing.Image img = System.Drawing.Image.FromStream(s);
string tempPath = Path.Combine(Path.GetTempPath(), "wallpaper.bmp");
ImgurWallpaperSetter.ImgurWallpaperSetter.log(tempPath);
img.Save(tempPath, System.Drawing.Imaging.ImageFormat.Bmp);
SystemParametersInfo(SPI_SETDESKWALLPAPER, 1, tempPath,
SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
int error = Marshal.GetLastWin32Error();
ImgurWallpaperSetter.ImgurWallpaperSetter.log("Last error: " + error);
}
}
It works perfectly when I run SetWallpaper from a unit test, but it doesn't work at all when I install the service and start it.
Here is the service start code:
protected override void OnStart(string[] args) {
//WallpaperScheduler.ScheduleWallpaperFetch(DateTime.Now.Hour, DateTime.Now.Minute+1);
//Debugger.Launch();
Uri imageUrl = WallpaperRetriever.mostPopularImgurWallpaper();
log(imageUrl.AbsoluteUri);
Wallpaper.SetWallpaper(imageUrl.AbsoluteUri);
}
I've confirmed that it's downloading the image into my temp directory correctly, but it's not setting the wallpaper. It doesn't error out or log anything to the event logs.
Here's my service installed in the local service viewer:
Running it does nothing.
A similar thread I've read
Edit:
Added this code to run on my serviceInstaller_Committed event which should allow the service to interact with the desktop, but I see a huge delay between the service run and the actual switching of the wallpaper:
ConnectionOptions coOptions = new ConnectionOptions();
coOptions.Impersonation = ImpersonationLevel.Impersonate;
ManagementScope mgmtScope = new ManagementScope(#"root\CIMV2", coOptions);
mgmtScope.Connect();
ManagementObject wmiService;
wmiService = new ManagementObject(
"Win32_Service.Name='" + serviceInstaller1.ServiceName + "'"
);
ManagementBaseObject InParam = wmiService.GetMethodParameters("Change");
InParam["DesktopInteract"] = true;
ManagementBaseObject OutParam = wmiService.InvokeMethod("Change", InParam, null);
Edit2:
I've updated my service to log to the system events GetLastError(). Now I am seeing that the service is throwing error 1459 ("This operation requires an interactive window station."). However, this doesn't explain why my wallpaper does eventually switch (I think usually after waking from sleep). Updated Wallpaper class above as well.
Edit3
I've confirmed that after sleeping, the new wallpaper is set. Can anyone explain why this is? Could it be that I need to restart for the Interactive Desktop ability to be set?
Edit4
What I'm doing is feeling pretty hacky. Would it be better if I had the service do nothing but download wallpapers and potentially had another non-service application for changing the wallpaper if new wallpapers have been downloaded and the user is logged in?
Are you aware of Session 0 Isolation? It means that your service is running in a desktop that no user will ever log into, and that restricted environment may very well be affecting your program's behavior.
You say that the code "doesn't error out or log anything to the event logs", but, based on what you have shown, you need to improve the error checking to catch more subtle problems. For example, SystemParametersInfo() returns FALSE when it fails (and a subsequent call to GetLastError() may be very informative!) but your code doesn't check for that result. You can not rely on explicit exceptions alone.
Add this class: http://pastebin.com/ERsnqMEy
Use it like this: http://pastebin.com/RYvvT7bH
Works wonders in using WMI impersonating the logged in user from a windows system service. Best of luck.
In Services, go to Properties, and check "Use Local Account" and something like "Allow Using of Desktop". I'm not sure about names, because my Windows is in a different language, but you should be able to find it.

Installer Permissions Detection

I need some information regarding permissions/installers.
I am currently on a project where we need to have an installer for our application. The program is compatible with Windows XP+ so XP, Vista, 7 & 8. What I need to be able to do is detect whether the current user has permissions to be able to install our application.
I've found a couple of posts on the matter but none of them seem to give me the definitive answer I want/need.
As for our Architecture it is as follows:
We have a 'click once' application that contains a C++ application, a .NET 2.0 'Windows Application' & a .NET 4.0 'Windows Application' - the C++ application is fairly simple it basically just detects the version of .NET they have installed and delegates off to that Windows Application. Each of those Windows Applications are essentially the same - they basically do a Connection Speed check and if that passes it downloads and runs the appropriate MSI Installer for our Software.
The user obviously needs to be able to have permissions to be able to install our application which we need to add detection in somewhere along this chain (either around the speed check in the windows application or part of the MSI installer I'm not sure - this is what I need people's help with).
What is the best way to go about doing this and how?
From what I can tell they'll be some complexities around UAC (whether it's on, off & whether they are local admin's, domain admins or just a regular user, also if they are a domain user and not currently on the network). They'll also be some other complexities as we'll also need to compensate for XP which doesn't have UAC (in fact I'm not sure at all how to detect it for XP).
From what I've seen online there's some options doing it in code such as this: In .NET/C# test if process has administrative privileges
There's also some other options around a Manifest such as: How do I force my .NET application to run as administrator? - would a manifest type approach work on XP?
I've got a few options along this pipeline with where to add this in so what I'm looking for from the community is information on where/how to do this for all my requirements.
Any help that anyone could provide would be much appreciated.
Thanks,
Michael
Not sure on the best practice, but if you wanted to detect if a user has admin rights either by their account being in the Administrator group or if they have the ability to escalate to that role via UAC, have a look at the MSDN code sample for Self Escalation: http://code.msdn.microsoft.com/windowsdesktop/CSUACSelfElevation-644673d3/sourcecode?fileId=21729&pathId=1041468146
That example has a method MainForm.IsUserInAdminGroup() that looks at either the current user's token or their escalation token:
/// <summary>
/// The function checks whether the primary access token of the process belongs
/// to user account that is a member of the local Administrators group, even if
/// it currently is not elevated.
/// </summary>
/// <returns>
/// Returns true if the primary access token of the process belongs to user
/// account that is a member of the local Administrators group. Returns false
/// if the token does not.
/// </returns>
/// <exception cref="System.ComponentModel.Win32Exception">
/// When any native Windows API call fails, the function throws a Win32Exception
/// with the last error code.
/// </exception>
internal bool IsUserInAdminGroup()
{
bool fInAdminGroup = false;
SafeTokenHandle hToken = null;
SafeTokenHandle hTokenToCheck = null;
IntPtr pElevationType = IntPtr.Zero;
IntPtr pLinkedToken = IntPtr.Zero;
int cbSize = 0;
try
{
// Open the access token of the current process for query and duplicate.
if (!NativeMethods.OpenProcessToken(Process.GetCurrentProcess().Handle,
NativeMethods.TOKEN_QUERY | NativeMethods.TOKEN_DUPLICATE, out hToken))
{
throw new Win32Exception();
}
// Determine whether system is running Windows Vista or later operating
// systems (major version >= 6) because they support linked tokens, but
// previous versions (major version < 6) do not.
if (Environment.OSVersion.Version.Major >= 6)
{
// Running Windows Vista or later (major version >= 6).
// Determine token type: limited, elevated, or default.
// Allocate a buffer for the elevation type information.
cbSize = sizeof(TOKEN_ELEVATION_TYPE);
pElevationType = Marshal.AllocHGlobal(cbSize);
if (pElevationType == IntPtr.Zero)
{
throw new Win32Exception();
}
// Retrieve token elevation type information.
if (!NativeMethods.GetTokenInformation(hToken,
TOKEN_INFORMATION_CLASS.TokenElevationType, pElevationType,
cbSize, out cbSize))
{
throw new Win32Exception();
}
// Marshal the TOKEN_ELEVATION_TYPE enum from native to .NET.
TOKEN_ELEVATION_TYPE elevType = (TOKEN_ELEVATION_TYPE)
Marshal.ReadInt32(pElevationType);
// If limited, get the linked elevated token for further check.
if (elevType == TOKEN_ELEVATION_TYPE.TokenElevationTypeLimited)
{
// Allocate a buffer for the linked token.
cbSize = IntPtr.Size;
pLinkedToken = Marshal.AllocHGlobal(cbSize);
if (pLinkedToken == IntPtr.Zero)
{
throw new Win32Exception();
}
// Get the linked token.
if (!NativeMethods.GetTokenInformation(hToken,
TOKEN_INFORMATION_CLASS.TokenLinkedToken, pLinkedToken,
cbSize, out cbSize))
{
throw new Win32Exception();
}
// Marshal the linked token value from native to .NET.
IntPtr hLinkedToken = Marshal.ReadIntPtr(pLinkedToken);
hTokenToCheck = new SafeTokenHandle(hLinkedToken);
}
}
// CheckTokenMembership requires an impersonation token. If we just got
// a linked token, it already is an impersonation token. If we did not
// get a linked token, duplicate the original into an impersonation
// token for CheckTokenMembership.
if (hTokenToCheck == null)
{
if (!NativeMethods.DuplicateToken(hToken,
SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
out hTokenToCheck))
{
throw new Win32Exception();
}
}
// Check if the token to be checked contains admin SID.
WindowsIdentity id = new WindowsIdentity(hTokenToCheck.DangerousGetHandle());
WindowsPrincipal principal = new WindowsPrincipal(id);
fInAdminGroup = principal.IsInRole(WindowsBuiltInRole.Administrator);
}
finally
{
// Centralized cleanup for all allocated resources.
if (hToken != null)
{
hToken.Close();
hToken = null;
}
if (hTokenToCheck != null)
{
hTokenToCheck.Close();
hTokenToCheck = null;
}
if (pElevationType != IntPtr.Zero)
{
Marshal.FreeHGlobal(pElevationType);
pElevationType = IntPtr.Zero;
}
if (pLinkedToken != IntPtr.Zero)
{
Marshal.FreeHGlobal(pLinkedToken);
pLinkedToken = IntPtr.Zero;
}
}
return fInAdminGroup;
}

Access denied while getting process path

I am trying to get process path by pid but I'm getting Win32Exception (access id denied).
The code looks like this:
string path = Process.GetProcessById(pid).MainModule.FileName
I have tried using OpenProcess with GetModuleFileNameEx but OpenProcess is returning 0. I even tried enabling SeDebugPrivilege according to C# – How to enable SeDebugPrivilege but it didn't help.
The above code works for most of the processes but throws error for SynTPHelper.exe (Synaptics Pointing Device Helper) The application is running under the same username as my code. Both, my application and the process run in 64 bit.
Is it possible to retrieve the path without running my application as an administrator?
Edit
Task Manager is able to 'open file location' even though I'm not running it as an administrator.
Finally I managed to solve it. As it turned out there is new function in Vista and above for getting process path and new process access (PROCESS_QUERY_LIMITED_INFORMATION):
QueryFullProcessImageName
Here is the code that works from non-elevated process:
private static string GetExecutablePathAboveVista(UIntPtr dwProcessId)
{
StringBuilder buffer = new StringBuilder(1024);
IntPtr hprocess = OpenProcess(ProcessAccessFlags.PROCESS_QUERY_LIMITED_INFORMATION, false, dwProcessId);
if (hprocess != IntPtr.Zero)
{
try
{
int size = buffer.Capacity;
if (QueryFullProcessImageName(hprocess, 0, buff, out size))
{
return buffer.ToString();
}
}
finally
{
CloseHandle(hprocess);
}
}
return string.Empty;
}
Well, it is certainly not unheard of for services to remove access rights so that even an administrator cannot open the process. A service has enough privileges to do so, DRM components like audiodg.exe readily do so. A mouse pad helper doesn't strike me as something that would require such protection. But what the hey, why would anybody ever need to mess with a mouse pad helper?

Launching GUI App from Windows Service - Window Does Not Appear

I have written a simple windows service which will launch a exe specified in the
onstart() method of the service. After starting the service the exe got launched it only
presents in the memory but it doesnt show in the explorer.
I'm trying to launch a calc.exe from my code.it shows the exe in the memory but it
doesnt comes into my view(i.e) in the explorer.
Below is my code to launch the exe in the onStart() method
Process pr=new Process();
pr.StartInfo.FileName="calc.exe";
pr.StartInfo.WindowStyle=ProcessWindowStyle.Maximized;
pr.StartInfo.CreateNoWindow=false;
pr.Start();
// pr.WaitForExit();
Services run in other session on Vista or later and applications started directly from services are started in the same session by default. Starting applications in other sessions is possible - you have to find the id of the user session and use CreateProcessAsUser.
If more than one user is logged in and you need to start your program for all users you must find the ids of all sessions.
Here is sample code:
int session = Win32.WTSGetActiveConsoleSessionId();
if (session == 0xFFFFFFFF)
{
return false;
}
IntPtr userToken;
bool res = Win32.WTSQueryUserToken(session, out userToken);
if (!res)
{
this.log.WriteEntry("Error WTSQueryUserToken");
return false;
}
string path = GetPath();
string dir = Path.GetDirectoryName(path);
Win32.STARTUPINFO si = new Win32.STARTUPINFO();
si.lpDesktop = "winsta0\\default";
si.cb = Marshal.SizeOf(si);
Win32.PROCESS_INFORMATION pi = new Win32.PROCESS_INFORMATION();
Win32.SECURITY_ATTRIBUTES sa = new Win32.SECURITY_ATTRIBUTES();
sa.bInheritHandle = 0;
sa.nLength = Marshal.SizeOf(sa);
sa.lpSecurityDescriptor = IntPtr.Zero;
if (!Win32.CreateProcessAsUser(userToken, // user token
path, // exexutable path
string.Empty, // arguments
ref sa, // process security attributes ( none )
ref sa, // thread security attributes ( none )
false, // inherit handles?
0, // creation flags
IntPtr.Zero, // environment variables
dir, // current directory of the new process
ref si, // startup info
out pi)) // receive process information in pi
{
int error = Marshal.GetLastWin32Error();
this.log.WriteEntry("Error CreateProcessAsUser:" + error);
return false;
}
Services are run under different account privileges (LocalService/NetworkService etc)and hence they don't have access to your desktop (under your login account's control).
Services are meant to do their job silently and thats what they should do. (with the exception of logging something in windows event log when they have something important to say)
If you open your service's properties window, go to the Log On tab then check the "Allow service to interact with desktop" check box you will get the behavior you want. Also depending on what app you what to run you may need to change the log on account.
Services are not interactive by definition, so you shouldn't expect any user interface elements to show when you launch an application from a service.
It's by design...
Like already mentioned from the others a windows service is "normally" running under a separate account ("LocalSystem" or "NetworkService"). This is the reason why you might no see the UI of the program started by your service. Also services are not intended to have a UI, they act as a background service.
But also note that starting a application by a service can be a high security risk, because the application is running with the same privileges than your service is. Normally this would be the local system account.
I don't know what your are trying to achieve with your service, but consider to use the autostart function of windows instead of a service to run your application.

Categories

Resources