Added remainder of AdvApi32 Windows service management functions and provided .NET native security access functions and classes.

pull/60/head
David Hall 2019-05-30 13:37:31 -06:00
parent 5e7350a41a
commit ca8fcb7bf5
5 changed files with 2048 additions and 154 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,12 @@
#if (NET20 || NET35 || NET40 || NET45)
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.ServiceProcess;
using Vanara.PInvoke;
using static Vanara.PInvoke.AdvApi32;
namespace Vanara.Extensions
@ -9,6 +14,38 @@ namespace Vanara.Extensions
/// <summary>Extension methods for <see cref="ServiceController"/>.</summary>
public static partial class ServiceControllerExtension
{
/// <summary>
/// Gets a <see cref="Security.AccessControl.ServiceControllerSecurity"/> object that encapsulates the specified type of access
/// control list (ACL) entries for the service described by the current <see cref="ServiceController"/> object.
/// </summary>
/// <param name="svc">The <see cref="ServiceController"/> object from which to get access control.</param>
/// <param name="includeSections">
/// One of the <see cref="AccessControlSections"/> values that specifies which group of access control entries to retrieve.
/// </param>
/// <returns>
/// A <see cref="Security.AccessControl.ServiceControllerSecurity"/> object that encapsulates the access control rules for the
/// current service.
/// </returns>
public static Security.AccessControl.ServiceControllerSecurity GetAccessControl(this ServiceController svc, AccessControlSections includeSections = AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group) => new Security.AccessControl.ServiceControllerSecurity(svc.ServiceHandle, includeSections);
/// <summary>
/// Applies access control list (ACL) entries described by a <see cref="Security.AccessControl.ServiceControllerSecurity"/> object to
/// the service described by the current <see cref="ServiceController"/> object.
/// </summary>
/// <param name="svc">The <see cref="ServiceController"/> object on which to set access control.</param>
/// <param name="serviceSecurity">
/// A <see cref="Security.AccessControl.ServiceControllerSecurity"/> object that describes an access control list (ACL) entry to
/// apply to the current service.
/// </param>
public static void SetAccessControl(this ServiceController svc, Security.AccessControl.ServiceControllerSecurity serviceSecurity)
{
if (serviceSecurity is null) throw new ArgumentNullException(nameof(serviceSecurity));
serviceSecurity.Persist(svc.ServiceHandle);
}
/// <summary>Changes the start mode of a service.</summary>
/// <param name="svc">The <see cref="ServiceController"/> instance.</param>
/// <param name="mode">The new start mode.</param>
public static void SetStartType(this ServiceController svc, ServiceStartMode mode)
{
using (var serviceHandle = svc.ServiceHandle)
@ -19,4 +56,275 @@ namespace Vanara.Extensions
}
}
}
namespace Vanara.Security.AccessControl
{
/// <summary>Defines the access rights to use when creating access and audit rules.</summary>
[Flags]
public enum ServiceControllerAccessRights : uint
{
/// <summary>Includes STANDARD_RIGHTS_REQUIRED in addition to all access rights in this table.</summary>
FullControl = ServiceAccessRights.SERVICE_ALL_ACCESS | AccessSystemSecurity | ACCESS_MASK.STANDARD_RIGHTS_ALL,
/// <summary>
/// Required to call the ChangeServiceConfig or ChangeServiceConfig2 function to change the service configuration. Because this
/// grants the caller the right to change the executable file that the system runs, it should be granted only to administrators.
/// </summary>
ChangeConfig = ServiceAccessRights.SERVICE_CHANGE_CONFIG,
/// <summary>Required to call the EnumDependentServices function to enumerate all the services dependent on the service.</summary>
EnumerateDependents = ServiceAccessRights.SERVICE_ENUMERATE_DEPENDENTS,
/// <summary>Required to call the ControlService function to ask the service to report its status immediately.</summary>
Interrogate = ServiceAccessRights.SERVICE_INTERROGATE,
/// <summary>Required to call the ControlService function to pause or continue the service.</summary>
Continue = ServiceAccessRights.SERVICE_PAUSE_CONTINUE,
/// <summary>Required to call the QueryServiceConfig and QueryServiceConfig2 functions to query the service configuration.</summary>
QueryConfig = ServiceAccessRights.SERVICE_QUERY_CONFIG,
/// <summary>
/// Required to call the QueryServiceStatus or QueryServiceStatusEx function to ask the service control manager about the status of
/// the service.
/// <para>Required to call the NotifyServiceStatusChange function to receive notification when a service changes status.</para>
/// </summary>
QueryStatus = ServiceAccessRights.SERVICE_QUERY_STATUS,
/// <summary>Required to call the StartService function to start the service.</summary>
Start = ServiceAccessRights.SERVICE_START,
/// <summary>Required to call the ControlService function to stop the service.</summary>
Stop = ServiceAccessRights.SERVICE_STOP,
/// <summary>Required to call the ControlService function to specify a user-defined control code.</summary>
UserDefinedControl = ServiceAccessRights.SERVICE_USER_DEFINED_CONTROL,
/// <summary>
/// Required to call the QueryServiceObjectSecurity or SetServiceObjectSecurity function to access the SACL. The proper way to obtain
/// this access is to enable the SE_SECURITY_NAMEprivilege in the caller's current access token, open the handle for
/// ACCESS_SYSTEM_SECURITY access, and then disable the privilege.
/// </summary>
AccessSystemSecurity = ACCESS_MASK.ACCESS_SYSTEM_SECURITY,
/// <summary>Required to call the DeleteService function to delete the service.</summary>
Delete = ACCESS_MASK.DELETE,
/// <summary>Required to call the QueryServiceObjectSecurity function to query the security descriptor of the service object.</summary>
ReadPermissions = ACCESS_MASK.READ_CONTROL,
/// <summary>
/// Required to call the SetServiceObjectSecurity function to modify the Dacl member of the service object's security descriptor.
/// </summary>
ChangePermissions = ACCESS_MASK.WRITE_DAC,
/// <summary>
/// Required to call the SetServiceObjectSecurity function to modify the Owner and Group members of the service object's security descriptor.
/// </summary>
TakeOwnership = ACCESS_MASK.WRITE_OWNER,
/// <summary>
/// Specifies the right to open and copy folders or files as read-only. This right includes the ReadData right,
/// ReadExtendedAttributes right, ReadAttributes right, and ReadPermissions right.
/// </summary>
Read = ACCESS_MASK.STANDARD_RIGHTS_READ | ServiceAccessRights.SERVICE_QUERY_CONFIG | ServiceAccessRights.SERVICE_QUERY_STATUS | ServiceAccessRights.SERVICE_INTERROGATE | ServiceAccessRights.SERVICE_ENUMERATE_DEPENDENTS,
/// <summary>
/// Specifies the right to create folders and files, and to add or remove data from files. This right includes the WriteData right,
/// AppendData right, WriteExtendedAttributes right, and WriteAttributes right.
/// </summary>
Write = ACCESS_MASK.STANDARD_RIGHTS_WRITE | ServiceAccessRights.SERVICE_CHANGE_CONFIG,
/// <summary>Specifies the right to run an application file.</summary>
Execute = ACCESS_MASK.STANDARD_RIGHTS_EXECUTE | ServiceAccessRights.SERVICE_START | ServiceAccessRights.SERVICE_STOP | ServiceAccessRights.SERVICE_PAUSE_CONTINUE | ServiceAccessRights.SERVICE_USER_DEFINED_CONTROL,
}
/// <summary>Represents an abstraction of an access control entry (ACE) that defines an access rule for a service.</summary>
/// <seealso cref="System.Security.AccessControl.AccessRule"/>
public sealed class ServiceControllerAccessRule : AccessRule
{
/// <summary>
/// Initializes a new instance of the <see cref="ServiceControllerAccessRule"/> class with the specified identity, access rights, and
/// access control type..
/// </summary>
/// <param name="identity">The name of the user account.</param>
/// <param name="rights">
/// One of the <see cref="ServiceControllerAccessRights"/> values that specifies the type of operation associated with the access rule.
/// </param>
/// <param name="type">One of the <see cref="AccessControlType"/> values that specifies whether to allow or deny the operation.</param>
public ServiceControllerAccessRule(string identity, ServiceControllerAccessRights rights, AccessControlType type)
: this(new NTAccount(identity), AccessMaskFromRights(rights), false, type)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ServiceControllerAccessRule"/> class with the specified identity, access rights, and
/// access control type..
/// </summary>
/// <param name="identity">An <see cref="IdentityReference"/> object that encapsulates a reference to a user account.</param>
/// <param name="rights">
/// One of the <see cref="ServiceControllerAccessRights"/> values that specifies the type of operation associated with the access rule.
/// </param>
/// <param name="type">One of the <see cref="AccessControlType"/> values that specifies whether to allow or deny the operation.</param>
public ServiceControllerAccessRule(IdentityReference identity, ServiceControllerAccessRights rights, AccessControlType type)
: this(identity, AccessMaskFromRights(rights), false, type)
{
}
internal ServiceControllerAccessRule(IdentityReference identity, int accessMask, bool isInherited, AccessControlType type)
: base(identity, accessMask, isInherited, InheritanceFlags.None, PropagationFlags.None, type)
{
}
/// <summary>
/// Gets the <see cref="ServiceControllerAccessRights"/> flags that are associated with the current
/// <see cref="ServiceControllerAccessRule"/> object.
/// </summary>
/// <value>A bitwise combination of the <see cref="ServiceControllerAccessRights"/> values.</value>
public ServiceControllerAccessRights AccessRights => (ServiceControllerAccessRights)AccessMask;
internal static int AccessMaskFromRights(ServiceControllerAccessRights rights) =>
rights >= 0 && rights <= ServiceControllerAccessRights.FullControl ?
(int)rights : throw new ArgumentOutOfRangeException(nameof(rights));
}
/// <summary>Represents an abstraction of an access control entry (ACE) that defines an audit rule for a service.</summary>
/// <seealso cref="System.Security.AccessControl.AuditRule"/>
public sealed class ServiceControllerAuditRule : AuditRule
{
/// <summary>
/// Initializes a new instance of the <see cref="ServiceControllerAuditRule"/> class for a user account specified in a
/// <see cref="IdentityReference"/> object.
/// </summary>
/// <param name="identity">An <see cref="IdentityReference"/> object that encapsulates a reference to a user account.</param>
/// <param name="rights">
/// One of the <see cref="ServiceControllerAccessRights"/> values that specifies the type of operation associated with the access rule.
/// </param>
/// <param name="flags">One of the <see cref="AuditFlags"/> values that specifies when to perform auditing.</param>
public ServiceControllerAuditRule(IdentityReference identity, ServiceControllerAccessRights rights, AuditFlags flags)
: this(identity, ServiceControllerAccessRule.AccessMaskFromRights(rights), false, flags)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ServiceControllerAuditRule"/> class for a user account specified in a
/// <see cref="IdentityReference"/> object.
/// </summary>
/// <param name="identity">The name of the user account.</param>
/// <param name="rights">
/// One of the <see cref="ServiceControllerAccessRights"/> values that specifies the type of operation associated with the access rule.
/// </param>
/// <param name="flags">One of the <see cref="AuditFlags"/> values that specifies when to perform auditing.</param>
public ServiceControllerAuditRule(string identity, ServiceControllerAccessRights rights, AuditFlags flags)
: this(new NTAccount(identity), ServiceControllerAccessRule.AccessMaskFromRights(rights), false, flags)
{
}
internal ServiceControllerAuditRule(IdentityReference identity, int accessMask, bool isInherited, AuditFlags flags)
: base(identity, accessMask, isInherited, InheritanceFlags.None, PropagationFlags.None, flags)
{
}
}
/// <summary>Represents the access control and audit security for a service.</summary>
/// <seealso cref="System.Security.AccessControl.NativeObjectSecurity"/>
public class ServiceControllerSecurity : NativeObjectSecurity
{
/// <summary>Initializes a new instance of the <see cref="ServiceControllerSecurity"/> class.</summary>
public ServiceControllerSecurity() : base(false, System.Security.AccessControl.ResourceType.Service) { }
internal ServiceControllerSecurity(SafeHandle handle, AccessControlSections includeSections) :
base(false, System.Security.AccessControl.ResourceType.Service, handle, includeSections)
{
}
/// <summary>
/// Gets the <see cref="T:System.Type"/> of the securable object associated with this
/// <see cref="T:System.Security.AccessControl.ObjectSecurity"/> object.
/// </summary>
public override Type AccessRightType => typeof(ServiceControllerAccessRights);
/// <summary>
/// Gets the <see cref="T:System.Type"/> of the object associated with the access rules of this
/// <see cref="T:System.Security.AccessControl.ObjectSecurity"/> object. The <see cref="T:System.Type"/> object must be an object
/// that can be cast as a <see cref="T:System.Security.Principal.SecurityIdentifier"/> object.
/// </summary>
public override Type AccessRuleType => typeof(ServiceControllerAccessRule);
/// <summary>
/// Gets the <see cref="T:System.Type"/> object associated with the audit rules of this
/// <see cref="T:System.Security.AccessControl.ObjectSecurity"/> object. The <see cref="T:System.Type"/> object must be an object
/// that can be cast as a <see cref="T:System.Security.Principal.SecurityIdentifier"/> object.
/// </summary>
public override Type AuditRuleType => typeof(ServiceControllerAuditRule);
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Security.AccessControl.AccessRule"/> class with the specified values.
/// </summary>
/// <param name="identityReference">The identity to which the access rule applies. It must be an object that can be cast as a <see cref="T:System.Security.Principal.SecurityIdentifier"/>.</param>
/// <param name="accessMask">
/// The access mask of this rule. The access mask is a 32-bit collection of anonymous bits, the meaning of which is defined by the
/// individual integrators.
/// </param>
/// <param name="isInherited">true if this rule is inherited from a parent container.</param>
/// <param name="inheritanceFlags">Specifies the inheritance properties of the access rule.</param>
/// <param name="propagationFlags">
/// Specifies whether inherited access rules are automatically propagated. The propagation flags are ignored if
/// <paramref name="inheritanceFlags"/> is set to <see cref="F:System.Security.AccessControl.InheritanceFlags.None"/>.
/// </param>
/// <param name="type">Specifies the valid access control type.</param>
/// <returns>The <see cref="T:System.Security.AccessControl.AccessRule"/> object that this method creates.</returns>
public override AccessRule AccessRuleFactory(IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) => new ServiceControllerAccessRule(identityReference, (ServiceControllerAccessRights)accessMask, type);
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Security.AccessControl.AuditRule"/> class with the specified values.
/// </summary>
/// <param name="identityReference">The identity to which the audit rule applies. It must be an object that can be cast as a <see cref="T:System.Security.Principal.SecurityIdentifier"/>.</param>
/// <param name="accessMask">
/// The access mask of this rule. The access mask is a 32-bit collection of anonymous bits, the meaning of which is defined by the
/// individual integrators.
/// </param>
/// <param name="isInherited">true if this rule is inherited from a parent container.</param>
/// <param name="inheritanceFlags">Specifies the inheritance properties of the audit rule.</param>
/// <param name="propagationFlags">
/// Specifies whether inherited audit rules are automatically propagated. The propagation flags are ignored if
/// <paramref name="inheritanceFlags"/> is set to <see cref="F:System.Security.AccessControl.InheritanceFlags.None"/>.
/// </param>
/// <param name="flags">Specifies the conditions for which the rule is audited.</param>
/// <returns>The <see cref="T:System.Security.AccessControl.AuditRule"/> object that this method creates.</returns>
public override AuditRule AuditRuleFactory(IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags) => new ServiceControllerAuditRule(identityReference, (ServiceControllerAccessRights)accessMask, flags);
internal void Persist(SafeHandle handle)
{
WriteLock();
try
{
var persistRules = GetAccessControlSectionsFromChanges();
if (persistRules == AccessControlSections.None)
return;
Persist(handle, persistRules);
OwnerModified = GroupModified = AuditRulesModified = AccessRulesModified = false;
}
finally
{
WriteUnlock();
}
}
private AccessControlSections GetAccessControlSectionsFromChanges()
{
var persistRules = AccessControlSections.None;
if (AccessRulesModified)
persistRules = AccessControlSections.Access;
if (AuditRulesModified)
persistRules |= AccessControlSections.Audit;
if (OwnerModified)
persistRules |= AccessControlSections.Owner;
if (GroupModified)
persistRules |= AccessControlSections.Group;
return persistRules;
}
}
}
#endif

