Launching GUI App from Windows Service - Window Does Not Appear - c#

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.

Related

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.

Could not load C:\Windows\TEMP\Sybase.AdoNet4.AseClient.32bits.4.157.501.0\sbgse2.dll

I am running into a problem using the Sybase drivers. I get the following when Sybase tries to create a connection.
[AseException: Could not load C:\Windows\TEMP\Sybase.AdoNet4.AseClient.32bits.4.157.501.0\sbgse2.dll]
Sybase.Data.AseClient1.AseConnection.SaveAndLoadLibrary(String dirName, String dllName, Int32 bits) +419
Sybase.Data.AseClient1.AseConnection.LoadLibraries() +243
Sybase.Data.AseClient1.AseConnection..cctor() +5
[TypeInitializationException: The type initializer for 'Sybase.Data.AseClient1.AseConnection' threw an exception.]
Sybase.Data.AseClient1.AseConnection..ctor(AseConnection realConnection) +0
Sybase.Data.AseClient.AseConnection..ctor() +27
I see that Sybase compiles some unmanaged code as embedded resources and then at runtime if these files are not in the temp directory it copies them there.
In my case I see that the files do in fact exist. I can even delete them and see them get copied back at runtime.
I'm stumped now as to why they cannot be loaded.
This machine at one time was running .net 4.5. It was uninstalled and the .net 4.0 re-installed. Not sure that has anything to do with it.
Here is the relevant code in the Sybase driver
private static void LoadLibraries()
{
int bits = IntPtr.Size * 8;
string str = Path.Combine(Path.GetTempPath(), Assembly.GetExecutingAssembly().GetName().Name + "." + bits.ToString() + "bits." + ((object) Assembly.GetExecutingAssembly().GetName().Version).ToString());
if (!Directory.Exists(str))
Directory.CreateDirectory(str);
Sybase.Data.AseClient1.AseConnection.SaveAndLoadLibrary(str, "sbgse2.dll", bits);
Sybase.Data.AseClient1.AseConnection.SaveAndLoadLibrary(str, "sybdrvado20.dll", bits);
}
private static void SaveAndLoadLibrary(string dirName, string dllName, int bits)
{
string str = Path.Combine(dirName, dllName);
if (!File.Exists(str))
{
using (Stream manifestResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Sybase.Data.AseClient.Resources._" + bits.ToString() + "bits." + dllName))
{
try
{
using (Stream stream = (Stream) File.Create(str))
{
byte[] buffer = new byte[4096];
while (true)
{
int count = manifestResourceStream.Read(buffer, 0, 4096);
if (count >= 1)
stream.Write(buffer, 0, count);
else
break;
}
stream.Close();
}
}
catch
{
}
}
}
if (Sybase.Data.AseClient1.AseConnection.LoadLibrary(str) == IntPtr.Zero)
throw new AseException("Could not load " + str);
}
[DllImport("kernel32.dll")]
public static IntPtr LoadLibrary(string dllToLoad);
Any thoughts or ideas?
It appears (for some reason) the files copied to c:\windows\temp took on special permissions from the AppPool where this codes was initialized, causing other app pools to not be able to read the file. For this I would have expected an Access Denied type exception.
It's still a bit of magic and hand waving at best, but removing the special perms of the app pool from the files, deleting the temp directory, and restarting has kept the erroring application (in a different app pool) from throwing this exception. The special permissions also have not reappeared on the temp files.
Since all application pools identities have to be in the IIS worker process group, a solution is to grant Modify permissions to the C:\Windows\TEMP folder for the IIS group :
IIS_WPG for IIS 6
IIS_IUSRS for IIS 7
The rights will be correctly inherited (verified on a Windows Server 2008 and IIS 7).
What finally worked for me was assigning the application pools to the same identity.
Stop both applications pools
Delete directory "Windows\temp\Sybase.AdoNet4.AseClient.32bits.4.157.1000.0
Assign identity NETWORK SERVICE to both application pools
Start both application pools.
Everything should work.
Do Steps 1 and 2.
Created Custom Identity by going to "Control Panel > Administrative Tools > Computer Management > Local Users and Groups > Users"
Right click and select "New User..."
Add new username and password
Click Create button and close dialog
In IIS, select first Application Pool > Advanced Settings
Click on Identity field > click ... button
Select Custom account, click Set... button
Enter new user account information, click OK
Click OK to close Application Pool Identity dialog
Click OK to close Advanced Settings.
Repeat steps 11-16 for second Application Pool
Do step 4.
Final note, in the web.config you may need to specify multipleSiteBindingsEnabled="true" in the tag serviceHostingEnvironment in the section system.serviceModel.
Try changing the identity on your AppPool from ApplicationPoolIdentity to LocalSystem.

Get my application to be allowed access through firewall using c#

i am trying to get my application to be allowed through firewall, as I have to do ftp in active and passive mode is not an option as servers are not configured for that. so i tried the below code which compiles fine, I exexcute it using:
MyApp.Classes.INetFwMgr mgr = new MyApp.Classes.INetFwMgr();
mgr.AuthorizeApplication(Application.ProductName, Application.StartupPath,
NET_FW_SCOPE_.NET_FW_SCOPE_ALL,
NET_FW_IP_VERSION_.NET_FW_IP_VERSION_ANY);
And the class which does the job:
private const string CLSID_FIREWALL_MANAGER =
"{304CE942-6E39-40D8-943A-B913C40C9CD4}";
private static NetFwTypeLib.INetFwMgr GetFirewallManager()
{
Type objectType = Type.GetTypeFromCLSID(
new Guid(CLSID_FIREWALL_MANAGER));
return Activator.CreateInstance(objectType)
as NetFwTypeLib.INetFwMgr;
}
private const string PROGID_AUTHORIZED_APPLICATION =
"HNetCfg.FwAuthorizedApplication";
public bool AuthorizeApplication(string title, string applicationPath,
NET_FW_SCOPE_ scope, NET_FW_IP_VERSION_ ipVersion)
{
// Create the type from prog id
Type type = Type.GetTypeFromProgID(PROGID_AUTHORIZED_APPLICATION);
INetFwAuthorizedApplication auth = Activator.CreateInstance(type)
as INetFwAuthorizedApplication;
auth.Name = title;
auth.ProcessImageFileName = applicationPath; //Getting Access Denied Exception Here
auth.Scope = scope;
auth.IpVersion = ipVersion;
auth.Enabled = true;
NetFwTypeLib.INetFwMgr manager = GetFirewallManager();
try
{
manager.LocalPolicy.CurrentProfile.AuthorizedApplications.Add(auth);
}
catch (Exception ex)
{
return false;
}
return true;
}
using above code, but i get Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)) c# exception on line
auth.ProcessImageFileName = applicationPath;
any ideas what to do ?
Edit1: How would i run this as an admin using code?
Edit2: I also tried Putting <requestedExecutionLevel level="requireAdministrator" uiAccess="false" /> in manifest did not make a difference
P.S.This programs execution context can be Win 7, vista, xp
Firewall management is a system level security feature and has to be done outside of user mode application code. Configuration must be done by an administrator.
It is bad practice to write the code that you wrote and assume that your application will be run as administrator. Even if it is run by an administrator, you now have an application that "does FTP stuff" and "does firewall stuff". No application has ever been written like this.
You can write code that interacts with the system firewall, and that code must be run with elevated permissions. Typically such "helper applications" are never even created however as Windows (and every other OS) has all the necessary management tools shipped with the OS (i.e. wf.msc).
i have observed that if i change the order of ftp download statements to following windows dialog appears asking that do you want to allow this program access through firewall; if i click allow access the code works perfectly.
requestDownload = (FtpWebRequest)WebRequest.Create(uri);
requestDownload.UsePassive = false;
requestDownload.KeepAlive = false;
requestDownload.UseBinary = true;
requestDownload.Method = WebRequestMethods.Ftp.DownloadFile;
requestDownload.Credentials = new NetworkCredential(ftpInfoDownload[3], ftpInfoDownload[4]);
responseDownload = (FtpWebResponse)requestDownload.GetResponse();
Stream ftpStream = responseDownload.GetResponseStream();
Try opening the FTP ports in the firewall -- ports 20 and 21 -- and see if that solves your issue.
For running as a different user:
Run Code as a different user (C#)
As for getting through the firewall, have you talked to the person/group responsible for the firewall security? They may have some rules in place that you could use.

Correct way to deal with UAC in 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.

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?

Categories

Resources