Windows Service with NLog - c#

I am creating a Windows Service which I want to use NLog with. I want the logs to be written to the install location of the service say something like:
PathToInstalledService\Logs\MyLog.txt
This is of course going to require administrator priveledges. So my question is, when creating the install for the Service, what account should I use on the ServiceProcessInstaller. I have been currently using LocalService, but this account does not have the required elevation.
Thanks.

During installation you should change the permissions of the 'Logs' directory to allow your service account to write files. Use the account with the least privileges needed to perform your services function, generally the NETWORK SERVICE account.
You can do this from an install class on the service:
void Installer1_AfterInstall(object sender, InstallEventArgs e)
{
string myAssembly = Path.GetFullPath(this.Context.Parameters["assemblypath"]);
string logPath = Path.Combine(Path.GetDirectoryName(myAssembly), "Logs");
Directory.CreateDirectory(logPath);
ReplacePermissions(logPath, WellKnownSidType.NetworkServiceSid, FileSystemRights.FullControl);
}
static void ReplacePermissions(string filepath, WellKnownSidType sidType, FileSystemRights allow)
{
FileSecurity sec = File.GetAccessControl(filepath);
SecurityIdentifier sid = new SecurityIdentifier(sidType, null);
sec.PurgeAccessRules(sid); //remove existing
sec.AddAccessRule(new FileSystemAccessRule(sid, allow, AccessControlType.Allow));
File.SetAccessControl(filepath, sec);
}

Related

How to use shared memory between service and User Interface App in c#?

If I write following in service:
mmfKernel = MemoryMappedFile.CreateNew("Global\\myMMF", 1024);
and following in user app:
MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("Global\\myMMF");
I get access denial to global mmf. How to grant access rights to mmfKernel to everyone with all possible rights?
But the other way round,
following in user app after I acquire SeCreateGlobalPrivilege:
mmfKernel = MemoryMappedFile.CreateNew("Global\\myMMF", 1024);
and following in service:
MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("Global\\myMMF");
I get path access denial while creating mmfKernel even though I have already SeCreateGlobalPrivilege.
How to create it as normal user but not admin?
If they run under different user accounts, and you think memory mapped files is a good IPC for your use case, you should use another CreateNew version, which accepts MemoryMappedFileSecurity argument with the permissions.
Here’s how to creates an access control list which gives full control permission to everyone:
var sid = new SecurityIdentifier( WellKnownSidType.WorldSid, null );
var ace = new AccessRule<MemoryMappedFileRights>( sid,
MemoryMappedFileRights.FullControl, AccessControlType.Allow );
var acl = new MemoryMappedFileSecurity();
acl.AddAccessRule( ace );
Then pass that acl object to MemoryMappedFile.CreateNew method.
Consider which permissions you actually need. If your desktop process only needs read access to the shared file, change the access rule accordingly. Also it’s a good idea to use more specific trustee instead of everyone, maybe AuthenticatedUserSid (=“Authenticated users”) in place of WorldSid (=“Everyone”), maybe a specific user account or security group.

How to change DCOM config identity programmatically