View File

@ -378,30 +378,6 @@ namespace Vanara.PInvoke.Tests
Assert.That(LookupPrivilegeName(null, luid, sb, ref chSz), Is.False);
}
[Test()]
public void OpenCloseSCManager()
{
using (var scm = AdvApi32.OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_CONNECT))
{
AssertHandleIsValid(scm);
}
}
[Test()]
public void OpenCloseService()
{
using (var scm = AdvApi32.OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_CONNECT))
{
AssertHandleIsValid(scm);
//opens task scheduler service
using (var service = AdvApi32.OpenService(scm, "Schedule", ServiceAccessTypes.SERVICE_QUERY_STATUS))
{
AssertHandleIsValid(service);
}
}
}
[Test()]
public void PrivilegeCheckTest()
{
@ -439,27 +415,6 @@ namespace Vanara.PInvoke.Tests
}
}
[Test()]
public void QueryServiceStatus()
{
using (var scm = AdvApi32.OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_CONNECT))
{
AssertHandleIsValid(scm);
//opens task scheduler service
using (var service = AdvApi32.OpenService(scm, "Schedule", ServiceAccessTypes.SERVICE_QUERY_STATUS))
{
AssertHandleIsValid(service);
//query service status
var status = AdvApi32.QueryServiceStatusEx<SERVICE_STATUS_PROCESS>(service, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO);
Assert.That(status.dwServiceType, Is.EqualTo(ServiceTypes.SERVICE_WIN32).Or.EqualTo(ServiceTypes.SERVICE_WIN32_SHARE_PROCESS));
Assert.That(status.dwServiceFlags, Is.EqualTo(0));
}
}
}
[Test()]
public void RegNotifyChangeKeyValueTest()
{
@ -498,48 +453,6 @@ namespace Vanara.PInvoke.Tests
}
}
[Test()]
[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
public void StartStopService()
{
using (var scm = AdvApi32.OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_CONNECT))
{
AssertHandleIsValid(scm);
var access = ServiceAccessTypes.SERVICE_START | ServiceAccessTypes.SERVICE_STOP | ServiceAccessTypes.SERVICE_QUERY_STATUS;
//opens print spooler service
using (var service = AdvApi32.OpenService(scm, "Spooler", access))
{
AssertHandleIsValid(service);
//query service status
var status = AdvApi32.QueryServiceStatusEx<SERVICE_STATUS_PROCESS>(service, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO);
if (status.dwCurrentState == ServiceState.SERVICE_RUNNING)
{
var ret4 = AdvApi32.StopService(service, out var _);
if (!ret4) Win32Error.ThrowLastError();
WaitForServiceStatus(service, ServiceState.SERVICE_STOPPED);
var ret6 = AdvApi32.StartService(service);
if (!ret6) Win32Error.ThrowLastError();
}
else
{
var ret4 = AdvApi32.StartService(service);
if (!ret4) Win32Error.ThrowLastError();
WaitForServiceStatus(service, ServiceState.SERVICE_RUNNING);
var ret6 = AdvApi32.StopService(service, out var _);
if (!ret6) Win32Error.ThrowLastError();
}
}
}
}
[Test]
public void UserTest()
{
@ -557,6 +470,7 @@ namespace Vanara.PInvoke.Tests
TestContext.WriteLine($"Ace{i}: {ace.GetHeader().AceType}={domain}\\{account}; {ace.GetMask()}");
}
}
internal static SafeSecurityDescriptor GetSD(string filename, SECURITY_INFORMATION si = SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION)
{
var err = GetNamedSecurityInfo(filename, SE_OBJECT_TYPE.SE_FILE_OBJECT, si, out _, out _, out _, out _, out var pSD);
@ -564,34 +478,5 @@ namespace Vanara.PInvoke.Tests
Assert.That(!pSD.IsInvalid);
return pSD;
}
private static void AssertHandleIsValid(SafeSC_HANDLE handle)
{
if (handle.IsInvalid)
Win32Error.ThrowLastError();
Assert.That(handle.IsNull, Is.False);
Assert.That(handle.IsClosed, Is.False);
Assert.That(handle.IsInvalid, Is.False);
}
private static void WaitForServiceStatus(SafeSC_HANDLE service, ServiceState status)
{
//query service status again to check that it changed
var tests = 0;
while (tests < 40)
{
var status2 = AdvApi32.QueryServiceStatusEx<SERVICE_STATUS_PROCESS>(service, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO);
if (status2.dwCurrentState == status)
break;
Thread.Sleep(500);
tests++;
}
if (tests >= 40)
throw new TimeoutException($"Timed-out waiting for service status {status}");
}
}
}

