I have a service that I wrote that I need to deploy to a number (about 1100) devices. All of these devices are logged in as a regular user, not an administrator.
I can push out the service with our deployment software, which does run as an admin. Our security team does not want this service to run on the Local System account (for obvious reasons). What I've come up with is that the service will install as the Local System, but will then change it's log in account to a virtual user, which then needs access to a folder in Program Files (x86)
.
What I've found is that if I install the service (using remote admin access) via the command line, I can install the service, but it won't start.
When I look in the event logs, I get an UnauthorizedAccessException
error.
This I suspect is because the service is already running under the virtual user which doesn't have access to start the service. So how can I get around this?
In the main class for the service, I have this method, which is supposed to give the user access to the necessary folder:
private void GiveDirectoryAccess(string dir, string user)
{
try
{
DirectoryInfo directoryInfo = new DirectoryInfo(dir);
DirectorySecurity ds = directoryInfo.GetAccessControl();
ds.AddAccessRule(new FileSystemAccessRule(user, FileSystemRights.FullControl,
InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, PropagationFlags.NoPropagateInherit, AccessControlType.Allow));
directoryInfo.SetAccessControl(ds);
}
catch (Exception e)
{
SimpleLog.Log(e);
throw;
}
}
This is called right after the service is initialized:
public CheckRALVersionService()
{
InitializeComponent();
// Give directory access
string alhadminPath = System.IO.Path.Combine(pathToFolder, alhadmin);
GiveDirectoryAccess(alhadminPath, serviceUser);
string exeName = System.IO.Path.GetFileName(fullExeNameAndPath);
string tmppath = System.IO.Path.Combine(localdir, tmp);
SimpleLog.SetLogFile(logDir: tmppath, prefix: "debout." + exeName + "_", extension: "log");
watcher = new DirectoryWatcher(pathToFolder, alhadmin);
}
Then, in the ProjectInstaller
class, I am changing the user to the virtual user in the serviceInstaller1_Committed
method:
void serviceInstaller1_Committed(object sender, InstallEventArgs e)
{
using (ManagementObject service = new ManagementObject(new ManagementPath("Win32_Service.Name='RalConfigUpdate'")))
{
object[] wmiParams = new object[11];
wmiParams[6] = @"NT Service\RalConfigUpdate";
service.InvokeMethod("Change", wmiParams);
}
}
Do I need a helper service to give the access? Can what I want to do be done all within this service?
Thanks in advance.
After sitting on this for a bit, I found a solution. It may not be the most elegant, but it should work for my purposes. I had all of the "parts", but was just doing things in the wrong order.
Previously, I was trying to change the user during the install process, which wasn't working. What I ended up doing was allow the service to install as the LOCAL SYSTEM
account, and then change to the virtual account user during the OnStart
method of the actual program.
So:
protected override void OnStart(string[] args)
{
string alhadminPath = System.IO.Path.Combine(pathToFolder, alohadmin);
try
{
// Update the service state to start pending
ServiceStatus serviceStatus = new ServiceStatus
{
dwCurrentState = ServiceState.SERVICE_START_PENDING,
dwWaitHint = 100000
};
SetServiceStatus(this.ServiceHandle, ref serviceStatus);
// Update the logs
eventLog1.WriteEntry("Starting Service", EventLogEntryType.Information, eventId++);
SimpleLog.Info("RAL Config Update Service started");
serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING;
SetServiceStatus(this.ServiceHandle, ref serviceStatus);
// Change the user to the virutal user
using (ManagementObject service = new ManagementObject(new ManagementPath("Win32_Service.Name='RalConfigUpdate'")))
{
object[] wmiParams = new object[11];
wmiParams[6] = serviceUser;
service.InvokeMethod("Change", wmiParams);
}
GiveDirectoryAccess(alhadminPath, serviceUser);
}
catch (Exception e)
{
eventLog1.WriteEntry("Service failed to start", EventLogEntryType.Error, eventId++);
SimpleLog.Log(e);
throw;
}
}
This is working the way it should, and should also satisfy the security procedures. Thanks everyone.