Added some missing functions, structs and enums to winsvc.cs and tests

Added QueryServiceStatusEx SERVICE_STATUS_PROCESS and more and test

Removed redundant CloseServiceHandle calls

Added Start/Stop/Delete/Control service functions

Added Start/Stop/Delete/Control service functions and appveyor for testing

Fixed version in appveyor.yml

Reducing msbuild verbosity

Improved AdvApi32 tests

Improve StartStop test method

Fixed StartService arguments types

Added ControlService, Fixed ControlServiceEx

Delete appveyor.yml
pull/38/head
Luigi Grilli 2019-02-07 00:52:59 +00:00
parent 1efc1993f2
commit dd15c0478c
5 changed files with 771 additions and 29 deletions

View File

@ -61,8 +61,13 @@ namespace Vanara.InteropServices
/// <returns>SafeHGlobalHandle object to an native (unmanaged) Unicode string</returns>
public SafeHGlobalHandle(string s) : base(s) { }
/// <summary>Initializes a new instance of the <see cref="SafeHGlobalHandle"/> class.</summary>
[ExcludeFromCodeCoverage]
/// <summary>Initializes a new instance of the <see cref="SafeHGlobalHandle"/> class.</summary>
/// <param name="structureType">The type of a structure</param>
/// <exception cref="System.ArgumentOutOfRangeException">size - The value of this argument must be non-negative</exception>
public SafeHGlobalHandle(Type structureType) : base(Marshal.SizeOf(structureType)) { }
/// <summary>Initializes a new instance of the <see cref="SafeHGlobalHandle"/> class.</summary>
[ExcludeFromCodeCoverage]
internal SafeHGlobalHandle() : base(0) { }
/// <summary>Represents a NULL memory pointer.</summary>

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Vanara.Extensions;
@ -259,7 +259,7 @@ namespace Vanara.PInvoke
/// <summary>The current state of the service.</summary>
[PInvokeData("winsvc.h", MSDNShortId = "d268609b-d442-4d0f-9d49-ed23fee84961")]
public enum ServiceState
public enum ServiceState : uint
{
/// <summary>The service continue is pending.</summary>
SERVICE_CONTINUE_PENDING = 0x00000005,
@ -283,9 +283,69 @@ namespace Vanara.PInvoke
SERVICE_STOPPED = 0x00000001,
}
/// <summary>Service trigger action types referenced by <see cref="SERVICE_TRIGGER"/>.</summary>
[PInvokeData("winsvc.h", MSDNShortId = "a57aa702-40a2-4880-80db-6c4f43c3e7ea")]
public enum ServiceTriggerAction
/// <summary>
/// Service control codes to be used with <see cref="ControlService"/> and <see cref="ControlServiceEx"/>
/// </summary>
public enum ServiceControl : uint
{
/// <summary>Notifies a service that it should stop. The hService handle must have the SERVICE_STOP access right. After sending the stop request to a service, you should not send other controls to the service.</summary>
SERVICE_CONTROL_STOP = 0x00000001,
/// <summary>Notifies a service that it should pause. The hService handle must have the SERVICE_PAUSE_CONTINUE access right.</summary>
SERVICE_CONTROL_PAUSE = 0x00000002,
/// <summary>Notifies a paused service that it should resume. The hService handle must have the SERVICE_PAUSE_CONTINUE access right.</summary>
SERVICE_CONTROL_CONTINUE = 0x00000003,
/// <summary>
/// Notifies a service that it should report its current status information to the service control manager. The hService handle must have the SERVICE_INTERROGATE access right.
/// Note that this control is not generally useful as the SCM is aware of the current state of the service
/// </summary>
SERVICE_CONTROL_INTERROGATE = 0x00000004,
/// <summary></summary>
SERVICE_CONTROL_SHUTDOWN = 0x00000005,
/// <summary>Notifies a service that its startup parameters have changed. The hService handle must have the SERVICE_PAUSE_CONTINUE access right.</summary>
SERVICE_CONTROL_PARAMCHANGE = 0x00000006,
/// <summary>Notifies a network service that there is a new component for binding. The hService handle must have the SERVICE_PAUSE_CONTINUE access right. However, this control code has been deprecated; use Plug and Play functionality instead.</summary>
SERVICE_CONTROL_NETBINDADD = 0x00000007,
/// <summary>Notifies a network service that a component for binding has been removed. The hService handle must have the SERVICE_PAUSE_CONTINUE access right. However, this control code has been deprecated; use Plug and Play functionality instead.</summary>
SERVICE_CONTROL_NETBINDREMOVE = 0x00000008,
/// <summary>Notifies a network service that a disabled binding has been enabled. The hService handle must have the SERVICE_PAUSE_CONTINUE access right. However, this control code has been deprecated; use Plug and Play functionality instead.</summary>
SERVICE_CONTROL_NETBINDENABLE = 0x00000009,
/// <summary>Notifies a network service that one of its bindings has been disabled. The hService handle must have the SERVICE_PAUSE_CONTINUE access right. However, this control code has been deprecated; use Plug and Play functionality instead.</summary>
SERVICE_CONTROL_NETBINDDISABLE = 0x0000000A,
/// <summary></summary>
SERVICE_CONTROL_DEVICEEVENT = 0x0000000B,
/// <summary></summary>
SERVICE_CONTROL_HARDWAREPROFILECHANGE = 0x0000000C,
/// <summary></summary>
SERVICE_CONTROL_POWEREVENT = 0x0000000D,
/// <summary></summary>
SERVICE_CONTROL_SESSIONCHANGE = 0x0000000E,
/// <summary></summary>
SERVICE_CONTROL_PRESHUTDOWN = 0x0000000F,
/// <summary></summary>
SERVICE_CONTROL_TIMECHANGE = 0x00000010,
//#define SERVICE_CONTROL_USER_LOGOFF = 0x00000011
/// <summary></summary>
SERVICE_CONTROL_TRIGGEREVENT = 0x00000020,
//reserved for internal use = 0x00000021
//reserved for internal use = 0x00000050
/// <summary></summary>
SERVICE_CONTROL_LOWRESOURCES = 0x00000060,
/// <summary></summary>
SERVICE_CONTROL_SYSTEMLOWRESOURCES = 0x00000061
}
/// <summary>
/// Info levels for <see cref="ControlServiceEx"/>
/// </summary>
public enum ServiceInfoLevels : uint
{
[CorrespondingType(typeof(SERVICE_CONTROL_STATUS_REASON_PARAMS))]
SERVICE_CONTROL_STATUS_REASON_INFO = 1
}
/// <summary>Service trigger action types referenced by <see cref="SERVICE_TRIGGER"/>.</summary>
[PInvokeData("winsvc.h", MSDNShortId = "a57aa702-40a2-4880-80db-6c4f43c3e7ea")]
public enum ServiceTriggerAction : uint
{
/// <summary>Start the service when the specified trigger event occurs.</summary>
SERVICE_TRIGGER_ACTION_SERVICE_START = 1,
@ -1136,7 +1196,7 @@ namespace Vanara.PInvoke
// lpMachineName, LPCSTR lpDatabaseName, DWORD dwDesiredAccess );
[DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)]
[PInvokeData("winsvc.h", MSDNShortId = "a0237989-e5a7-4a3a-ab23-e2474a995341")]
public static extern SafeSC_HANDLE OpenSCManager(string lpMachineName, string lpDatabaseName, uint dwDesiredAccess);
public static extern SafeSC_HANDLE OpenSCManager(string lpMachineName, string lpDatabaseName, ScManagerAccessTypes dwDesiredAccess);
/// <summary>
/// <para>Opens an existing service.</para>
@ -1207,7 +1267,7 @@ namespace Vanara.PInvoke
// LPCSTR lpServiceName, DWORD dwDesiredAccess );
[DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)]
[PInvokeData("winsvc.h", MSDNShortId = "e0a42613-95ad-4d0f-a464-c6df33014064")]
public static extern SafeSC_HANDLE OpenService(SC_HANDLE hSCManager, string lpServiceName, uint dwDesiredAccess);
public static extern SafeSC_HANDLE OpenService(SC_HANDLE hSCManager, string lpServiceName, ServiceAccessTypes dwDesiredAccess);
/// <summary>
/// Retrieves the configuration parameters of the specified service. Optional configuration parameters are available using the
@ -1410,6 +1470,53 @@ namespace Vanara.PInvoke
}
}
/// <summary>
/// Retrieves the current status of the specified service based on the specified information level.
/// </summary>
/// <param name="hService">
/// A handle to the service. This handle is returned by the <see cref="CreateService"/> or <see cref="OpenService"/> function, and it must have the <see cref="ServiceAccessTypes.SERVICE_QUERY_STATUS"/> access right.
/// For more information, see <see cref="https://docs.microsoft.com/en-gb/windows/desktop/Services/service-security-and-access-rights">Service Security and Access Rights.</see>
/// </param>
/// <param name="InfoLevel">The service attributes to be returned. Use <see cref="ServiceStatusType.SC_STATUS_PROCESS_INFO"/> to retrieve the service status information. The lpBuffer parameter is a pointer to a <see cref="SERVICE_STATUS_PROCESS" /> structure. Currently, no other information levels are defined.</param>
/// <param name="lpBuffer"></param>
/// <param name="cbBufSize"></param>
/// <param name="pcbBytesNeeded"></param>
/// <returns>
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error
/// information, call GetLastError.
/// </returns>
[DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)]
[PInvokeData("winsvc.h")]
public static extern bool QueryServiceStatusEx(SC_HANDLE hService, ServiceStatusType InfoLevel, IntPtr lpBuffer, uint cbBufSize, out uint pcbBytesNeeded);
/// <summary>
/// Retrieves the current status of the specified service based on the specified information level.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="hService">
/// A handle to the service. This handle is returned by the <see cref="CreateService"/> or <see cref="OpenService"/> function, and it must have the <see cref="ServiceAccessTypes.SERVICE_QUERY_STATUS"/> access right.
/// For more information, see <see cref="https://docs.microsoft.com/en-gb/windows/desktop/Services/service-security-and-access-rights">Service Security and Access Rights.</see>
/// </param>
/// <param name="InfoLevel">The service attributes to be returned. Use <see cref="ServiceStatusType.SC_STATUS_PROCESS_INFO"/> to retrieve the service status information. The lpBuffer parameter is a pointer to a <see cref="SERVICE_STATUS_PROCESS" /> structure. Currently, no other information levels are defined.</param>
/// <param name="statusInfo">A variable that receives the service status information. The format of this data depends on the value of the dwInfoLevel parameter.</param>
/// <returns>
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error
/// information, call GetLastError.
/// </returns>
public static bool QueryServiceStatusEx<T>(SC_HANDLE hService, ServiceStatusType dwInfoLevel, out T statusInfo)
{
if (!CorrespondingTypeAttribute.CanGet(dwInfoLevel, typeof(T))) throw new ArgumentException("Type mismatch", nameof(statusInfo));
var b = QueryServiceStatusEx(hService, dwInfoLevel, IntPtr.Zero, 0, out var size);
statusInfo = default;
if (!b && Win32Error.GetLastError() != Win32Error.ERROR_INSUFFICIENT_BUFFER) return false;
using (var buf = new SafeHGlobalHandle((int)size))
{
if (!QueryServiceStatusEx(hService, dwInfoLevel, (IntPtr)buf, size, out size)) return false;
statusInfo = buf.ToStructure<T>();
return true;
}
}
/// <summary>
/// <para>Registers a function to handle service control requests.</para>
/// <para>
@ -1647,8 +1754,92 @@ namespace Vanara.PInvoke
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetServiceStatus(SERVICE_STATUS_HANDLE hServiceStatus, in SERVICE_STATUS lpServiceStatus);
/// <summary>Contains configuration information for an installed service. It is used by the QueryServiceConfig function.</summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
/// <summary>
/// Starts a service
/// </summary>
/// <param name="hServiceStatus">A handle to the service. This handle is returned by the <see cref="OpenService"/> or <see cref="CreateService"/> function, and it must have the <see cref="ServiceAccessTypes.SERVICE_START"/> access right.</param>
/// <param name="dwNumServiceArgs">The number of strings in the lpServiceArgVectors array. If lpServiceArgVectors is NULL, this parameter can be zero.</param>
/// <param name="lpServiceArgVectors">The null-terminated strings to be passed to the ServiceMain function for the service as arguments. If there are no arguments, this parameter can be NULL. Otherwise, the first argument (lpServiceArgVectors[0]) is the name of the service, followed by any additional arguments (lpServiceArgVectors[1] through lpServiceArgVectors[dwNumServiceArgs-1]).</param>
/// <returns>If the function succeeds, the return value is true. If the function fails, the return value is false. To get extended error information, call GetLastError.</returns>
/// https://docs.microsoft.com/en-gb/windows/desktop/api/winsvc/nf-winsvc-startservicew
[DllImport(Lib.AdvApi32, SetLastError = true)]
[PInvokeData("winsvc.h")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool StartService(SC_HANDLE hServiceStatus, int dwNumServiceArgs = 0, string[] lpServiceArgVectors = null);
/// <summary>
/// Marks the specified service for deletion from the service control manager database
/// </summary>
/// <param name="hServiceStatus">A handle to the service. This handle is returned by the <see cref="OpenService"/> or <see cref="CreateService"/> function, and it must have the <see cref="ACCESS_MASK.DELETE"/> access right.</param>
/// <returns>If the function succeeds, the return value is true. If the function fails, the return value is false. To get extended error information, call GetLastError.</returns>
/// https://docs.microsoft.com/en-gb/windows/desktop/api/winsvc/nf-winsvc-deleteservice
[DllImport(Lib.AdvApi32, SetLastError = true, ExactSpelling = true)]
[PInvokeData("winsvc.h")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteService(SC_HANDLE hServiceStatus);
/// <summary>
/// Sends a control code to a service.
/// </summary>
/// <param name="hService">A handle to the service. This handle is returned by the <see cref="OpenService"/> or <see cref="CreateService"/> function. The access rights required for this handle depend on the dwControl code requested.</param>
/// <param name="dwControl">The <see cref="ServiceControl"/> code to send to the service</param>
/// <param name="lpServiceStatus">A pointer to a <see cref="SERVICE_STATUS"/> structure that receives the latest service status information. The information returned reflects the most recent status that the service reported to the service control manager.</param>
/// <returns>If the function succeeds, the return value is true. If the function fails, the return value is false. To get extended error information, call GetLastError.</returns>
/// https://docs.microsoft.com/en-gb/windows/desktop/api/winsvc/nf-winsvc-controlservice
[DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)]
[PInvokeData("winsvc.h")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ControlService(SC_HANDLE hService, ServiceControl dwControl, ref SERVICE_STATUS lpServiceStatus);
/// <summary>
/// Sends a control code to a service
/// </summary>
/// <param name="hService">A handle to the service. This handle is returned by the <see cref="OpenService"/> or <see cref="CreateService"/> function. The access rights required for this handle depend on the <paramref name="dwControl"/> code requested.</param>
/// <param name="dwControl">The <see cref="ServiceControl"/> code to send to the service</param>
/// <param name="dwInfoLevel">The information level for the service control parameters. This parameter must be set to <see cref="SERVICE_CONTROL_STATUS_REASON_INFO"/></param>
/// <param name="pControlParams">A pointer to the service control parameters. If <paramref name="dwInfoLevel"/> is <see cref="SERVICE_CONTROL_STATUS_REASON_INFO"/>, this member is a pointer to a SERVICE_CONTROL_STATUS_REASON_PARAMS structure.</param>
/// <returns>If the function succeeds, the return value is true. If the function fails, the return value is false. To get extended error information, call GetLastError.</returns>
/// https://docs.microsoft.com/en-gb/windows/desktop/api/winsvc/nf-winsvc-controlserviceexw
[DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)]
[PInvokeData("winsvc.h")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ControlServiceEx(SC_HANDLE hService, ServiceControl dwControl, ServiceInfoLevels dwInfoLevel, [In, Out] ref SERVICE_CONTROL_STATUS_REASON_PARAMS pControlParams);
/// <summary>
/// Stops a service using <see cref="ControlService"/> with <see cref="ServiceControl.SERVICE_CONTROL_STOP"/>
/// </summary>
/// <param name="hService">A handle to the service. This handle is returned by the <see cref="OpenService"/> or <see cref="CreateService"/> function. The access rights required for this handle depend on the <paramref name="dwControl"/> code requested.</param>
/// <returns></returns>
public static bool StopService(SC_HANDLE hService)
{
var lpServiceStatus = new SERVICE_STATUS();
return StopService(hService, ref lpServiceStatus);
}
/// <summary>
/// Stops a service using <see cref="ControlService"/> with <see cref="ServiceControl.SERVICE_CONTROL_STOP"/>
/// </summary>
/// <param name="hService">A handle to the service. This handle is returned by the <see cref="OpenService"/> or <see cref="CreateService"/> function. The access rights required for this handle depend on the <paramref name="dwControl"/> code requested.</param>
/// <param name="lpServiceStatus">A pointer to a <see cref="SERVICE_STATUS"/> structure that receives the latest service status information. The information returned reflects the most recent status that the service reported to the service control manager.</param>
/// <returns></returns>
public static bool StopService(SC_HANDLE hService, ref SERVICE_STATUS lpServiceStatus)
{
return ControlService(hService, ServiceControl.SERVICE_CONTROL_STOP, ref lpServiceStatus);
}
/// <summary>
/// Stops a service using <see cref="ControlServiceEx"/> with <see cref="ServiceControl.SERVICE_CONTROL_STOP"/>
/// </summary>
/// <param name="hService">A handle to the service. This handle is returned by the <see cref="OpenService"/> or <see cref="CreateService"/> function. The access rights required for this handle depend on the <paramref name="dwControl"/> code requested.</param>
/// <param name="reason">A reason and comment for why the service is being stopped</param>
/// <returns></returns>
public static bool StopService(SC_HANDLE hService, ref SERVICE_CONTROL_STATUS_REASON_PARAMS reason)
{
return ControlServiceEx(hService, ServiceControl.SERVICE_CONTROL_STOP, ServiceInfoLevels.SERVICE_CONTROL_STATUS_REASON_INFO, ref reason);
}
/// <summary>Contains configuration information for an installed service. It is used by the QueryServiceConfig function.</summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
[PInvokeData("Winsvc.h", MSDNShortId = "ms684950")]
public struct QUERY_SERVICE_CONFIG
{
@ -1729,15 +1920,15 @@ namespace Vanara.PInvoke
public IEnumerable<string> Dependencies => lpDependencies.ToStringEnum();
}
/// <summary>
/// <para>Represents an action that the service control manager can perform.</para>
/// </summary>
/// <remarks>
/// <para>This structure is used by the ChangeServiceConfig2 and QueryServiceConfig2 functions, in the SERVICE_FAILURE_ACTIONS structure.</para>
/// </remarks>
// https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_sc_action typedef struct _SC_ACTION { SC_ACTION_TYPE Type;
// DWORD Delay; } SC_ACTION, *LPSC_ACTION;
[PInvokeData("winsvc.h", MSDNShortId = "e2c355a6-affe-46bf-a3e6-f8c420422d46")]
/// <summary>
/// <para>Represents an action that the service control manager can perform.</para>
/// </summary>
/// <remarks>
/// <para>This structure is used by the <see cref="ChangeServiceConfig2"/> and <see cref="QueryServiceConfig2"/> functions, in the SERVICE_FAILURE_ACTIONS structure.</para>
/// </remarks>
// https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_sc_action typedef struct _SC_ACTION { SC_ACTION_TYPE Type;
// DWORD Delay; } SC_ACTION, *LPSC_ACTION;
[PInvokeData("winsvc.h", MSDNShortId = "e2c355a6-affe-46bf-a3e6-f8c420422d46")]
[StructLayout(LayoutKind.Sequential)]
public struct SC_ACTION
{
@ -2461,8 +2652,280 @@ namespace Vanara.PInvoke
public uint dwWaitHint;
}
/// <summary>Provides a handle to a service status handle.</summary>
[StructLayout(LayoutKind.Sequential)]
/// <summary>
/// <para>
/// Contains status information for a service. The ControlService, EnumDependentServices, EnumServicesStatus, and QueryServiceStatus
/// functions use this structure. A service uses this structure in the SetServiceStatus function to report its current status to the
/// service control manager.
/// </para>
/// </summary>
// https://docs.microsoft.com/en-gb/windows/desktop/api/winsvc/ns-winsvc-_service_status_process typedef struct _SERVICE_STATUS_PROCESS {
// DWORD dwServiceType; DWORD dwCurrentState; DWORD dwControlsAccepted; DWORD dwWin32ExitCode; DWORD dwServiceSpecificExitCode;
// DWORD dwCheckPoint; DWORD dwWaitHint; DWORD dwProcessId; DWORD dwServiceFlags; } SERVICE_STATUS_PROCESS, * LPSERVICE_STATUS_PROCESS;
[PInvokeData("winsvc.h")]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SERVICE_STATUS_PROCESS
{
/// <summary>
/// <para>The type of service. This member can be one of the following values.</para>
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <term>Meaning</term>
/// </listheader>
/// <item>
/// <term>SERVICE_FILE_SYSTEM_DRIVER 0x00000002</term>
/// <term>The service is a file system driver.</term>
/// </item>
/// <item>
/// <term>SERVICE_KERNEL_DRIVER 0x00000001</term>
/// <term>The service is a device driver.</term>
/// </item>
/// <item>
/// <term>SERVICE_WIN32_OWN_PROCESS 0x00000010</term>
/// <term>The service runs in its own process.</term>
/// </item>
/// <item>
/// <term>SERVICE_WIN32_SHARE_PROCESS 0x00000020</term>
/// <term>The service shares a process with other services.</term>
/// </item>
/// <item>
/// <term>SERVICE_USER_OWN_PROCESS 0x00000050</term>
/// <term>The service runs in its own process under the logged-on user account.</term>
/// </item>
/// <item>
/// <term>SERVICE_USER_SHARE_PROCESS 0x00000060</term>
/// <term>The service shares a process with one or more other services that run under the logged-on user account.</term>
/// </item>
/// </list>
/// <para>
/// If the service type is either SERVICE_WIN32_OWN_PROCESS or SERVICE_WIN32_SHARE_PROCESS, and the service is running in the
/// context of the LocalSystem account, the following type may also be specified.
/// </para>
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <term>Meaning</term>
/// </listheader>
/// <item>
/// <term>SERVICE_INTERACTIVE_PROCESS 0x00000100</term>
/// <term>The service can interact with the desktop. For more information, see Interactive Services.</term>
/// </item>
/// </list>
/// </summary>
public ServiceTypes dwServiceType;
/// <summary>
/// <para>The current state of the service. This member can be one of the following values.</para>
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <term>Meaning</term>
/// </listheader>
/// <item>
/// <term>SERVICE_CONTINUE_PENDING 0x00000005</term>
/// <term>The service continue is pending.</term>
/// </item>
/// <item>
/// <term>SERVICE_PAUSE_PENDING 0x00000006</term>
/// <term>The service pause is pending.</term>
/// </item>
/// <item>
/// <term>SERVICE_PAUSED 0x00000007</term>
/// <term>The service is paused.</term>
/// </item>
/// <item>
/// <term>SERVICE_RUNNING 0x00000004</term>
/// <term>The service is running.</term>
/// </item>
/// <item>
/// <term>SERVICE_START_PENDING 0x00000002</term>
/// <term>The service is starting.</term>
/// </item>
/// <item>
/// <term>SERVICE_STOP_PENDING 0x00000003</term>
/// <term>The service is stopping.</term>
/// </item>
/// <item>
/// <term>SERVICE_STOPPED 0x00000001</term>
/// <term>The service is not running.</term>
/// </item>
/// </list>
/// </summary>
public ServiceState dwCurrentState;
/// <summary>
/// <para>
/// The control codes the service accepts and processes in its handler function (see Handler and HandlerEx). A user interface
/// process can control a service by specifying a control command in the ControlService or ControlServiceEx function. By default,
/// all services accept the <c>SERVICE_CONTROL_INTERROGATE</c> value.
/// </para>
/// <para>
/// To accept the <c>SERVICE_CONTROL_DEVICEEVENT</c> value, the service must register to receive device events by using the
/// RegisterDeviceNotification function.
/// </para>
/// <para>The following are the control codes.</para>
/// <list type="table">
/// <listheader>
/// <term>Control code</term>
/// <term>Meaning</term>
/// </listheader>
/// <item>
/// <term>SERVICE_ACCEPT_NETBINDCHANGE 0x00000010</term>
/// <term>
/// The service is a network component that can accept changes in its binding without being stopped and restarted. This control
/// code allows the service to receive SERVICE_CONTROL_NETBINDADD, SERVICE_CONTROL_NETBINDREMOVE, SERVICE_CONTROL_NETBINDENABLE,
/// and SERVICE_CONTROL_NETBINDDISABLE notifications.
/// </term>
/// </item>
/// <item>
/// <term>SERVICE_ACCEPT_PARAMCHANGE 0x00000008</term>
/// <term>
/// The service can reread its startup parameters without being stopped and restarted. This control code allows the service to
/// receive SERVICE_CONTROL_PARAMCHANGE notifications.
/// </term>
/// </item>
/// <item>
/// <term>SERVICE_ACCEPT_PAUSE_CONTINUE 0x00000002</term>
/// <term>
/// The service can be paused and continued. This control code allows the service to receive SERVICE_CONTROL_PAUSE and
/// SERVICE_CONTROL_CONTINUE notifications.
/// </term>
/// </item>
/// <item>
/// <term>SERVICE_ACCEPT_PRESHUTDOWN 0x00000100</term>
/// <term>
/// The service can perform preshutdown tasks. This control code enables the service to receive SERVICE_CONTROL_PRESHUTDOWN
/// notifications. Note that ControlService and ControlServiceEx cannot send this notification; only the system can send it.
/// Windows Server 2003 and Windows XP: This value is not supported.
/// </term>
/// </item>
/// <item>
/// <term>SERVICE_ACCEPT_SHUTDOWN 0x00000004</term>
/// <term>
/// The service is notified when system shutdown occurs. This control code allows the service to receive SERVICE_CONTROL_SHUTDOWN
/// notifications. Note that ControlService and ControlServiceEx cannot send this notification; only the system can send it.
/// </term>
/// </item>
/// <item>
/// <term>SERVICE_ACCEPT_STOP 0x00000001</term>
/// <term>The service can be stopped. This control code allows the service to receive SERVICE_CONTROL_STOP notifications.</term>
/// </item>
/// </list>
/// <para>
/// This member can also contain the following extended control codes, which are supported only by HandlerEx. (Note that these
/// control codes cannot be sent by ControlService or ControlServiceEx.)
/// </para>
/// <list type="table">
/// <listheader>
/// <term>Control code</term>
/// <term>Meaning</term>
/// </listheader>
/// <item>
/// <term>SERVICE_ACCEPT_HARDWAREPROFILECHANGE 0x00000020</term>
/// <term>
/// The service is notified when the computer's hardware profile has changed. This enables the system to send
/// SERVICE_CONTROL_HARDWAREPROFILECHANGE notifications to the service.
/// </term>
/// </item>
/// <item>
/// <term>SERVICE_ACCEPT_POWEREVENT 0x00000040</term>
/// <term>
/// The service is notified when the computer's power status has changed. This enables the system to send
/// SERVICE_CONTROL_POWEREVENT notifications to the service.
/// </term>
/// </item>
/// <item>
/// <term>SERVICE_ACCEPT_SESSIONCHANGE 0x00000080</term>
/// <term>
/// The service is notified when the computer's session status has changed. This enables the system to send
/// SERVICE_CONTROL_SESSIONCHANGE notifications to the service.
/// </term>
/// </item>
/// <item>
/// <term>SERVICE_ACCEPT_TIMECHANGE 0x00000200</term>
/// <term>
/// The service is notified when the system time has changed. This enables the system to send SERVICE_CONTROL_TIMECHANGE
/// notifications to the service. Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: This control code is
/// not supported.
/// </term>
/// </item>
/// <item>
/// <term>SERVICE_ACCEPT_TRIGGEREVENT 0x00000400</term>
/// <term>
/// The service is notified when an event for which the service has registered occurs. This enables the system to send
/// SERVICE_CONTROL_TRIGGEREVENT notifications to the service. Windows Server 2008, Windows Vista, Windows Server 2003 and
/// Windows XP: This control code is not supported.
/// </term>
/// </item>
/// <item>
/// <term>SERVICE_ACCEPT_USERMODEREBOOT 0x00000800</term>
/// <term>
/// The services is notified when the user initiates a reboot. Windows Server 2008 R2, Windows 7, Windows Server 2008, Windows
/// Vista, Windows Server 2003 and Windows XP: This control code is not supported.
/// </term>
/// </item>
/// </list>
/// </summary>
public ServiceAcceptedControlCodes dwControlsAccepted;
/// <summary>
/// <para>
/// The error code the service uses to report an error that occurs when it is starting or stopping. To return an error code
/// specific to the service, the service must set this value to <c>ERROR_SERVICE_SPECIFIC_ERROR</c> to indicate that the
/// <c>dwServiceSpecificExitCode</c> member contains the error code. The service should set this value to <c>NO_ERROR</c> when it
/// is running and on normal termination.
/// </para>
/// </summary>
public uint dwWin32ExitCode;
/// <summary>
/// <para>
/// A service-specific error code that the service returns when an error occurs while the service is starting or stopping. This
/// value is ignored unless the <c>dwWin32ExitCode</c> member is set to <c>ERROR_SERVICE_SPECIFIC_ERROR</c>.
/// </para>
/// </summary>
public uint dwServiceSpecificExitCode;
/// <summary>
/// <para>
/// The check-point value the service increments periodically to report its progress during a lengthy start, stop, pause, or
/// continue operation. For example, the service should increment this value as it completes each step of its initialization when
/// it is starting up. The user interface program that invoked the operation on the service uses this value to track the progress
/// of the service during a lengthy operation. This value is not valid and should be zero when the service does not have a start,
/// stop, pause, or continue operation pending.
/// </para>
/// </summary>
public uint dwCheckPoint;
/// <summary>
/// <para>
/// The estimated time required for a pending start, stop, pause, or continue operation, in milliseconds. Before the specified
/// amount of time has elapsed, the service should make its next call to the SetServiceStatus function with either an incremented
/// <c>dwCheckPoint</c> value or a change in <c>dwCurrentState</c>. If the amount of time specified by <c>dwWaitHint</c> passes,
/// and <c>dwCheckPoint</c> has not been incremented or <c>dwCurrentState</c> has not changed, the service control manager or
/// service control program can assume that an error has occurred and the service should be stopped. However, if the service
/// shares a process with other services, the service control manager cannot terminate the service application because it would
/// have to terminate the other services sharing the process as well.
/// </para>
/// </summary>
public uint dwWaitHint;
/// <summary>
/// The process identifier of the service.
/// </summary>
public uint dwProcessId;
/// <summary>
/// This member can be one of the following values.
/// 0 = The service is running in a process that is not a system process, or it is not running. If the service is running in a process that is not a system process, dwProcessId is nonzero.If the service is not running, dwProcessId is zero.
/// 1 = SERVICE_RUNS_IN_SYSTEM_PROCESS The service runs in a system process that must always be running.
/// </summary>
public uint dwServiceFlags;
}
/// <summary>Provides a handle to a service status handle.</summary>
[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_STATUS_HANDLE : IHandle
{
private IntPtr handle;
@ -2901,7 +3364,163 @@ namespace Vanara.PInvoke
protected override bool InternalReleaseHandle() => CloseServiceHandle(this);
}
/*LPHANDLER_FUNCTION_EX callback
/// <summary>Service Control Manager object specific access types</summary>
/// <see cref="https://docs.microsoft.com/en-gb/windows/desktop/Services/service-security-and-access-rights#access-rights-for-the-service-control-manager"/>
[PInvokeData("winsvc.h")]
[Flags]
public enum ScManagerAccessTypes : uint
{
/// <summary>Required to connect to the service control manager.</summary>
SC_MANAGER_CONNECT = 0x0001,
/// <summary>Required to call the CreateService function to create a service object and add it to the database.</summary>
SC_MANAGER_CREATE_SERVICE = 0x0002,
/// <summary>
/// Required to call the <see cref="EnumServicesStatus"/> or <see cref="EnumServicesStatusEx"/> function to list the services that are in the database.
/// Required to call the <see cref="NotifyServiceStatusChange"/> function to receive notification when any service is created or deleted.
/// </summary>
SC_MANAGER_ENUMERATE_SERVICE = 0x0004,
/// <summary>Required to call the <see cref="LockServiceDatabase"/> function to acquire a lock on the database.</summary>
SC_MANAGER_LOCK = 0x0008,
/// <summary>Required to call the <see cref="QueryServiceLockStatus"/> function to retrieve the lock status information for the database.</summary>
SC_MANAGER_QUERY_LOCK_STATUS = 0x0010,
/// <summary>Required to call the <see cref="NotifyBootConfigStatus"/> function.</summary>
SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020,
/// <summary>Includes <see cref="ACCESS_MASK.STANDARD_RIGHTS_REQUIRED"/>, in addition to all access rights in this table.</summary>
SC_MANAGER_ALL_ACCESS = ACCESS_MASK.STANDARD_RIGHTS_REQUIRED |
SC_MANAGER_CONNECT |
SC_MANAGER_CREATE_SERVICE |
SC_MANAGER_ENUMERATE_SERVICE |
SC_MANAGER_LOCK |
SC_MANAGER_QUERY_LOCK_STATUS |
SC_MANAGER_MODIFY_BOOT_CONFIG
}
/// <summary>Service object specific access type</summary>
/// <see cref="https://docs.microsoft.com/en-gb/windows/desktop/Services/service-security-and-access-rights#access-rights-for-a-service"/>
[PInvokeData("winsvc.h")]
[Flags]
public enum ServiceAccessTypes : uint
{
/// <summary>Required to call the <see cref="QueryServiceConfig"/> and <see cref="QueryServiceConfig2"/> functions to query the service configuration.</summary>
SERVICE_QUERY_CONFIG = 0x0001,
/// <summary>Required to call the <see cref="ChangeServiceConfig"/> or <see cref="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>
SERVICE_CHANGE_CONFIG = 0x0002,
/// <summary>
/// Required to call the <see cref="QueryServiceStatus"/> or <see cref="QueryServiceStatusEx"/> function to ask the service control manager about the status of the service.
/// Required to call the <see cref="NotifyServiceStatusChange"/> function to receive notification when a service changes status.
/// </summary>
SERVICE_QUERY_STATUS = 0x0004,
/// <summary>
/// Required to call the <see cref="EnumDependentServices"/> function to enumerate all the services dependent on the service.
/// </summary>
SERVICE_ENUMERATE_DEPENDENTS = 0x0008,
/// <summary>Required to call the <see cref="StartService"/> function to start the service.</summary>
SERVICE_START = 0x0010,
/// <summary>Required to call the <see cref="ControlService"/> function to stop the service.</summary>
SERVICE_STOP = 0x0020,
/// <summary>Required to call the <see cref="ControlService"/> function to pause or continue the service.</summary>
SERVICE_PAUSE_CONTINUE = 0x0040,
/// <summary>Required to call the <see cref="ControlService"/> function to ask the service to report its status immediately.</summary>
SERVICE_INTERROGATE = 0x0080,
/// <summary>Required to call the <see cref="ControlService"/> function to specify a user-defined control code.</summary>
SERVICE_USER_DEFINED_CONTROL = 0x0100,
/// <summary>Includes <see cref="ACCESS_MASK.STANDARD_RIGHTS_REQUIRED"/> in addition to all access rights in this table.</summary>
SERVICE_ALL_ACCESS = ACCESS_MASK.STANDARD_RIGHTS_REQUIRED |
SERVICE_QUERY_CONFIG |
SERVICE_CHANGE_CONFIG |
SERVICE_QUERY_STATUS |
SERVICE_ENUMERATE_DEPENDENTS |
SERVICE_START |
SERVICE_STOP |
SERVICE_PAUSE_CONTINUE |
SERVICE_INTERROGATE |
SERVICE_USER_DEFINED_CONTROL
}
/// <summary>Info levels for <see cref="QueryServiceStatusEx"/></summary>
public enum ServiceStatusType
{
[CorrespondingType(typeof(SERVICE_STATUS_PROCESS))]
SC_STATUS_PROCESS_INFO = 0
}
/// <summary>
/// Service control status reason parameters returned by <see cref="ControlServiceEx"/>
/// </summary>
[PInvokeData("winsvc.h")]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SERVICE_CONTROL_STATUS_REASON_PARAMS
{
public SERVICE_STOP_REASON dwReason;
public string pszComment;
public SERVICE_STATUS_PROCESS serviceStatus;
}
[Flags]
public enum SERVICE_STOP_REASON : uint
{
//
// Stop reason flags. Update SERVICE_STOP_REASON_FLAG_MAX when
// new flags are added.
//
SERVICE_STOP_REASON_FLAG_MIN = 0x00000000,
SERVICE_STOP_REASON_FLAG_UNPLANNED = 0x10000000,
SERVICE_STOP_REASON_FLAG_CUSTOM = 0x20000000,
SERVICE_STOP_REASON_FLAG_PLANNED = 0x40000000,
SERVICE_STOP_REASON_FLAG_MAX = 0x80000000,
//
// Microsoft major reasons. Update SERVICE_STOP_REASON_MAJOR_MAX when
// new codes are added.
//
SERVICE_STOP_REASON_MAJOR_MIN = 0x00000000,
SERVICE_STOP_REASON_MAJOR_OTHER = 0x00010000,
SERVICE_STOP_REASON_MAJOR_HARDWARE = 0x00020000,
SERVICE_STOP_REASON_MAJOR_OPERATINGSYSTEM = 0x00030000,
SERVICE_STOP_REASON_MAJOR_SOFTWARE = 0x00040000,
SERVICE_STOP_REASON_MAJOR_APPLICATION = 0x00050000,
SERVICE_STOP_REASON_MAJOR_NONE = 0x00060000,
SERVICE_STOP_REASON_MAJOR_MAX = 0x00070000,
SERVICE_STOP_REASON_MAJOR_MIN_CUSTOM = 0x00400000,
SERVICE_STOP_REASON_MAJOR_MAX_CUSTOM = 0x00ff0000,
//
// Microsoft minor reasons. Update SERVICE_STOP_REASON_MINOR_MAX when
// new codes are added.
//
SERVICE_STOP_REASON_MINOR_MIN = 0x00000000,
SERVICE_STOP_REASON_MINOR_OTHER = 0x00000001,
SERVICE_STOP_REASON_MINOR_MAINTENANCE = 0x00000002,
SERVICE_STOP_REASON_MINOR_INSTALLATION = 0x00000003,
SERVICE_STOP_REASON_MINOR_UPGRADE = 0x00000004,
SERVICE_STOP_REASON_MINOR_RECONFIG = 0x00000005,
SERVICE_STOP_REASON_MINOR_HUNG = 0x00000006,
SERVICE_STOP_REASON_MINOR_UNSTABLE = 0x00000007,
SERVICE_STOP_REASON_MINOR_DISK = 0x00000008,
SERVICE_STOP_REASON_MINOR_NETWORKCARD = 0x00000009,
SERVICE_STOP_REASON_MINOR_ENVIRONMENT = 0x0000000a,
SERVICE_STOP_REASON_MINOR_HARDWARE_DRIVER = 0x0000000b,
SERVICE_STOP_REASON_MINOR_OTHERDRIVER = 0x0000000c,
SERVICE_STOP_REASON_MINOR_SERVICEPACK = 0x0000000d,
SERVICE_STOP_REASON_MINOR_SOFTWARE_UPDATE = 0x0000000e,
SERVICE_STOP_REASON_MINOR_SECURITYFIX = 0x0000000f,
SERVICE_STOP_REASON_MINOR_SECURITY = 0x00000010,
SERVICE_STOP_REASON_MINOR_NETWORK_CONNECTIVITY = 0x00000011,
SERVICE_STOP_REASON_MINOR_WMI = 0x00000012,
SERVICE_STOP_REASON_MINOR_SERVICEPACK_UNINSTALL = 0x00000013,
SERVICE_STOP_REASON_MINOR_SOFTWARE_UPDATE_UNINSTALL = 0x00000014,
SERVICE_STOP_REASON_MINOR_SECURITYFIX_UNINSTALL = 0x00000015,
SERVICE_STOP_REASON_MINOR_MMC = 0x00000016,
SERVICE_STOP_REASON_MINOR_NONE = 0x00000017,
SERVICE_STOP_REASON_MINOR_MEMOTYLIMIT = 0x00000018,
SERVICE_STOP_REASON_MINOR_MAX = 0x00000019,
SERVICE_STOP_REASON_MINOR_MIN_CUSTOM = 0x00000100,
SERVICE_STOP_REASON_MINOR_MAX_CUSTOM = 0x0000FFFF
}
/*LPHANDLER_FUNCTION_EX callback
LPSERVICE_MAIN_FUNCTIONA callback
LPSERVICE_MAIN_FUNCTIONW callback
@ -2920,7 +3539,6 @@ namespace Vanara.PInvoke
QueryServiceLockStatus
QueryServiceObjectSecurity
QueryServiceStatus
QueryServiceStatusEx
SetServiceObjectSecurity
StartServiceCtrlDispatcher
StartService
@ -2929,12 +3547,9 @@ namespace Vanara.PInvoke
ENUM_SERVICE_STATUS_PROCESS
ENUM_SERVICE_STATUS
QUERY_SERVICE_LOCK_STATUS
SERVICE_CONTROL_STATUS_REASON_PARAMS
SERVICE_NOTIFY_2
SERVICE_STATUS
SERVICE_STATUS_PROCESS
SERVICE_TABLE_ENTRY
SERVICE_TIMECHANGE_INFO
SERVICE_TRIGGER_SPECIFIC_DATA_ITEM */
}
}
}