View File

@ -0,0 +1,232 @@
using NUnit.Framework;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using static Vanara.PInvoke.AdvApi32;
namespace Vanara.PInvoke.Tests
{
[TestFixture()]
public class ServiceTests
{
private const string svcKey = "Windows Management Instrumentation";
private const string svcName = "Winmgmt";
private SafeSC_HANDLE hSvc;
private SafeSC_HANDLE hSvcMgr;
[OneTimeSetUp]
public void _Setup()
{
hSvcMgr = OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_ALL_ACCESS);
AssertHandleIsValid(hSvcMgr);
hSvc = OpenService(hSvcMgr, svcName, ServiceAccessTypes.SERVICE_ALL_ACCESS);
AssertHandleIsValid(hSvc);
}
[OneTimeTearDown]
public void _TearDown()
{
hSvc.Dispose();
hSvcMgr.Dispose();
}
[Test]
public void ControlServiceTest()
{
Assert.That(ControlService(hSvc, ServiceControl.SERVICE_CONTROL_PAUSE, out var status), Is.True);
Thread.Sleep((int)status.dwWaitHint);
Assert.That(GetState(hSvc), Is.EqualTo(ServiceState.SERVICE_PAUSED).Or.EqualTo(ServiceState.SERVICE_PAUSE_PENDING));
Assert.That(ControlService(hSvc, ServiceControl.SERVICE_CONTROL_CONTINUE, out status), Is.True);
Thread.Sleep((int)status.dwWaitHint);
Assert.That(GetState(hSvc), Is.EqualTo(ServiceState.SERVICE_RUNNING));
}
[Test]
public void EnumDependentServicesTest()
{
var l = EnumDependentServices(hSvc);
TestContext.WriteLine(string.Join("; ", l.Select(i => i.lpDisplayName)));
Assert.That(l, Is.Not.Empty);
}
[Test]
public void EnumServicesStatusExTest()
{
var l = EnumServicesStatusEx(hSvcMgr, ServiceTypes.SERVICE_DRIVER, SERVICE_STATE.SERVICE_ACTIVE);
TestContext.WriteLine(string.Join("; ", l.Select(i => i.lpDisplayName)));
Assert.That(l, Is.Not.Empty);
}
[Test]
public void EnumServicesStatusTest()
{
var l = EnumServicesStatus(hSvcMgr);
TestContext.WriteLine(string.Join("; ", l.Select(i => i.lpDisplayName)));
Assert.That(l, Is.Not.Empty);
}
[Test]
public void GetServiceDisplayNameTest()
{
var sb = new StringBuilder(1024, 1024);
var sz = (uint)sb.Capacity;
var ret = GetServiceDisplayName(hSvcMgr, svcName, sb, ref sz);
TestContext.WriteLine(ret ? sb.ToString() : $"Error: {Win32Error.GetLastError()}");
Assert.That(ret, Is.True);
Assert.That(sb.ToString(), Is.EqualTo(svcKey));
}
[Test]
public void GetServiceKeyNameTest()
{
var sb = new StringBuilder(1024, 1024);
var sz = (uint)sb.Capacity;
var ret = GetServiceKeyName(hSvcMgr, svcKey, sb, ref sz);
TestContext.WriteLine(ret ? sb.ToString() : $"Error: {Win32Error.GetLastError()}");
Assert.That(ret, Is.True);
Assert.That(sb.ToString(), Is.EqualTo(svcName));
}
[Test]
public void NotifyServiceStatusChangeTest()
{
var svcNotify = new SERVICE_NOTIFY_2
{
dwVersion = 2,
pfnNotifyCallback = ChangeDelegate
};
Thread.BeginThreadAffinity();
var ret = NotifyServiceStatusChange(hSvc, SERVICE_NOTIFY_FLAGS.SERVICE_NOTIFY_PAUSED | SERVICE_NOTIFY_FLAGS.SERVICE_NOTIFY_PAUSE_PENDING | SERVICE_NOTIFY_FLAGS.SERVICE_NOTIFY_CONTINUE_PENDING, ref svcNotify);
if (ret.Failed) TestContext.WriteLine(ret);
Assert.That(ret.Succeeded, Is.True);
new Thread(ThreadExec).Start();
Kernel32.SleepEx(10000, true);
Thread.EndThreadAffinity();
void ChangeDelegate(ref SERVICE_NOTIFY_2 pParameter)
{
TestContext.WriteLine(pParameter.ServiceStatus.dwCurrentState);
}
void ThreadExec()
{
using (var mgr = OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_ALL_ACCESS))
{
if (!mgr.IsInvalid)
{
using (var svc = OpenService(hSvcMgr, svcName, ServiceAccessTypes.SERVICE_ALL_ACCESS))
{
if (!svc.IsInvalid)
{
TestContext.WriteLine("Pausing...");
ControlService(svc, ServiceControl.SERVICE_CONTROL_PAUSE, out _);
Thread.Sleep(3000);
TestContext.WriteLine("Continuing...");
ControlService(svc, ServiceControl.SERVICE_CONTROL_CONTINUE, out _);
Thread.Sleep(3000);
}
}
}
}
}
}
[Test]
public void OpenCloseSCManagerTest()
{
using (var scm = OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_CONNECT))
{
AssertHandleIsValid(scm);
}
}
[Test]
public void OpenCloseServiceTest()
{
//opens task scheduler service
using (var service = OpenService(hSvcMgr, "Schedule", ServiceAccessTypes.SERVICE_QUERY_STATUS))
{
AssertHandleIsValid(service);
}
}
[Test]
public void QueryServiceStatusTest()
{
//query service status
var ret = QueryServiceStatus(hSvc, out var i);
TestContext.WriteLine(ret ? i.dwCurrentState.ToString() : $"Error: {Win32Error.GetLastError()}");
Assert.That(ret, Is.True);
}
[Test]
public void QueryServiceStatusExTest()
{
//query service status
var status = QueryServiceStatusEx<SERVICE_STATUS_PROCESS>(hSvc, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO);
Assert.That(status.dwServiceType, Is.EqualTo(ServiceTypes.SERVICE_WIN32_OWN_PROCESS | ServiceTypes.SERVICE_INTERACTIVE_PROCESS));
Assert.That(status.dwServiceFlags, Is.EqualTo(0));
}
[Test]
public void StartStopServiceTest()
{
//query service status
var status = QueryServiceStatusEx<SERVICE_STATUS_PROCESS>(hSvc, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO);
if (status.dwCurrentState == ServiceState.SERVICE_RUNNING)
{
var ret4 = StopService(hSvc, out var _);
if (!ret4) Win32Error.ThrowLastError();
WaitForServiceStatus(hSvc, ServiceState.SERVICE_STOPPED);
var ret6 = StartService(hSvc);
if (!ret6) Win32Error.ThrowLastError();
}
else
{
var ret4 = StartService(hSvc);
if (!ret4) Win32Error.ThrowLastError();
WaitForServiceStatus(hSvc, ServiceState.SERVICE_RUNNING);
var ret6 = StopService(hSvc, out var _);
if (!ret6) Win32Error.ThrowLastError();
}
}
private static void AssertHandleIsValid(SafeSC_HANDLE handle)
{
if (handle.IsInvalid)
Win32Error.ThrowLastError();
Assert.That(handle.IsNull, Is.False);
Assert.That(handle.IsClosed, Is.False);
Assert.That(handle.IsInvalid, Is.False);
}
private static ServiceState GetState(SC_HANDLE handle) => QueryServiceStatus(handle, out var i) ? i.dwCurrentState : throw Win32Error.GetLastError().GetException();
private static void WaitForServiceStatus(SafeSC_HANDLE service, ServiceState status)
{
//query service status again to check that it changed
var tests = 0;
while (tests < 40)
{
if (GetState(service) == status)
break;
Thread.Sleep(500);
tests++;
}
if (tests >= 40)
throw new TimeoutException($"Timed-out waiting for service status {status}");
}
}
}

View File

@ -40,6 +40,7 @@
<ItemGroup>
<Compile Include="AdvApi32\AdvApi32Tests.cs" />
<Compile Include="AdvApi32\AuditTests.cs" />
<Compile Include="AdvApi32\ServiceTests.cs" />
<Compile Include="AdvApi32\PSIDTests.cs" />
<Compile Include="Authz\AuthzTests.cs" />
<Compile Include="Secur32\SaslTests.cs" />