Is there any way to get the information about Launching identity of DCOM application programmatically. See the picture attached to understand what i mean.
I tried to use WMI
ManagementObjectSearcher s = new ManagementObjectSearcher(new ManagementScope(#"\\.\root\cimv2"), new ObjectQuery(
"select * from Win32_DCOMApplicationSetting where AppID='{048EB43E-2059-422F-95E0-557DA96038AF}'"))
ManagementObjectCollection dcomSett = s.Get();
var value = dcomSett.Cast<ManagementObject>().ToArray()
[0].Properties["RunAsUser"].Value;
but "RunAsUser" property was empty.
Also tried Interop.COMAdmin
COMAdmin.COMAdminCatalogClass catalog = (COMAdmin.COMAdminCatalogClass)new COMAdmin.COMAdminCatalog();
(COMAdmin.COMAdminCatalogCollection)catalog.GetCollection("Applications")
in this way i managed to get applications which are listed under the "COM+ Applications" node in the "Component Services" snap-in of MMC:
I'm new in COM, DCOM, COM+ stuff and sure that i missed something important.
After a while i found out why i used to get NULL in the first approach (ManagementObject).
You will receive:
NULL if identity is currently set to The launching user
"Interactive User" in case of "The interactive user"
some string with username in case of third option (see the first picture)
But still i need a way to change identity for items like Microsoft PowerPoint Slide under DCOM Config node in MMC.
In the DCOM config, if you are using a specific user for the identity and you want to update the password via code, you need to update it in the Local Security Authority (LSA). This is possible with Windows API calls. MS has some sample code for a utility called dcomperm that does it, and you can see how they implemented in C++. You could make the same calls in C#. See the SetRunAsPassword method here. They are using the method LsaOpenPolicy to get a handle to the policy and calling LsaStorePrivateData to update the password. Then they are adding "login as a batch job" access to the account (but that shouldn't be necessary if you are only changing the password).
This sample code on pinvoke.net looks like it is making the requisite calls, except for the optional part about granting the login as a batch job permission. Note the "key" in the LSA is in the format SCM:{GUID-of-DCOM-object} Example: SCM:{00000000-0000-0000-0000-000000000000}
Oh, and I should mention as an aside that if you wanted to change the RunAs user itself (i.e. the username), you'd need to also update that in the windows registry directly (AFAIK that's the only way to do it). DCOM entries are stored under HKLM\SOFTWARE\Classes\AppID. You can do that with WMI or just use the Registry classes in .NET.
This is very simple , you can get APPId from
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AppID\{048EB43E-2059-422F-95E0-557DA96038AF}
using
(RegistryKey dcomPPTIdentity = Registry.LocalMachine.OpenSubKey("Software\\Classes\\AppID\\{048EB43E-2059-422F-95E0-557DA96038AF}"))
{
if (dcomPPTIdentity != null)
{
Registry.SetValue(dcomPPTIdentity.ToString(), "RunAs", "userName");
}
}
I am using COMAdmin DLL successfully. Try something like this:
COMAdminCatalog catalog = new COMAdminCatalog();
COMAdminCatalogCollection applications = catalog.GetCollection("Applications");
applications.Populate();
for (int i = 0; i < applications.Count; i++)
{
COMAdminCatalogObject application = COMAppCollectionInUse.Item[i];
if (application.Name == "Your COM+ application name")
{
application.Value["Identity"] = "nt authority\\localservice"; // for example
}
}
This works for me on my development server. Keep in mind, it is run against the server directly on the server
using COMAdmin;
using System;
namespace ComComponents
{
class Program
{
static void Main(string[] args)
{
COMAdminCatalog catalog = new COMAdminCatalog();
COMAdminCatalogCollection applications = catalog.GetCollection("Applications");
applications.Populate();
for (int i = 0; i < applications.Count; i++)
{
COMAdminCatalogObject application = applications.Item[i];
Console.WriteLine(application.Name);
Console.WriteLine(application.Value["Identity"]);
}
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
}

Get user appdata path from windows service by running service under local profile

I have a windows application. In that i have retrieved appdata using environment variable. So it gives me following path
c:\document and settings\current user name\application data.
But when I retrieve the appdata path from windows service using environment variable i get following path
c:\windows\ServiceProfiles\LocalService\AppData\Local
so this appdata path is different from appdata path that i got from windows application environments variable appdata path.
I m running windows service under local profile. I know that if i change service profile to run under user then service appdata path and windows application appdata path matches but service prompts for username and password.
so my question is how to get user appdata path from service by running service under local profile without prompting for username and password?
I have also encountered in that problem and looked at your question but i at first sight didnot find an answer in it.
This is the Mohit shah Answer
"I found out that is not possible to get user appdata path from windows service by running service under profile "LocalSystem". So I used Environment.SpecialFolder.CommomAppData which gives me app data path C:\ProgramData when run on windows 7 and when used same thing in windows service, it also gived the same path and i also ran the service under profile "LocalSystem" so it did not prompt me for credentials. So this solved my problem."
#Mohit Shah Please mark this is as answer so that other can take help from that.
Windows service will always runs on SYSTEM level and hence it wont able to access user specific folder. Either as #ovais suggested you can store user data inside program data folder or you can use following approach.
You can use Windows management API's to get the current windows user name.Usually remaining path will be constant and hence you can construct remaining path.
Say for example, data is stored inside - "C:\Users\xyzUser\appdata\roaming..."
Only thing which is not constant here is "xyzUser" and "C"(User can install in different drives).
public static string GetWindowsUserAccountName()
{
string userName = string.Empty;
ManagementScope ms = new ManagementScope("\\\\.\\root\\cimv2");
ObjectQuery query = new ObjectQuery("select * from win32_computersystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(ms, query);
foreach (ManagementObject mo in searcher?.Get())
{
userName = mo["username"]?.ToString();
}
userName = userName?.Substring(userName.IndexOf(#"\") + 1);
return userName;
}
Drawback of this approach is, when you connected through remote connection, username will give you "NULL". So please be careful while using .
Windows folder you can get through following snippet.
public static string GetWindowsFolder()
{
string windowsFolder = string.Empty;
ManagementScope ms = new ManagementScope("\\\\.\\root\\cimv2");
ObjectQuery query = new ObjectQuery("SELECT * FROM Win32_OperatingSystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(ms, query);
foreach (ManagementObject m in searcher?.Get())
{
windowsFolder = m["WindowsDirectory"]?.ToString();
}
windowsFolder = windowsFolder.Substring(0, windowsFolder.IndexOf(#"\"));
return windowsFolder;
}

Access to the path is Denied When create file by windows service

i cannot create file in my windows service
and this is error
error In onstart method Access to the path 'C:\Windows\system32\BridgeServiceLog.txt' is denied.
protected override void OnStart(string[] args)
{
try
{
Logger.InitLogFile("BridgeServiceLog.txt");
Trace.WriteLine(Logger.logSwitch.TraceInfo, "Trace Started");
Trace.WriteLineIf(Logger.logSwitch.TraceInfo, "OnStart Started");
_bridgeServiceEventLog.WriteEntry("new OnStart");
if (Vytru.Platform.Bridge.Configuration.LicenseValidetor.ValidCountAndTypeDevices())
{
SharedData.InitializeBridge();
// WsInitializeBridge();
}
else
{
this.Stop();
_bridgeServiceEventLog.WriteEntry("LicenseValidetor Error");
}
_bridgeServiceEventLog.WriteEntry("end Start");
}
catch (Exception e)
{
Trace.WriteLineIf(Logger.logSwitch.TraceError, e.Message);
_bridgeServiceEventLog.WriteEntry("error In onstart method " + e.Message);
}
Trace.WriteLineIf(Logger.logSwitch.TraceInfo, "OnStart Ended");
}
The service user account probably doesn't have access to write to C:\Windows\System32 (which is the working directory of a Windows service).
Anyway, you shouldn't write to that folder. It is for the operating system - not your service.
You can use Environment.GetFolderPath to get a suitable path for writing files like log files in a way that will work any computer, not just your own computer. Here is an example.
var companyPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"MyCompany"
);
var productPath = Path.Combine(companyPath, "MyProduct");
var logFilePath = Path.Combine(productPath, "BridgeServiceLog.txt");
You should of course use suitable values for MyCompany and MyProduct.
When running a Windows Service the default working folder is <System drive>:\Windows\System32\.
Fortunately, not everyone can just access that folder.
There are two ways about this; write your file to another folder to which you do have rights, or run your service with administrator rights.
I would recommend the first option.
The easiest solution is to go the folder where you want to save a file, right click, properties, security, add a new user IIS_Users and give permission to write.
Use LocalSystem account on ProjectInstaller

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