diff --git a/Advapi32.cs b/Advapi32.cs
index a9a4fc7..caaa662 100755
--- a/Advapi32.cs
+++ b/Advapi32.cs
@@ -81,6 +81,162 @@ namespace winsw
}
}
+ static class LogonAsAService
+ {
+ public static void AddLogonAsAServiceRight(string Username)
+ {
+ //Needs to be at least XP or 2003 server
+ //https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832%28v=vs.85%29.aspx
+ System.OperatingSystem osInfo = System.Environment.OSVersion;
+
+ if (osInfo.Version.Major >= 5 && osInfo.Version.Minor >= 1)
+ {
+ var newuser = GetLocalAccountIfLocalAccount(Username);
+ //Trace.WriteLine("Username for Logon as A Service: " + newuser);
+ long rightexitcode = SetRight(newuser, PrivlegeRights.SeServiceLogonRight.ToString());
+ if (rightexitcode != 0)
+ {
+ Console.WriteLine("Failed to set logon as a service right");
+ Environment.Exit(1);
+ }
+ }
+ else
+ {
+ Console.WriteLine("Cannot set Logon as a Service right. Unsupported operating system detected");
+ }
+ }
+
+ private static string GetDomain(string s)
+ {
+ int stop = s.IndexOf("\\");
+ if (stop >= 0)
+ return s.Substring(0, stop);
+ else
+ return null;
+ }
+ private static string GetLogin(string s)
+ {
+ int stop = s.IndexOf("\\");
+ return (stop > -1) ? s.Substring(stop + 1, s.Length - stop - 1) : s;
+ }
+ private static string GetLocalAccountIfLocalAccount(string username)
+ {
+ var machinename = Environment.MachineName;
+ string domain = GetDomain(username);
+ if (domain == null || domain.ToLower() == machinename.ToLower() || domain == ".")
+ {
+ return GetLogin(username);
+ }
+ return username;
+ }
+
+ /// Adds a privilege to an account
+ /// Name of an account - "domain\account" or only "account"
+ /// Name ofthe privilege
+ /// The windows error code returned by LsaAddAccountRights
+ private static long SetRight(String accountName, String privilegeName)
+ {
+ long winErrorCode = 0; //contains the last error
+
+ //pointer an size for the SID
+ IntPtr sid = IntPtr.Zero;
+ int sidSize = 0;
+ //StringBuilder and size for the domain name
+ StringBuilder domainName = new StringBuilder();
+ int nameSize = 0;
+ //account-type variable for lookup
+ int accountType = 0;
+
+ //get required buffer size
+ Advapi32.LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType);
+
+ //allocate buffers
+ domainName = new StringBuilder(nameSize);
+ sid = Marshal.AllocHGlobal(sidSize);
+
+ //lookup the SID for the account
+ bool result = Advapi32.LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize,
+ ref accountType);
+
+ //say what you're doing
+ //Console.WriteLine("LookupAccountName result = " + result);
+ //Console.WriteLine("IsValidSid: " + Advapi32.IsValidSid(sid));
+ //Console.WriteLine("LookupAccountName domainName: " + domainName.ToString());
+
+ if (!result)
+ {
+ winErrorCode = Kernel32.GetLastError();
+ Console.WriteLine("LookupAccountName failed: " + winErrorCode);
+ }
+ else
+ {
+
+ //initialize an empty unicode-string
+ LSA_UNICODE_STRING systemName = new LSA_UNICODE_STRING();
+ //combine all policies
+ int access = (int)(
+ LSA_AccessPolicy.POLICY_AUDIT_LOG_ADMIN |
+ LSA_AccessPolicy.POLICY_CREATE_ACCOUNT |
+ LSA_AccessPolicy.POLICY_CREATE_PRIVILEGE |
+ LSA_AccessPolicy.POLICY_CREATE_SECRET |
+ LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION |
+ LSA_AccessPolicy.POLICY_LOOKUP_NAMES |
+ LSA_AccessPolicy.POLICY_NOTIFICATION |
+ LSA_AccessPolicy.POLICY_SERVER_ADMIN |
+ LSA_AccessPolicy.POLICY_SET_AUDIT_REQUIREMENTS |
+ LSA_AccessPolicy.POLICY_SET_DEFAULT_QUOTA_LIMITS |
+ LSA_AccessPolicy.POLICY_TRUST_ADMIN |
+ LSA_AccessPolicy.POLICY_VIEW_AUDIT_INFORMATION |
+ LSA_AccessPolicy.POLICY_VIEW_LOCAL_INFORMATION
+ );
+ //initialize a pointer for the policy handle
+ IntPtr policyHandle = IntPtr.Zero;
+
+ //these attributes are not used, but LsaOpenPolicy wants them to exists
+ LSA_OBJECT_ATTRIBUTES ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
+ ObjectAttributes.Length = 0;
+ ObjectAttributes.RootDirectory = IntPtr.Zero;
+ ObjectAttributes.Attributes = 0;
+ ObjectAttributes.SecurityDescriptor = IntPtr.Zero;
+ ObjectAttributes.SecurityQualityOfService = IntPtr.Zero;
+
+ //get a policy handle
+ uint resultPolicy = Advapi32.LsaOpenPolicy(ref systemName, ref ObjectAttributes, access, out policyHandle);
+ winErrorCode = Advapi32.LsaNtStatusToWinError(resultPolicy);
+
+ if (winErrorCode != 0)
+ {
+ Console.WriteLine("OpenPolicy failed: " + winErrorCode);
+ }
+ else
+ {
+ //Now that we have the SID an the policy,
+ //we can add rights to the account.
+
+ //initialize an unicode-string for the privilege name
+ LSA_UNICODE_STRING[] userRights = new LSA_UNICODE_STRING[1];
+ userRights[0] = new LSA_UNICODE_STRING();
+ userRights[0].Buffer = Marshal.StringToHGlobalUni(privilegeName);
+ userRights[0].Length = (UInt16)(privilegeName.Length * UnicodeEncoding.CharSize);
+ userRights[0].MaximumLength = (UInt16)((privilegeName.Length + 1) * UnicodeEncoding.CharSize);
+
+ //add the right to the account
+ uint res = Advapi32.LsaAddAccountRights(policyHandle, sid, userRights, 1);
+ winErrorCode = Advapi32.LsaNtStatusToWinError(res);
+ if (winErrorCode != 0)
+ {
+ Console.WriteLine("LsaAddAccountRights failed: " + winErrorCode);
+ }
+
+ Advapi32.LsaClose(policyHandle);
+ }
+ Advapi32.FreeSid(sid);
+ }
+
+ return winErrorCode;
+ }
+ }
+
///
/// Advapi32.dll wrapper for performing additional service related operations that are not
/// available in WMI.
@@ -105,10 +261,85 @@ namespace winsw
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseServiceHandle(IntPtr hSCObject);
- [DllImport("ADVAPI32.DLL")]
+ [DllImport("advapi32.DLL")]
internal static extern bool SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus);
+
+ [DllImport("advapi32.dll", PreserveSig = true)]
+ internal static extern UInt32 LsaOpenPolicy(ref LSA_UNICODE_STRING SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, Int32 DesiredAccess,
+ out IntPtr PolicyHandle);
+
+ [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
+ internal static extern uint LsaAddAccountRights(IntPtr PolicyHandle, IntPtr AccountSid, LSA_UNICODE_STRING[] UserRights, uint CountOfRights);
+
+ [DllImport("advapi32")]
+ internal static extern void FreeSid(IntPtr pSid);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, PreserveSig = true)]
+ internal static extern bool LookupAccountName(string lpSystemName, string lpAccountName, IntPtr psid, ref int cbsid, StringBuilder domainName,
+ ref int cbdomainLength, ref int use);
+
+ [DllImport("advapi32.dll")]
+ internal static extern bool IsValidSid(IntPtr pSid);
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ internal static extern uint LsaClose(IntPtr ObjectHandle);
+
+ [DllImport("advapi32.dll", SetLastError = false)]
+ internal static extern uint LsaNtStatusToWinError(uint status);
+
}
+ //http://msdn.microsoft.com/en-us/library/windows/desktop/bb545671(v=vs.85).aspx
+ internal enum PrivlegeRights
+ {
+ SeServiceLogonRight, //Required for an account to log on using the service logon type.
+ SeRemoteInteractiveLogonRight, //Required for an account to log on remotely using the interactive logon type.
+ SeNetworkLogonRight, //Required for an account to log on using the network logon type.
+ SeInteractiveLogonRight, //Required for an account to log on using the interactive logon type.
+ SeDenyServiceLogonRight, //Explicitly denies an account the right to log on using the service logon type.
+ SeDenyRemoteInteractiveLogonRight, //Explicitly denies an account the right to log on remotely using the interactive logon type.
+ SeDenyNetworkLogonRight, //Explicitly denies an account the right to log on using the network logon type.
+ SeDenyInteractiveLogonRight, //Explicitly denies an account the right to log on using the interactive logon type.
+ SeDenyBatchLogonRight, //Explicitly denies an account the right to log on using the batch logon type.
+ SeBatchLogonRight //Required for an account to log on using the batch logon type.
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct LSA_UNICODE_STRING
+ {
+ public UInt16 Length;
+ public UInt16 MaximumLength;
+ public IntPtr Buffer;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct LSA_OBJECT_ATTRIBUTES
+ {
+ public int Length;
+ public IntPtr RootDirectory;
+ public LSA_UNICODE_STRING ObjectName;
+ public UInt32 Attributes;
+ public IntPtr SecurityDescriptor;
+ public IntPtr SecurityQualityOfService;
+ }
+
+ // enum all policies
+ enum LSA_AccessPolicy : long
+ {
+ POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
+ POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
+ POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
+ POLICY_TRUST_ADMIN = 0x00000008L,
+ POLICY_CREATE_ACCOUNT = 0x00000010L,
+ POLICY_CREATE_SECRET = 0x00000020L,
+ POLICY_CREATE_PRIVILEGE = 0x00000040L,
+ POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
+ POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
+ POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
+ POLICY_SERVER_ADMIN = 0x00000400L,
+ POLICY_LOOKUP_NAMES = 0x00000800L,
+ POLICY_NOTIFICATION = 0x00001000L
+ }
internal enum SCM_ACCESS : uint
{
diff --git a/Kernel32.cs b/Kernel32.cs
index 56c3e5e..5fad8fa 100755
--- a/Kernel32.cs
+++ b/Kernel32.cs
@@ -21,6 +21,8 @@ namespace winsw
[In] ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
+ [DllImport("kernel32.dll")]
+ internal static extern int GetLastError();
}
[StructLayout(LayoutKind.Sequential)]
diff --git a/Main.cs b/Main.cs
index 1ce231e..e8adf1e 100644
--- a/Main.cs
+++ b/Main.cs
@@ -549,6 +549,7 @@ namespace winsw
if (args[0] == "install")
{
string username=null, password=null;
+ bool setallowlogonasaserviceright = false;
if (args.Count > 1 && args[1] == "/p")
{
// we expected username/password on stdin
@@ -556,6 +557,14 @@ namespace winsw
username = Console.ReadLine();
Console.Write("Password: ");
password = ReadPassword();
+ Console.WriteLine();
+ Console.Write("Set Account rights to allow log on as a service (y/n)?: ");
+ var keypressed = Console.ReadKey();
+ Console.WriteLine();
+ if (keypressed.Key == ConsoleKey.Y)
+ {
+ setallowlogonasaserviceright = true;
+ }
}
else
{
@@ -563,8 +572,14 @@ namespace winsw
{
username = d.ServiceAccountUser;
password = d.ServiceAccountPassword;
+ setallowlogonasaserviceright = d.AllowServiceAcountLogonRight;
}
}
+
+ if (setallowlogonasaserviceright)
+ {
+ LogonAsAService.AddLogonAsAServiceRight(username);
+ }
svc.Create (
d.Id,
@@ -699,6 +714,5 @@ namespace winsw
}
}
}
-
}
}
diff --git a/README.markdown b/README.markdown
index 82510d1..ee36f76 100644
--- a/README.markdown
+++ b/README.markdown
@@ -278,8 +278,11 @@ It is possible to specify the useraccount (and password) that the service will r
YOURDOMAIN
useraccount
Pa55w0rd
+ true
+The is optional. If set to true, will automatically set the "Allow Log On As A Service" right to the listed account.
+
### Working directory
Some services need to run with a working directory specified. To do this, specify a `` element like this:
diff --git a/ServiceDescriptor.cs b/ServiceDescriptor.cs
index 51c45bf..37bcd5c 100755
--- a/ServiceDescriptor.cs
+++ b/ServiceDescriptor.cs
@@ -540,6 +540,14 @@ namespace winsw
}
+ protected string AllowServiceLogon
+ {
+ get
+ {
+ return GetServiceAccountPart("allowservicelogon");
+ }
+ }
+
protected string serviceAccountDomain
{
get{
@@ -573,6 +581,22 @@ namespace winsw
return !string.IsNullOrEmpty(serviceAccountDomain) && !string.IsNullOrEmpty(serviceAccountName);
}
+ public bool AllowServiceAcountLogonRight
+ {
+ get
+ {
+ if (AllowServiceLogon != null)
+ {
+ bool parsedvalue = false;
+ if (Boolean.TryParse(AllowServiceLogon, out parsedvalue))
+ {
+ return parsedvalue;
+ }
+ }
+ return false;
+ }
+ }
+
///
/// Time to wait for the service to gracefully shutdown before we forcibly kill it
///
diff --git a/Tests/winswTests/ServiceDescriptorTests.cs b/Tests/winswTests/ServiceDescriptorTests.cs
index 0418850..764bdfb 100644
--- a/Tests/winswTests/ServiceDescriptorTests.cs
+++ b/Tests/winswTests/ServiceDescriptorTests.cs
@@ -17,6 +17,7 @@ namespace winswTests
private const string Username = "User";
private const string Password = "Password";
private const string Domain = "Domain";
+ private const string AllowServiceAccountLogonRight = "true";
[SetUp]
public void SetUp()
@@ -32,6 +33,7 @@ namespace winswTests
+ "" + Domain + ""
+ "" + Username + ""
+ "" + Password + ""
+ + "" + AllowServiceAccountLogonRight + ""
+ ""
+ ""
+ ExpectedWorkingDirectory
@@ -48,6 +50,12 @@ namespace winswTests
Assert.That(extendedServiceDescriptor.WorkingDirectory, Is.EqualTo(ExpectedWorkingDirectory));
}
+ [Test]
+ public void VerifyServiceLogonRight()
+ {
+ Assert.That(extendedServiceDescriptor.AllowServiceAcountLogonRight, Is.EqualTo(true));
+ }
+
[Test]
public void VerifyUsername()
{
@@ -153,5 +161,33 @@ namespace winswTests
Assert.That(logHandler.Period, Is.EqualTo(7));
Assert.That(logHandler.Pattern, Is.EqualTo("log pattern"));
}
+
+ [Test]
+ public void VerifyServiceLogonRightGraceful()
+ {
+ const string SeedXml=""
+ + ""
+ + "" + Domain + ""
+ + "" + Username + ""
+ + "" + Password + ""
+ + "true1"
+ + ""
+ + "";
+ var serviceDescriptor = ServiceDescriptor.FromXML(SeedXml);
+ Assert.That(serviceDescriptor.AllowServiceAcountLogonRight, Is.EqualTo(false));
+ }
+ [Test]
+ public void VerifyServiceLogonRightOmitted()
+ {
+ const string SeedXml = ""
+ + ""
+ + "" + Domain + ""
+ + "" + Username + ""
+ + "" + Password + ""
+ + ""
+ + "";
+ var serviceDescriptor = ServiceDescriptor.FromXML(SeedXml);
+ Assert.That(serviceDescriptor.AllowServiceAcountLogonRight, Is.EqualTo(false));
+ }
}
}