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;
}
Related
In our existing application we are using a windows service which has a DLL import to call some functions in native assembly. This has been working fine as long as the service where this native function is called is running as Network Service. It also works when the service runs as LocalSystem.
In order to set customized privileges , we have decided to add support for using a Service account. However simply running the service as a newly created windows user account results in Access Violation error during the Native method call.
Similarly, I tried with a domain account which has administrator permissions on the system to run the service, but still it results in the same problem.
I cannot find any documentation on MSDN where specific permission for user account are listed for DLL Import to work. Can anyone tell me what I might be doing wrong ?
C# Code
[DllImport("lmgr11.dll", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern string GetFeaturelist(short jobType, int flag);
C Code
char* WINAPI GetFeaturelist(int type, int flag)
{
char * featureList;
char ** features, ** tempFeatureList;
int counter = 0;
if (!lm_job)
{
return NULL;
}
features = tempFeatureList = lc_feat_list(lm_job,flag,NULL);
//loop to know the number of features
while(*tempFeatureList != NULL)
{
counter++;
tempFeatureList++;
}
featureList = (char*) CoTaskMemAlloc(counter * 50);
strcpy(featureList,"");
while(*features != NULL)
{
strcat(featureList,*features);
strcat(featureList,"$$&&#&&$$");
features++;
}
lc_log(lm_job, featureList);
return featureList;
}
The problem was that "featurelist" variable in the C code was null, and the below code was not enough to handle it.
while(*features != NULL)
Adding another check using the below code, avoids the crash.
features != NULL
As for the error occurring only when not running as "Network Service", the 3rd Party code (lc_feat_list) depended on an user specific registry entry in HKU, whereas the documentation incorrectly refers to a static registry entry under HKLM, which ultimately caused it to return a null value.
I've recently been working on a very nice Registry Editor.
However, certain Registry keys, pointed out below in Regedit, will not show up in my program, as they raise an error of insufficient privileges when opened, and so are caught by error handling and skipped:
Regedit:
My program:
As you can see, the SECURITY key is missing, and the SAM key is not expandable, even though I am running the program with administrator privileges.
This can obviously be fixed by making fake keys and putting them there, and just displaying an empty default value for them, however that isn't a concrete solution, just a way to make it seem to the user as if the issue is solved.
I was wondering if there is a way to fix the issue in a concrete way, or in other words, to receive registry access to those keys?
All they display is an empty default value any way, including the expandable SAM key - it just has a subkey named 'SAM' with an empty default value as well.
However, to the user, it's much better if the program displays exactly as in Regedit, as it means that it's a fully functional piece of software.
Thanks for the help.
Edit (code included):
public static void TreeViewItemExpanded(TreeViewItem sender)
{
if (sender.Items[0] is string)
{
sender.Items.Clear();
RegistryKey expandedKey = (RegistryKey)sender.Tag;
foreach (string key in expandedKey.GetSubKeyNames().OrderBy(x => x)) try { sender.Items.Add(CreateTreeViewItem(expandedKey.OpenSubKey(key))); } catch { }
}
}
private static TreeViewItem CreateTreeViewItem(RegistryKey key)
{
TreeViewItem treeViewItem = new TreeViewItem() { Header = new RegistryEditor_RegistryStructure_TreeView() { Name = Path.GetFileName(key.ToString()) }, Tag = key };
try { if (key.SubKeyCount > 0) treeViewItem.Items.Add("Loading..."); } catch { }
return treeViewItem;
}
You did not supply sample code to your routine, but I have a suspision that you are using a default registry security descriptor.
You can specify a security descriptor for a registry key when you call the RegCreateKeyEx or RegSetKeySecurity function.
When you call the RegOpenKeyEx function, the system checks the requested access rights against the key's security descriptor. If the user does not have the correct access to the registry key, the open operation fails. If an administrator needs access to the key, the solution is to enable the SE_TAKE_OWNERSHIP_NAME privilege and open the registry key with WRITE_OWNER access.
This information is taken from: MSDN:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms724878(v=vs.85).aspx
In C# You should be using the Registery Permission Class
https://msdn.microsoft.com/en-us/library/system.security.permissions.registrypermission(v=vs.110).aspx
A good example of how to handle Registry Permissions can be found here:
https://msdn.microsoft.com/en-us/library/microsoft.win32.registrykey.setaccesscontrol(v=vs.110).aspx
you need enable SE_RESTORE_PRIVILEGE and SE_BACKUP_PRIVILEGE and use RegOpenKeyEx or ZwOpenKeyEx with REG_OPTION_BACKUP_RESTORE flag (but this will be work only begin from Windows 7 and later versions of Windows)
If this flag is set, the function ignores the samDesired parameter and
attempts to open the key with the access required to backup or restore
the key. If the calling thread has the SE_BACKUP_NAME privilege
enabled, the key is opened with the ACCESS_SYSTEM_SECURITY and
KEY_READ access rights. If the calling thread has the SE_RESTORE_NAME
privilege enabled, beginning with Windows Vista, the key is opened
with the ACCESS_SYSTEM_SECURITY, DELETE and KEY_WRITE access rights.
If both privileges are enabled, the key has the combined access rights
for both privileges.
for example
#define LAA(se) {{se},SE_PRIVILEGE_ENABLED|SE_PRIVILEGE_ENABLED_BY_DEFAULT}
#define BEGIN_PRIVILEGES(tp, n) static const struct {ULONG PrivilegeCount;LUID_AND_ATTRIBUTES Privileges[n];} tp = {n,{
#define END_PRIVILEGES }};
ULONG AdjustBackupRestore()
{
HANDLE hToken;
if (OpenProcessToken(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
BEGIN_PRIVILEGES(tp, 2)
LAA(SE_RESTORE_PRIVILEGE),
LAA(SE_BACKUP_PRIVILEGE),
END_PRIVILEGES
AdjustTokenPrivileges(hToken, FALSE, (::PTOKEN_PRIVILEGES)&tp, 0, 0, 0);
ULONG err = GetLastError();
CloseHandle(hToken);
return err;
}
return GetLastError();
}
if (!AdjustBackupRestore())//called once on startup
{
HKEY hKey;
if (!RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SECURITY\\SAM", REG_OPTION_BACKUP_RESTORE|REG_OPTION_OPEN_LINK, 0, &hKey))
{
RegCloseKey(hKey);
}
}
however for get full power for registry editor/viewer I be use native api
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.
So I'm running this code
public static void ConvertToWma(string inFile, string outFile, string profileName)
{
// Create a WMEncoder object.
WMEncoder encoder = new WMEncoder();
ManualResetEvent stopped = new ManualResetEvent(false);
encoder.OnStateChange += delegate(WMENC_ENCODER_STATE enumState)
{
if (enumState == WMENC_ENCODER_STATE.WMENC_ENCODER_STOPPED)
stopped.Set();
};
// Retrieve the source group collection.
IWMEncSourceGroupCollection srcGrpColl = encoder.SourceGroupCollection;
// Add a source group to the collection.
IWMEncSourceGroup srcGrp = srcGrpColl.Add("SG_1");
// Add an audio source to the source group.
IWMEncSource srcAud = srcGrp.AddSource(WMENC_SOURCE_TYPE.WMENC_AUDIO);
srcAud.SetInput(inFile, "", "");
// Specify a file object in which to save encoded content.
IWMEncFile file = encoder.File;
file.LocalFileName = outFile;
// Choose a profile from the collection.
IWMEncProfileCollection proColl = encoder.ProfileCollection;
proColl.ProfileDirectory = AssemblyInformation.GetExecutingAssemblyDirectory();
proColl.Refresh();
IWMEncProfile pro;
for (int i = 0; i < proColl.Count; i++)
{
pro = proColl.Item(i);
if (pro.Name == profileName)
{
srcGrp.set_Profile(pro);
break;
}
}
// Start the encoding process.
// Wait until the encoding process stops before exiting the application.
encoder.SynchronizeOperation = false;
encoder.PrepareToEncode(true);
encoder.Start();
stopped.WaitOne();
}
And I get a COMException (0x80004005) when encoder.PrepareToEncode gets executed.
Some notes:
1) The process is spawned by an ASP.NET web service so it runs as NETWORK SERVICE
2) inFile and outFile are absolute local paths and their extensions are correct, in addition inFile definitely exists (this has been a source of problems in the past)
3) The program works when I run it as myself but doesn't work in the ASP.NET context.
This says to me its a security permission issue so in addition I've granted Full Control to the directory containing the program AND the directories containing the audio files to NETWORK SERVICE. So I really don't have any idea what more I can do on the security front. Any help?
Running WM Encoder SDK based app in windows service is not supported. It uses hidden windows for various reasons, and there isn't a desktop window in service. DRM would certainly fail with no user profile. Besides, even when you make your service talk to WME instance on a user's desktop, Microsoft only supports 4 concurrent requests per machine because the global lock in WME (I know, not pretty programming, but WME is old). For more scalable solutions, consider Windows Media Format SDK.
You may want to move your WM Encoder based app to Expression Encoder SDK as WM Encoder's support is ending.
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.