mirror of https://github.com/dahall/Vanara.git
Added remainder of AdvApi32 Windows service management functions and provided .NET native security access functions and classes.
parent
5e7350a41a
commit
ca8fcb7bf5
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
|
|
Loading…
Reference in New Issue