View File

@ -402,7 +402,126 @@ namespace Vanara.PInvoke.Tests
}
}
internal static object[] AuthCasesFromFile
[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 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 ret3 = AdvApi32.QueryServiceStatusEx<SERVICE_STATUS_PROCESS>(service, ServiceStatusType.SC_STATUS_PROCESS_INFO, out var status);
Assert.That(ret3, Is.True);
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 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 ret3 = AdvApi32.QueryServiceStatusEx<SERVICE_STATUS_PROCESS>(service, ServiceStatusType.SC_STATUS_PROCESS_INFO, out var status);
if(!ret3) Win32Error.ThrowLastError();
if(status.dwCurrentState == ServiceState.SERVICE_RUNNING)
{
var ret4 = AdvApi32.StopService(service);
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);
if (!ret6) Win32Error.ThrowLastError();
}
}
}
}
private static void WaitForServiceStatus(SafeSC_HANDLE service, ServiceState status)
{
//query service status again to check that it changed
var status2 = new SERVICE_STATUS_PROCESS();
var tests = 0;
while (tests < 40)
{
var ret = AdvApi32.QueryServiceStatusEx<SERVICE_STATUS_PROCESS>(service, ServiceStatusType.SC_STATUS_PROCESS_INFO, out status2);
if (!ret) Win32Error.ThrowLastError();
if (status2.dwCurrentState == status)
break;
Thread.Sleep(500);
tests++;
}
if (tests >= 40)
throw new TimeoutException($"Timedout waiting for service status {status}");
}
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);
}
internal static object[] AuthCasesFromFile
{
get
{

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\NUnit3TestAdapter.3.12.0\build\net35\NUnit3TestAdapter.props" Condition="Exists('..\packages\NUnit3TestAdapter.3.12.0\build\net35\NUnit3TestAdapter.props')" />
<Import Project="..\packages\NUnit.3.11.0\build\NUnit.props" Condition="Exists('..\packages\NUnit.3.11.0\build\NUnit.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@ -274,6 +275,7 @@
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\NUnit.3.11.0\build\NUnit.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\NUnit.3.11.0\build\NUnit.props'))" />
<Error Condition="!Exists('..\packages\NUnit3TestAdapter.3.12.0\build\net35\NUnit3TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\NUnit3TestAdapter.3.12.0\build\net35\NUnit3TestAdapter.props'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NUnit" version="3.11.0" targetFramework="net45" />
<package id="NUnit3TestAdapter" version="3.12.0" targetFramework="net45" />
</packages>