diff --git a/src/Core/WinSWCore/Download.cs b/src/Core/WinSWCore/Download.cs old mode 100755 new mode 100644 index dc70b2f..c1b8c9d --- a/src/Core/WinSWCore/Download.cs +++ b/src/Core/WinSWCore/Download.cs @@ -1,280 +1,280 @@ -using System; -using System.IO; -using System.Net; -#if !VNEXT -using System.Reflection; -#endif -using System.Text; -#if VNEXT -using System.Threading.Tasks; -#endif -using System.Xml; -using log4net; -using WinSW.Util; - -namespace WinSW -{ - /// - /// Specify the download activities prior to the launch. - /// This enables self-updating services. - /// - public class Download - { - public enum AuthType - { - None = 0, - Sspi, - Basic - } - - private static readonly ILog Logger = LogManager.GetLogger(typeof(Download)); - - public readonly string From; - public readonly string To; - public readonly AuthType Auth; - public readonly string? Username; - public readonly string? Password; - public readonly bool UnsecureAuth; - public readonly bool FailOnError; - public readonly string? Proxy; - - public string ShortId => $"(download from {this.From})"; - - static Download() - { -#if NET461 - // If your app runs on .NET Framework 4.7 or later versions, but targets an earlier version - AppContext.SetSwitch("Switch.System.Net.DontEnableSystemDefaultTlsVersions", false); -#elif !VNEXT - // If your app runs on .NET Framework 4.6, but targets an earlier version - Type.GetType("System.AppContext")?.InvokeMember("SetSwitch", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, new object[] { "Switch.System.Net.DontEnableSchUseStrongCrypto", false }); - - const SecurityProtocolType Tls12 = (SecurityProtocolType)0x00000C00; - const SecurityProtocolType Tls11 = (SecurityProtocolType)0x00000300; - - // Windows 7 and Windows Server 2008 R2 - if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1) - { - try - { - ServicePointManager.SecurityProtocol |= Tls11 | Tls12; - Logger.Info("TLS 1.1/1.2 enabled"); - } - catch (NotSupportedException) - { - Logger.Info("TLS 1.1/1.2 disabled"); - } - } -#endif - } - - // internal - public Download( - string from, - string to, - bool failOnError = false, - AuthType auth = AuthType.None, - string? username = null, - string? password = null, - bool unsecureAuth = false, - string? proxy = null) - { - this.From = from; - this.To = to; - this.FailOnError = failOnError; - this.Proxy = proxy; - this.Auth = auth; - this.Username = username; - this.Password = password; - this.UnsecureAuth = unsecureAuth; - } - - /// - /// Constructs the download setting sfrom the XML entry - /// - /// XML element - /// The required attribute is missing or the configuration is invalid. - internal Download(XmlElement n) - { - this.From = XmlHelper.SingleAttribute(n, "from"); - this.To = XmlHelper.SingleAttribute(n, "to"); - - // All arguments below are optional - this.FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false); - this.Proxy = XmlHelper.SingleAttribute(n, "proxy", null); - - this.Auth = XmlHelper.EnumAttribute(n, "auth", AuthType.None); - this.Username = XmlHelper.SingleAttribute(n, "user", null); - this.Password = XmlHelper.SingleAttribute(n, "password", null); - this.UnsecureAuth = XmlHelper.SingleAttribute(n, "unsecureAuth", false); - - if (this.Auth == AuthType.Basic) - { - // Allow it only for HTTPS or for UnsecureAuth - if (!this.From.StartsWith("https:") && !this.UnsecureAuth) - { - throw new InvalidDataException("Warning: you're sending your credentials in clear text to the server " + this.ShortId + - "If you really want this you must enable 'unsecureAuth' in the configuration"); - } - - // Also fail if there is no user/password - if (this.Username is null) - { - throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + this.ShortId); - } - - if (this.Password is null) - { - throw new InvalidDataException("Basic Auth is enabled, but password is not specified " + this.ShortId); - } - } - } - - // Source: http://stackoverflow.com/questions/2764577/forcing-basic-authentication-in-webrequest - private void SetBasicAuthHeader(WebRequest request, string username, string password) - { - string authInfo = username + ":" + password; - authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo)); - request.Headers["Authorization"] = "Basic " + authInfo; - } - - /// - /// Downloads the requested file and puts it to the specified target. - /// - /// - /// Download failure. FailOnError flag should be processed outside. - /// -#if VNEXT - public async Task PerformAsync() -#else - public void Perform() -#endif - { - WebRequest request = WebRequest.Create(this.From); - if (!string.IsNullOrEmpty(this.Proxy)) - { - CustomProxyInformation proxyInformation = new CustomProxyInformation(this.Proxy!); - if (proxyInformation.Credentials != null) - { - request.Proxy = new WebProxy(proxyInformation.ServerAddress, false, null, proxyInformation.Credentials); - } - else - { - request.Proxy = new WebProxy(proxyInformation.ServerAddress); - } - } - - switch (this.Auth) - { - case AuthType.None: - // Do nothing - break; - - case AuthType.Sspi: - request.UseDefaultCredentials = true; - request.PreAuthenticate = true; - request.Credentials = CredentialCache.DefaultCredentials; - break; - - case AuthType.Basic: - this.SetBasicAuthHeader(request, this.Username!, this.Password!); - break; - - default: - throw new WebException("Code defect. Unsupported authentication type: " + this.Auth); - } - - bool supportsIfModifiedSince = false; - if (request is HttpWebRequest httpRequest && File.Exists(this.To)) - { - supportsIfModifiedSince = true; - httpRequest.IfModifiedSince = File.GetLastWriteTime(this.To); - } - - DateTime lastModified = default; - string tmpFilePath = this.To + ".tmp"; - try - { -#if VNEXT - using (WebResponse response = await request.GetResponseAsync()) -#else - using (WebResponse response = request.GetResponse()) -#endif - using (Stream responseStream = response.GetResponseStream()) - using (FileStream tmpStream = new FileStream(tmpFilePath, FileMode.Create)) - { - if (supportsIfModifiedSince) - { - lastModified = ((HttpWebResponse)response).LastModified; - } - -#if VNEXT - await responseStream.CopyToAsync(tmpStream); -#elif NET20 - CopyStream(responseStream, tmpStream); -#else - responseStream.CopyTo(tmpStream); -#endif - } - - FileHelper.MoveOrReplaceFile(this.To + ".tmp", this.To); - - if (supportsIfModifiedSince) - { - File.SetLastWriteTime(this.To, lastModified); - } - } - catch (WebException e) - { - if (supportsIfModifiedSince && ((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotModified) - { - Logger.Info($"Skipped downloading unmodified resource '{this.From}'"); - } - else - { - throw; - } - } - } - -#if NET20 - private static void CopyStream(Stream source, Stream destination) - { - byte[] buffer = new byte[8192]; - int read; - while ((read = source.Read(buffer, 0, buffer.Length)) != 0) - { - destination.Write(buffer, 0, read); - } - } -#endif - } - - public class CustomProxyInformation - { - public string ServerAddress { get; set; } - - public NetworkCredential? Credentials { get; set; } - - public CustomProxyInformation(string proxy) - { - if (proxy.Contains("@")) - { - // Extract proxy credentials - int credsFrom = proxy.IndexOf("://") + 3; - int credsTo = proxy.LastIndexOf("@"); - string completeCredsStr = proxy.Substring(credsFrom, credsTo - credsFrom); - int credsSeparator = completeCredsStr.IndexOf(":"); - - string username = completeCredsStr.Substring(0, credsSeparator); - string password = completeCredsStr.Substring(credsSeparator + 1); - this.Credentials = new NetworkCredential(username, password); - this.ServerAddress = proxy.Replace(completeCredsStr + "@", string.Empty); - } - else - { - this.ServerAddress = proxy; - } - } - } -} +using System; +using System.IO; +using System.Net; +#if !VNEXT +using System.Reflection; +#endif +using System.Text; +#if VNEXT +using System.Threading.Tasks; +#endif +using System.Xml; +using log4net; +using WinSW.Util; + +namespace WinSW +{ + /// + /// Specify the download activities prior to the launch. + /// This enables self-updating services. + /// + public class Download + { + public enum AuthType + { + None = 0, + Sspi, + Basic + } + + private static readonly ILog Logger = LogManager.GetLogger(typeof(Download)); + + public readonly string From; + public readonly string To; + public readonly AuthType Auth; + public readonly string? Username; + public readonly string? Password; + public readonly bool UnsecureAuth; + public readonly bool FailOnError; + public readonly string? Proxy; + + public string ShortId => $"(download from {this.From})"; + + static Download() + { +#if NET461 + // If your app runs on .NET Framework 4.7 or later versions, but targets an earlier version + AppContext.SetSwitch("Switch.System.Net.DontEnableSystemDefaultTlsVersions", false); +#elif !VNEXT + // If your app runs on .NET Framework 4.6, but targets an earlier version + Type.GetType("System.AppContext")?.InvokeMember("SetSwitch", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, new object[] { "Switch.System.Net.DontEnableSchUseStrongCrypto", false }); + + const SecurityProtocolType Tls12 = (SecurityProtocolType)0x00000C00; + const SecurityProtocolType Tls11 = (SecurityProtocolType)0x00000300; + + // Windows 7 and Windows Server 2008 R2 + if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1) + { + try + { + ServicePointManager.SecurityProtocol |= Tls11 | Tls12; + Logger.Info("TLS 1.1/1.2 enabled"); + } + catch (NotSupportedException) + { + Logger.Info("TLS 1.1/1.2 disabled"); + } + } +#endif + } + + // internal + public Download( + string from, + string to, + bool failOnError = false, + AuthType auth = AuthType.None, + string? username = null, + string? password = null, + bool unsecureAuth = false, + string? proxy = null) + { + this.From = from; + this.To = to; + this.FailOnError = failOnError; + this.Proxy = proxy; + this.Auth = auth; + this.Username = username; + this.Password = password; + this.UnsecureAuth = unsecureAuth; + } + + /// + /// Constructs the download setting sfrom the XML entry + /// + /// XML element + /// The required attribute is missing or the configuration is invalid. + internal Download(XmlElement n) + { + this.From = XmlHelper.SingleAttribute(n, "from"); + this.To = XmlHelper.SingleAttribute(n, "to"); + + // All arguments below are optional + this.FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false); + this.Proxy = XmlHelper.SingleAttribute(n, "proxy", null); + + this.Auth = XmlHelper.EnumAttribute(n, "auth", AuthType.None); + this.Username = XmlHelper.SingleAttribute(n, "user", null); + this.Password = XmlHelper.SingleAttribute(n, "password", null); + this.UnsecureAuth = XmlHelper.SingleAttribute(n, "unsecureAuth", false); + + if (this.Auth == AuthType.Basic) + { + // Allow it only for HTTPS or for UnsecureAuth + if (!this.From.StartsWith("https:") && !this.UnsecureAuth) + { + throw new InvalidDataException("Warning: you're sending your credentials in clear text to the server " + this.ShortId + + "If you really want this you must enable 'unsecureAuth' in the configuration"); + } + + // Also fail if there is no user/password + if (this.Username is null) + { + throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + this.ShortId); + } + + if (this.Password is null) + { + throw new InvalidDataException("Basic Auth is enabled, but password is not specified " + this.ShortId); + } + } + } + + // Source: http://stackoverflow.com/questions/2764577/forcing-basic-authentication-in-webrequest + private void SetBasicAuthHeader(WebRequest request, string username, string password) + { + string authInfo = username + ":" + password; + authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo)); + request.Headers["Authorization"] = "Basic " + authInfo; + } + + /// + /// Downloads the requested file and puts it to the specified target. + /// + /// + /// Download failure. FailOnError flag should be processed outside. + /// +#if VNEXT + public async Task PerformAsync() +#else + public void Perform() +#endif + { + WebRequest request = WebRequest.Create(this.From); + if (!string.IsNullOrEmpty(this.Proxy)) + { + CustomProxyInformation proxyInformation = new CustomProxyInformation(this.Proxy!); + if (proxyInformation.Credentials != null) + { + request.Proxy = new WebProxy(proxyInformation.ServerAddress, false, null, proxyInformation.Credentials); + } + else + { + request.Proxy = new WebProxy(proxyInformation.ServerAddress); + } + } + + switch (this.Auth) + { + case AuthType.None: + // Do nothing + break; + + case AuthType.Sspi: + request.UseDefaultCredentials = true; + request.PreAuthenticate = true; + request.Credentials = CredentialCache.DefaultCredentials; + break; + + case AuthType.Basic: + this.SetBasicAuthHeader(request, this.Username!, this.Password!); + break; + + default: + throw new WebException("Code defect. Unsupported authentication type: " + this.Auth); + } + + bool supportsIfModifiedSince = false; + if (request is HttpWebRequest httpRequest && File.Exists(this.To)) + { + supportsIfModifiedSince = true; + httpRequest.IfModifiedSince = File.GetLastWriteTime(this.To); + } + + DateTime lastModified = default; + string tmpFilePath = this.To + ".tmp"; + try + { +#if VNEXT + using (WebResponse response = await request.GetResponseAsync()) +#else + using (WebResponse response = request.GetResponse()) +#endif + using (Stream responseStream = response.GetResponseStream()) + using (FileStream tmpStream = new FileStream(tmpFilePath, FileMode.Create)) + { + if (supportsIfModifiedSince) + { + lastModified = ((HttpWebResponse)response).LastModified; + } + +#if VNEXT + await responseStream.CopyToAsync(tmpStream); +#elif NET20 + CopyStream(responseStream, tmpStream); +#else + responseStream.CopyTo(tmpStream); +#endif + } + + FileHelper.MoveOrReplaceFile(this.To + ".tmp", this.To); + + if (supportsIfModifiedSince) + { + File.SetLastWriteTime(this.To, lastModified); + } + } + catch (WebException e) + { + if (supportsIfModifiedSince && ((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotModified) + { + Logger.Info($"Skipped downloading unmodified resource '{this.From}'"); + } + else + { + throw; + } + } + } + +#if NET20 + private static void CopyStream(Stream source, Stream destination) + { + byte[] buffer = new byte[8192]; + int read; + while ((read = source.Read(buffer, 0, buffer.Length)) != 0) + { + destination.Write(buffer, 0, read); + } + } +#endif + } + + public class CustomProxyInformation + { + public string ServerAddress { get; set; } + + public NetworkCredential? Credentials { get; set; } + + public CustomProxyInformation(string proxy) + { + if (proxy.Contains("@")) + { + // Extract proxy credentials + int credsFrom = proxy.IndexOf("://") + 3; + int credsTo = proxy.LastIndexOf("@"); + string completeCredsStr = proxy.Substring(credsFrom, credsTo - credsFrom); + int credsSeparator = completeCredsStr.IndexOf(":"); + + string username = completeCredsStr.Substring(0, credsSeparator); + string password = completeCredsStr.Substring(credsSeparator + 1); + this.Credentials = new NetworkCredential(username, password); + this.ServerAddress = proxy.Replace(completeCredsStr + "@", string.Empty); + } + else + { + this.ServerAddress = proxy; + } + } + } +} diff --git a/src/Core/WinSWCore/DynamicProxy.cs b/src/Core/WinSWCore/DynamicProxy.cs index d1ba71a..283d758 100644 --- a/src/Core/WinSWCore/DynamicProxy.cs +++ b/src/Core/WinSWCore/DynamicProxy.cs @@ -1,205 +1,205 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; - -namespace DynamicProxy -{ - /// - /// Interface that a user defined proxy handler needs to implement. This interface - /// defines one method that gets invoked by the generated proxy. - /// - public interface IProxyInvocationHandler - { - /// The instance of the proxy - /// The method info that can be used to invoke the actual method on the object implementation - /// Parameters to pass to the method - /// Object - object? Invoke(object proxy, MethodInfo method, object[] parameters); - } - - /// - /// - public static class ProxyFactory - { - private const string ProxySuffix = "Proxy"; - private const string AssemblyName = "ProxyAssembly"; - private const string ModuleName = "ProxyModule"; - private const string HandlerName = "handler"; - - private static readonly Dictionary TypeCache = new Dictionary(); - - private static readonly AssemblyBuilder AssemblyBuilder = -#if VNEXT - AssemblyBuilder.DefineDynamicAssembly( -#else - AppDomain.CurrentDomain.DefineDynamicAssembly( -#endif - new AssemblyName(AssemblyName), AssemblyBuilderAccess.Run); - - private static readonly ModuleBuilder ModuleBuilder = AssemblyBuilder.DefineDynamicModule(ModuleName); - - public static object Create(IProxyInvocationHandler handler, Type objType, bool isObjInterface = false) - { - string typeName = objType.FullName + ProxySuffix; - Type? type = null; - lock (TypeCache) - { - if (!TypeCache.TryGetValue(typeName, out type)) - { - type = CreateType(typeName, isObjInterface ? new Type[] { objType } : objType.GetInterfaces()); - TypeCache.Add(typeName, type); - } - } - - return Activator.CreateInstance(type, new object[] { handler })!; - } - - private static Type CreateType(string dynamicTypeName, Type[] interfaces) - { - Type objType = typeof(object); - Type handlerType = typeof(IProxyInvocationHandler); - - TypeAttributes typeAttributes = TypeAttributes.Public | TypeAttributes.Sealed; - - // Gather up the proxy information and create a new type builder. One that - // inherits from Object and implements the interface passed in - TypeBuilder typeBuilder = ModuleBuilder.DefineType( - dynamicTypeName, typeAttributes, objType, interfaces); - - // Define a member variable to hold the delegate - FieldBuilder handlerField = typeBuilder.DefineField( - HandlerName, handlerType, FieldAttributes.Private | FieldAttributes.InitOnly); - - // build a constructor that takes the delegate object as the only argument - ConstructorInfo baseConstructor = objType.GetConstructor(Type.EmptyTypes)!; - ConstructorBuilder delegateConstructor = typeBuilder.DefineConstructor( - MethodAttributes.Public, CallingConventions.Standard, new Type[] { handlerType }); - - ILGenerator constructorIL = delegateConstructor.GetILGenerator(); - - // Load "this" - constructorIL.Emit(OpCodes.Ldarg_0); - - // Load first constructor parameter - constructorIL.Emit(OpCodes.Ldarg_1); - - // Set the first parameter into the handler field - constructorIL.Emit(OpCodes.Stfld, handlerField); - - // Load "this" - constructorIL.Emit(OpCodes.Ldarg_0); - - // Call the super constructor - constructorIL.Emit(OpCodes.Call, baseConstructor); - - // Constructor return - constructorIL.Emit(OpCodes.Ret); - - // for every method that the interfaces define, build a corresponding - // method in the dynamic type that calls the handlers invoke method. - foreach (Type interfaceType in interfaces) - { - GenerateMethod(interfaceType, handlerField, typeBuilder); - } - - return typeBuilder.CreateType()!; - } - - /// - /// . - /// - private static readonly MethodInfo InvokeMethod = typeof(IProxyInvocationHandler).GetMethod(nameof(IProxyInvocationHandler.Invoke))!; - - /// - /// . - /// - private static readonly MethodInfo GetMethodFromHandleMethod = typeof(MethodBase).GetMethod(nameof(MethodBase.GetMethodFromHandle), new[] { typeof(RuntimeMethodHandle) })!; - - private static void GenerateMethod(Type interfaceType, FieldBuilder handlerField, TypeBuilder typeBuilder) - { - MethodInfo[] interfaceMethods = interfaceType.GetMethods(); - - for (int i = 0; i < interfaceMethods.Length; i++) - { - MethodInfo methodInfo = interfaceMethods[i]; - - // Get the method parameters since we need to create an array - // of parameter types - ParameterInfo[] methodParams = methodInfo.GetParameters(); - int numOfParams = methodParams.Length; - Type[] methodParameters = new Type[numOfParams]; - - // convert the ParameterInfo objects into Type - for (int j = 0; j < numOfParams; j++) - { - methodParameters[j] = methodParams[j].ParameterType; - } - - // create a new builder for the method in the interface - MethodBuilder methodBuilder = typeBuilder.DefineMethod( - methodInfo.Name, - /*MethodAttributes.Public | MethodAttributes.Virtual | */ methodInfo.Attributes & ~MethodAttributes.Abstract, - CallingConventions.Standard, - methodInfo.ReturnType, - methodParameters); - - ILGenerator methodIL = methodBuilder.GetILGenerator(); - - // invoke target: IProxyInvocationHandler - methodIL.Emit(OpCodes.Ldarg_0); - methodIL.Emit(OpCodes.Ldfld, handlerField); - - // 1st parameter: object proxy - methodIL.Emit(OpCodes.Ldarg_0); - - // 2nd parameter: MethodInfo method - methodIL.Emit(OpCodes.Ldtoken, methodInfo); - methodIL.Emit(OpCodes.Call, GetMethodFromHandleMethod); - methodIL.Emit(OpCodes.Castclass, typeof(MethodInfo)); - - // 3rd parameter: object[] parameters - methodIL.Emit(OpCodes.Ldc_I4, numOfParams); - methodIL.Emit(OpCodes.Newarr, typeof(object)); - - // if we have any parameters, then iterate through and set the values - // of each element to the corresponding arguments - for (int j = 0; j < numOfParams; j++) - { - methodIL.Emit(OpCodes.Dup); // copy the array - methodIL.Emit(OpCodes.Ldc_I4, j); - methodIL.Emit(OpCodes.Ldarg, j + 1); // +1 for "this" - if (methodParameters[j].IsValueType) - { - methodIL.Emit(OpCodes.Box, methodParameters[j]); - } - - methodIL.Emit(OpCodes.Stelem_Ref); - } - - // call the Invoke method - methodIL.Emit(OpCodes.Callvirt, InvokeMethod); - - if (methodInfo.ReturnType != typeof(void)) - { - methodIL.Emit(OpCodes.Unbox_Any, methodInfo.ReturnType); - } - else - { - // pop the return value that Invoke returned from the stack since - // the method's return type is void. - methodIL.Emit(OpCodes.Pop); - } - - // Return - methodIL.Emit(OpCodes.Ret); - } - - // Iterate through the parent interfaces and recursively call this method - foreach (Type parentType in interfaceType.GetInterfaces()) - { - GenerateMethod(parentType, handlerField, typeBuilder); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + +namespace DynamicProxy +{ + /// + /// Interface that a user defined proxy handler needs to implement. This interface + /// defines one method that gets invoked by the generated proxy. + /// + public interface IProxyInvocationHandler + { + /// The instance of the proxy + /// The method info that can be used to invoke the actual method on the object implementation + /// Parameters to pass to the method + /// Object + object? Invoke(object proxy, MethodInfo method, object[] parameters); + } + + /// + /// + public static class ProxyFactory + { + private const string ProxySuffix = "Proxy"; + private const string AssemblyName = "ProxyAssembly"; + private const string ModuleName = "ProxyModule"; + private const string HandlerName = "handler"; + + private static readonly Dictionary TypeCache = new Dictionary(); + + private static readonly AssemblyBuilder AssemblyBuilder = +#if VNEXT + AssemblyBuilder.DefineDynamicAssembly( +#else + AppDomain.CurrentDomain.DefineDynamicAssembly( +#endif + new AssemblyName(AssemblyName), AssemblyBuilderAccess.Run); + + private static readonly ModuleBuilder ModuleBuilder = AssemblyBuilder.DefineDynamicModule(ModuleName); + + public static object Create(IProxyInvocationHandler handler, Type objType, bool isObjInterface = false) + { + string typeName = objType.FullName + ProxySuffix; + Type? type = null; + lock (TypeCache) + { + if (!TypeCache.TryGetValue(typeName, out type)) + { + type = CreateType(typeName, isObjInterface ? new Type[] { objType } : objType.GetInterfaces()); + TypeCache.Add(typeName, type); + } + } + + return Activator.CreateInstance(type, new object[] { handler })!; + } + + private static Type CreateType(string dynamicTypeName, Type[] interfaces) + { + Type objType = typeof(object); + Type handlerType = typeof(IProxyInvocationHandler); + + TypeAttributes typeAttributes = TypeAttributes.Public | TypeAttributes.Sealed; + + // Gather up the proxy information and create a new type builder. One that + // inherits from Object and implements the interface passed in + TypeBuilder typeBuilder = ModuleBuilder.DefineType( + dynamicTypeName, typeAttributes, objType, interfaces); + + // Define a member variable to hold the delegate + FieldBuilder handlerField = typeBuilder.DefineField( + HandlerName, handlerType, FieldAttributes.Private | FieldAttributes.InitOnly); + + // build a constructor that takes the delegate object as the only argument + ConstructorInfo baseConstructor = objType.GetConstructor(Type.EmptyTypes)!; + ConstructorBuilder delegateConstructor = typeBuilder.DefineConstructor( + MethodAttributes.Public, CallingConventions.Standard, new Type[] { handlerType }); + + ILGenerator constructorIL = delegateConstructor.GetILGenerator(); + + // Load "this" + constructorIL.Emit(OpCodes.Ldarg_0); + + // Load first constructor parameter + constructorIL.Emit(OpCodes.Ldarg_1); + + // Set the first parameter into the handler field + constructorIL.Emit(OpCodes.Stfld, handlerField); + + // Load "this" + constructorIL.Emit(OpCodes.Ldarg_0); + + // Call the super constructor + constructorIL.Emit(OpCodes.Call, baseConstructor); + + // Constructor return + constructorIL.Emit(OpCodes.Ret); + + // for every method that the interfaces define, build a corresponding + // method in the dynamic type that calls the handlers invoke method. + foreach (Type interfaceType in interfaces) + { + GenerateMethod(interfaceType, handlerField, typeBuilder); + } + + return typeBuilder.CreateType()!; + } + + /// + /// . + /// + private static readonly MethodInfo InvokeMethod = typeof(IProxyInvocationHandler).GetMethod(nameof(IProxyInvocationHandler.Invoke))!; + + /// + /// . + /// + private static readonly MethodInfo GetMethodFromHandleMethod = typeof(MethodBase).GetMethod(nameof(MethodBase.GetMethodFromHandle), new[] { typeof(RuntimeMethodHandle) })!; + + private static void GenerateMethod(Type interfaceType, FieldBuilder handlerField, TypeBuilder typeBuilder) + { + MethodInfo[] interfaceMethods = interfaceType.GetMethods(); + + for (int i = 0; i < interfaceMethods.Length; i++) + { + MethodInfo methodInfo = interfaceMethods[i]; + + // Get the method parameters since we need to create an array + // of parameter types + ParameterInfo[] methodParams = methodInfo.GetParameters(); + int numOfParams = methodParams.Length; + Type[] methodParameters = new Type[numOfParams]; + + // convert the ParameterInfo objects into Type + for (int j = 0; j < numOfParams; j++) + { + methodParameters[j] = methodParams[j].ParameterType; + } + + // create a new builder for the method in the interface + MethodBuilder methodBuilder = typeBuilder.DefineMethod( + methodInfo.Name, + /*MethodAttributes.Public | MethodAttributes.Virtual | */ methodInfo.Attributes & ~MethodAttributes.Abstract, + CallingConventions.Standard, + methodInfo.ReturnType, + methodParameters); + + ILGenerator methodIL = methodBuilder.GetILGenerator(); + + // invoke target: IProxyInvocationHandler + methodIL.Emit(OpCodes.Ldarg_0); + methodIL.Emit(OpCodes.Ldfld, handlerField); + + // 1st parameter: object proxy + methodIL.Emit(OpCodes.Ldarg_0); + + // 2nd parameter: MethodInfo method + methodIL.Emit(OpCodes.Ldtoken, methodInfo); + methodIL.Emit(OpCodes.Call, GetMethodFromHandleMethod); + methodIL.Emit(OpCodes.Castclass, typeof(MethodInfo)); + + // 3rd parameter: object[] parameters + methodIL.Emit(OpCodes.Ldc_I4, numOfParams); + methodIL.Emit(OpCodes.Newarr, typeof(object)); + + // if we have any parameters, then iterate through and set the values + // of each element to the corresponding arguments + for (int j = 0; j < numOfParams; j++) + { + methodIL.Emit(OpCodes.Dup); // copy the array + methodIL.Emit(OpCodes.Ldc_I4, j); + methodIL.Emit(OpCodes.Ldarg, j + 1); // +1 for "this" + if (methodParameters[j].IsValueType) + { + methodIL.Emit(OpCodes.Box, methodParameters[j]); + } + + methodIL.Emit(OpCodes.Stelem_Ref); + } + + // call the Invoke method + methodIL.Emit(OpCodes.Callvirt, InvokeMethod); + + if (methodInfo.ReturnType != typeof(void)) + { + methodIL.Emit(OpCodes.Unbox_Any, methodInfo.ReturnType); + } + else + { + // pop the return value that Invoke returned from the stack since + // the method's return type is void. + methodIL.Emit(OpCodes.Pop); + } + + // Return + methodIL.Emit(OpCodes.Ret); + } + + // Iterate through the parent interfaces and recursively call this method + foreach (Type parentType in interfaceType.GetInterfaces()) + { + GenerateMethod(parentType, handlerField, typeBuilder); + } + } + } +} diff --git a/src/Core/WinSWCore/LogAppenders.cs b/src/Core/WinSWCore/LogAppenders.cs index 564a58d..5bcddc0 100644 --- a/src/Core/WinSWCore/LogAppenders.cs +++ b/src/Core/WinSWCore/LogAppenders.cs @@ -1,620 +1,620 @@ -using System; -using System.Diagnostics; -#if VNEXT -using System.IO.Compression; -#endif -using System.IO; -using System.Threading; -#if !VNEXT -using ICSharpCode.SharpZipLib.Zip; -#endif -using WinSW.Util; - -namespace WinSW -{ - public interface IEventLogger - { - void LogEvent(string message); - - void LogEvent(string message, EventLogEntryType type); - } - - /// - /// Abstraction for handling log. - /// - public abstract class LogHandler - { - public abstract void Log(StreamReader outputReader, StreamReader errorReader); - - /// - /// Error and information about logging should be reported here. - /// -#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. - public IEventLogger EventLogger { get; set; } -#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. - - /// - /// Convenience method to copy stuff from StreamReader to StreamWriter - /// - protected void CopyStream(StreamReader reader, StreamWriter writer) - { - string? line; - while ((line = reader.ReadLine()) != null) - { - writer.WriteLine(line); - } - - reader.Dispose(); - writer.Dispose(); - } - - /// - /// File replacement. - /// - protected void MoveFile(string sourceFileName, string destFileName) - { - try - { - FileHelper.MoveOrReplaceFile(sourceFileName, destFileName); - } - catch (IOException e) - { - this.EventLogger.LogEvent("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message); - } - } - } - - /// - /// Base class for file-based loggers - /// - public abstract class AbstractFileLogAppender : LogHandler - { - protected string BaseLogFileName { get; private set; } - - protected bool OutFileDisabled { get; private set; } - - protected bool ErrFileDisabled { get; private set; } - - protected string OutFilePattern { get; private set; } - - protected string ErrFilePattern { get; private set; } - - protected AbstractFileLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) - { - this.BaseLogFileName = Path.Combine(logDirectory, baseName); - this.OutFileDisabled = outFileDisabled; - this.OutFilePattern = outFilePattern; - this.ErrFileDisabled = errFileDisabled; - this.ErrFilePattern = errFilePattern; - } - - public override void Log(StreamReader outputReader, StreamReader errorReader) - { - if (this.OutFileDisabled) - { - outputReader.Dispose(); - } - else - { - this.LogOutput(outputReader); - } - - if (this.ErrFileDisabled) - { - errorReader.Dispose(); - } - else - { - this.LogError(errorReader); - } - } - - protected StreamWriter CreateWriter(FileStream stream) => new StreamWriter(stream) { AutoFlush = true }; - - protected abstract void LogOutput(StreamReader outputReader); - - protected abstract void LogError(StreamReader errorReader); - } - - public abstract class SimpleLogAppender : AbstractFileLogAppender - { - public FileMode FileMode { get; private set; } - - public string OutputLogFileName { get; private set; } - - public string ErrorLogFileName { get; private set; } - - protected SimpleLogAppender(string logDirectory, string baseName, FileMode fileMode, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) - : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) - { - this.FileMode = fileMode; - this.OutputLogFileName = this.BaseLogFileName + ".out.log"; - this.ErrorLogFileName = this.BaseLogFileName + ".err.log"; - } - - protected override void LogOutput(StreamReader outputReader) - { - new Thread(() => this.CopyStream(outputReader, this.CreateWriter(new FileStream(this.OutputLogFileName, this.FileMode)))).Start(); - } - - protected override void LogError(StreamReader errorReader) - { - new Thread(() => this.CopyStream(errorReader, this.CreateWriter(new FileStream(this.ErrorLogFileName, this.FileMode)))).Start(); - } - } - - public class DefaultLogAppender : SimpleLogAppender - { - public DefaultLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) - : base(logDirectory, baseName, FileMode.Append, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) - { - } - } - - public class ResetLogAppender : SimpleLogAppender - { - public ResetLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) - : base(logDirectory, baseName, FileMode.Create, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) - { - } - } - - /// - /// LogHandler that throws away output - /// - public class IgnoreLogAppender : LogHandler - { - public override void Log(StreamReader outputReader, StreamReader errorReader) - { - outputReader.Dispose(); - errorReader.Dispose(); - } - } - - public class TimeBasedRollingLogAppender : AbstractFileLogAppender - { - public string Pattern { get; private set; } - - public int Period { get; private set; } - - public TimeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, string pattern, int period) - : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) - { - this.Pattern = pattern; - this.Period = period; - } - - protected override void LogOutput(StreamReader outputReader) - { - new Thread(() => this.CopyStreamWithDateRotation(outputReader, this.OutFilePattern)).Start(); - } - - protected override void LogError(StreamReader errorReader) - { - new Thread(() => this.CopyStreamWithDateRotation(errorReader, this.ErrFilePattern)).Start(); - } - - /// - /// Works like the CopyStream method but does a log rotation based on time. - /// - private void CopyStreamWithDateRotation(StreamReader reader, string ext) - { - PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.Pattern, this.Period); - periodicRollingCalendar.Init(); - - StreamWriter writer = this.CreateWriter(new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Append)); - string? line; - while ((line = reader.ReadLine()) != null) - { - if (periodicRollingCalendar.ShouldRoll) - { - writer.Dispose(); - writer = this.CreateWriter(new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Create)); - } - - writer.WriteLine(line); - } - - reader.Dispose(); - writer.Dispose(); - } - } - - public class SizeBasedRollingLogAppender : AbstractFileLogAppender - { - public static int BytesPerKB = 1024; - public static int BytesPerMB = 1024 * BytesPerKB; - public static int DefaultSizeThreshold = 10 * BytesPerMB; // roll every 10MB. - public static int DefaultFilesToKeep = 8; - - public int SizeTheshold { get; private set; } - - public int FilesToKeep { get; private set; } - - public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, int sizeThreshold, int filesToKeep) - : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) - { - this.SizeTheshold = sizeThreshold; - this.FilesToKeep = filesToKeep; - } - - public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) - : this(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern, DefaultSizeThreshold, DefaultFilesToKeep) - { - } - - protected override void LogOutput(StreamReader outputReader) - { - new Thread(() => this.CopyStreamWithRotation(outputReader, this.OutFilePattern)).Start(); - } - - protected override void LogError(StreamReader errorReader) - { - new Thread(() => this.CopyStreamWithRotation(errorReader, this.ErrFilePattern)).Start(); - } - - /// - /// Works like the CopyStream method but does a log rotation. - /// - private void CopyStreamWithRotation(StreamReader reader, string ext) - { - StreamWriter writer = this.CreateWriter(new FileStream(this.BaseLogFileName + ext, FileMode.Append)); - long fileLength = new FileInfo(this.BaseLogFileName + ext).Length; - - string? line; - while ((line = reader.ReadLine()) != null) - { - int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char); - if (fileLength + lengthToWrite > this.SizeTheshold) - { - writer.Dispose(); - - try - { - for (int j = this.FilesToKeep; j >= 1; j--) - { - string dst = this.BaseLogFileName + "." + (j - 1) + ext; - string src = this.BaseLogFileName + "." + (j - 2) + ext; - if (File.Exists(dst)) - { - File.Delete(dst); - } - - if (File.Exists(src)) - { - File.Move(src, dst); - } - } - - File.Move(this.BaseLogFileName + ext, this.BaseLogFileName + ".0" + ext); - } - catch (IOException e) - { - this.EventLogger.LogEvent("Failed to roll log: " + e.Message); - } - - // even if the log rotation fails, create a new one, or else - // we'll infinitely try to roll. - writer = this.CreateWriter(new FileStream(this.BaseLogFileName + ext, FileMode.Create)); - fileLength = new FileInfo(this.BaseLogFileName + ext).Length; - } - - writer.WriteLine(line); - fileLength += lengthToWrite; - } - - reader.Dispose(); - writer.Dispose(); - } - } - - /// - /// Roll log when a service is newly started. - /// - public class RollingLogAppender : SimpleLogAppender - { - public RollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) - : base(logDirectory, baseName, FileMode.Append, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) - { - } - - public override void Log(StreamReader outputReader, StreamReader errorReader) - { - if (!this.OutFileDisabled) - { - this.MoveFile(this.OutputLogFileName, this.OutputLogFileName + ".old"); - } - - if (!this.ErrFileDisabled) - { - this.MoveFile(this.ErrorLogFileName, this.ErrorLogFileName + ".old"); - } - - base.Log(outputReader, errorReader); - } - } - - public class RollingSizeTimeLogAppender : AbstractFileLogAppender - { - public static int BytesPerKB = 1024; - - public int SizeTheshold { get; private set; } - - public string FilePattern { get; private set; } - - public TimeSpan? AutoRollAtTime { get; private set; } - - public int? ZipOlderThanNumDays { get; private set; } - - public string ZipDateFormat { get; private set; } - - public RollingSizeTimeLogAppender( - string logDirectory, - string baseName, - bool outFileDisabled, - bool errFileDisabled, - string outFilePattern, - string errFilePattern, - int sizeThreshold, - string filePattern, - TimeSpan? autoRollAtTime, - int? zipolderthannumdays, - string zipdateformat) - : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) - { - this.SizeTheshold = sizeThreshold; - this.FilePattern = filePattern; - this.AutoRollAtTime = autoRollAtTime; - this.ZipOlderThanNumDays = zipolderthannumdays; - this.ZipDateFormat = zipdateformat; - } - - protected override void LogOutput(StreamReader outputReader) - { - new Thread(() => this.CopyStreamWithRotation(outputReader, this.OutFilePattern)).Start(); - } - - protected override void LogError(StreamReader errorReader) - { - new Thread(() => this.CopyStreamWithRotation(errorReader, this.ErrFilePattern)).Start(); - } - - private void CopyStreamWithRotation(StreamReader reader, string extension) - { - // lock required as the timer thread and the thread that will write to the stream could try and access the file stream at the same time - var fileLock = new object(); - - var baseDirectory = Path.GetDirectoryName(this.BaseLogFileName)!; - var baseFileName = Path.GetFileName(this.BaseLogFileName); - var logFile = this.BaseLogFileName + extension; - - var writer = this.CreateWriter(new FileStream(logFile, FileMode.Append)); - var fileLength = new FileInfo(logFile).Length; - - // We auto roll at time is configured then we need to create a timer and wait until time is elasped and roll the file over - if (this.AutoRollAtTime is TimeSpan autoRollAtTime) - { - // Run at start - var tickTime = this.SetupRollTimer(autoRollAtTime); - var timer = new System.Timers.Timer(tickTime); - timer.Elapsed += (s, e) => - { - try - { - timer.Stop(); - lock (fileLock) - { - writer.Dispose(); - - var now = DateTime.Now.AddDays(-1); - var nextFileNumber = this.GetNextFileNumber(extension, baseDirectory, baseFileName, now); - var nextFileName = Path.Combine(baseDirectory, string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(this.FilePattern), nextFileNumber, extension)); - File.Move(logFile, nextFileName); - - writer = this.CreateWriter(new FileStream(logFile, FileMode.Create)); - fileLength = new FileInfo(logFile).Length; - } - - // Next day so check if file can be zipped - this.ZipFiles(baseDirectory, extension, baseFileName); - } - catch (Exception ex) - { - this.EventLogger.LogEvent($"Failed to to trigger auto roll at time event due to: {ex.Message}"); - } - finally - { - // Recalculate the next interval - timer.Interval = this.SetupRollTimer(autoRollAtTime); - timer.Start(); - } - }; - timer.Start(); - } - - string? line; - while ((line = reader.ReadLine()) != null) - { - lock (fileLock) - { - int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char); - if (fileLength + lengthToWrite > this.SizeTheshold) - { - try - { - // roll file - var now = DateTime.Now; - var nextFileNumber = this.GetNextFileNumber(extension, baseDirectory, baseFileName, now); - var nextFileName = Path.Combine( - baseDirectory, - string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(this.FilePattern), nextFileNumber, extension)); - File.Move(logFile, nextFileName); - - // even if the log rotation fails, create a new one, or else - // we'll infinitely try to roll. - writer = this.CreateWriter(new FileStream(logFile, FileMode.Create)); - fileLength = new FileInfo(logFile).Length; - } - catch (Exception e) - { - this.EventLogger.LogEvent($"Failed to roll size time log: {e.Message}"); - } - } - - writer.WriteLine(line); - fileLength += lengthToWrite; - } - } - - reader.Dispose(); - writer.Dispose(); - } - - private void ZipFiles(string directory, string fileExtension, string zipFileBaseName) - { - if (this.ZipOlderThanNumDays is null || this.ZipOlderThanNumDays <= 0) - { - return; - } - - try - { - foreach (string path in Directory.GetFiles(directory, "*" + fileExtension)) - { - var fileInfo = new FileInfo(path); - if (fileInfo.LastWriteTimeUtc >= DateTime.UtcNow.AddDays(-this.ZipOlderThanNumDays.Value)) - { - continue; - } - - string sourceFileName = Path.GetFileName(path); - string zipFilePattern = fileInfo.LastAccessTimeUtc.ToString(this.ZipDateFormat); - string zipFilePath = Path.Combine(directory, $"{zipFileBaseName}.{zipFilePattern}.zip"); - this.ZipOneFile(path, sourceFileName, zipFilePath); - - File.Delete(path); - } - } - catch (Exception e) - { - this.EventLogger.LogEvent($"Failed to Zip files. Error {e.Message}"); - } - } - -#if VNEXT - private void ZipOneFile(string sourceFilePath, string entryName, string zipFilePath) - { - ZipArchive? zipArchive = null; - try - { - zipArchive = ZipFile.Open(zipFilePath, ZipArchiveMode.Update); - - if (zipArchive.GetEntry(entryName) is null) - { - zipArchive.CreateEntryFromFile(sourceFilePath, entryName); - } - } - catch (Exception e) - { - this.EventLogger.LogEvent($"Failed to Zip the File {sourceFilePath}. Error {e.Message}"); - } - finally - { - zipArchive?.Dispose(); - } - } -#else - private void ZipOneFile(string sourceFilePath, string entryName, string zipFilePath) - { - ZipFile? zipFile = null; - try - { - zipFile = new ZipFile(File.Open(zipFilePath, FileMode.OpenOrCreate)); - zipFile.BeginUpdate(); - - if (zipFile.FindEntry(entryName, false) < 0) - { - zipFile.Add(sourceFilePath, entryName); - } - - zipFile.CommitUpdate(); - } - catch (Exception e) - { - this.EventLogger.LogEvent($"Failed to Zip the File {sourceFilePath}. Error {e.Message}"); - zipFile?.AbortUpdate(); - } - finally - { - zipFile?.Close(); - } - } -#endif - - private double SetupRollTimer(TimeSpan autoRollAtTime) - { - var nowTime = DateTime.Now; - var scheduledTime = new DateTime( - nowTime.Year, - nowTime.Month, - nowTime.Day, - autoRollAtTime.Hours, - autoRollAtTime.Minutes, - autoRollAtTime.Seconds, - 0); - if (nowTime > scheduledTime) - { - scheduledTime = scheduledTime.AddDays(1); - } - - double tickTime = (scheduledTime - DateTime.Now).TotalMilliseconds; - return tickTime; - } - - private int GetNextFileNumber(string ext, string baseDirectory, string baseFileName, DateTime now) - { - var nextFileNumber = 0; - var files = Directory.GetFiles(baseDirectory, string.Format("{0}.{1}.#*{2}", baseFileName, now.ToString(this.FilePattern), ext)); - if (files.Length == 0) - { - nextFileNumber = 1; - } - else - { - foreach (var f in files) - { - try - { - var filenameOnly = Path.GetFileNameWithoutExtension(f); - var hashIndex = filenameOnly.IndexOf('#'); - var lastNumberAsString = filenameOnly.Substring(hashIndex + 1, 4); - if (int.TryParse(lastNumberAsString, out int lastNumber)) - { - if (lastNumber > nextFileNumber) - { - nextFileNumber = lastNumber; - } - } - else - { - throw new IOException($"File {f} does not follow the pattern provided"); - } - } - catch (Exception e) - { - throw new IOException($"Failed to process file {f} due to error {e.Message}", e); - } - } - - if (nextFileNumber == 0) - { - throw new IOException("Cannot roll the file because matching pattern not found"); - } - - nextFileNumber++; - } - - return nextFileNumber; - } - } -} +using System; +using System.Diagnostics; +#if VNEXT +using System.IO.Compression; +#endif +using System.IO; +using System.Threading; +#if !VNEXT +using ICSharpCode.SharpZipLib.Zip; +#endif +using WinSW.Util; + +namespace WinSW +{ + public interface IEventLogger + { + void LogEvent(string message); + + void LogEvent(string message, EventLogEntryType type); + } + + /// + /// Abstraction for handling log. + /// + public abstract class LogHandler + { + public abstract void Log(StreamReader outputReader, StreamReader errorReader); + + /// + /// Error and information about logging should be reported here. + /// +#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. + public IEventLogger EventLogger { get; set; } +#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. + + /// + /// Convenience method to copy stuff from StreamReader to StreamWriter + /// + protected void CopyStream(StreamReader reader, StreamWriter writer) + { + string? line; + while ((line = reader.ReadLine()) != null) + { + writer.WriteLine(line); + } + + reader.Dispose(); + writer.Dispose(); + } + + /// + /// File replacement. + /// + protected void MoveFile(string sourceFileName, string destFileName) + { + try + { + FileHelper.MoveOrReplaceFile(sourceFileName, destFileName); + } + catch (IOException e) + { + this.EventLogger.LogEvent("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message); + } + } + } + + /// + /// Base class for file-based loggers + /// + public abstract class AbstractFileLogAppender : LogHandler + { + protected string BaseLogFileName { get; private set; } + + protected bool OutFileDisabled { get; private set; } + + protected bool ErrFileDisabled { get; private set; } + + protected string OutFilePattern { get; private set; } + + protected string ErrFilePattern { get; private set; } + + protected AbstractFileLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) + { + this.BaseLogFileName = Path.Combine(logDirectory, baseName); + this.OutFileDisabled = outFileDisabled; + this.OutFilePattern = outFilePattern; + this.ErrFileDisabled = errFileDisabled; + this.ErrFilePattern = errFilePattern; + } + + public override void Log(StreamReader outputReader, StreamReader errorReader) + { + if (this.OutFileDisabled) + { + outputReader.Dispose(); + } + else + { + this.LogOutput(outputReader); + } + + if (this.ErrFileDisabled) + { + errorReader.Dispose(); + } + else + { + this.LogError(errorReader); + } + } + + protected StreamWriter CreateWriter(FileStream stream) => new StreamWriter(stream) { AutoFlush = true }; + + protected abstract void LogOutput(StreamReader outputReader); + + protected abstract void LogError(StreamReader errorReader); + } + + public abstract class SimpleLogAppender : AbstractFileLogAppender + { + public FileMode FileMode { get; private set; } + + public string OutputLogFileName { get; private set; } + + public string ErrorLogFileName { get; private set; } + + protected SimpleLogAppender(string logDirectory, string baseName, FileMode fileMode, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) + : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) + { + this.FileMode = fileMode; + this.OutputLogFileName = this.BaseLogFileName + ".out.log"; + this.ErrorLogFileName = this.BaseLogFileName + ".err.log"; + } + + protected override void LogOutput(StreamReader outputReader) + { + new Thread(() => this.CopyStream(outputReader, this.CreateWriter(new FileStream(this.OutputLogFileName, this.FileMode)))).Start(); + } + + protected override void LogError(StreamReader errorReader) + { + new Thread(() => this.CopyStream(errorReader, this.CreateWriter(new FileStream(this.ErrorLogFileName, this.FileMode)))).Start(); + } + } + + public class DefaultLogAppender : SimpleLogAppender + { + public DefaultLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) + : base(logDirectory, baseName, FileMode.Append, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) + { + } + } + + public class ResetLogAppender : SimpleLogAppender + { + public ResetLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) + : base(logDirectory, baseName, FileMode.Create, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) + { + } + } + + /// + /// LogHandler that throws away output + /// + public class IgnoreLogAppender : LogHandler + { + public override void Log(StreamReader outputReader, StreamReader errorReader) + { + outputReader.Dispose(); + errorReader.Dispose(); + } + } + + public class TimeBasedRollingLogAppender : AbstractFileLogAppender + { + public string Pattern { get; private set; } + + public int Period { get; private set; } + + public TimeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, string pattern, int period) + : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) + { + this.Pattern = pattern; + this.Period = period; + } + + protected override void LogOutput(StreamReader outputReader) + { + new Thread(() => this.CopyStreamWithDateRotation(outputReader, this.OutFilePattern)).Start(); + } + + protected override void LogError(StreamReader errorReader) + { + new Thread(() => this.CopyStreamWithDateRotation(errorReader, this.ErrFilePattern)).Start(); + } + + /// + /// Works like the CopyStream method but does a log rotation based on time. + /// + private void CopyStreamWithDateRotation(StreamReader reader, string ext) + { + PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.Pattern, this.Period); + periodicRollingCalendar.Init(); + + StreamWriter writer = this.CreateWriter(new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Append)); + string? line; + while ((line = reader.ReadLine()) != null) + { + if (periodicRollingCalendar.ShouldRoll) + { + writer.Dispose(); + writer = this.CreateWriter(new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Create)); + } + + writer.WriteLine(line); + } + + reader.Dispose(); + writer.Dispose(); + } + } + + public class SizeBasedRollingLogAppender : AbstractFileLogAppender + { + public static int BytesPerKB = 1024; + public static int BytesPerMB = 1024 * BytesPerKB; + public static int DefaultSizeThreshold = 10 * BytesPerMB; // roll every 10MB. + public static int DefaultFilesToKeep = 8; + + public int SizeTheshold { get; private set; } + + public int FilesToKeep { get; private set; } + + public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, int sizeThreshold, int filesToKeep) + : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) + { + this.SizeTheshold = sizeThreshold; + this.FilesToKeep = filesToKeep; + } + + public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) + : this(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern, DefaultSizeThreshold, DefaultFilesToKeep) + { + } + + protected override void LogOutput(StreamReader outputReader) + { + new Thread(() => this.CopyStreamWithRotation(outputReader, this.OutFilePattern)).Start(); + } + + protected override void LogError(StreamReader errorReader) + { + new Thread(() => this.CopyStreamWithRotation(errorReader, this.ErrFilePattern)).Start(); + } + + /// + /// Works like the CopyStream method but does a log rotation. + /// + private void CopyStreamWithRotation(StreamReader reader, string ext) + { + StreamWriter writer = this.CreateWriter(new FileStream(this.BaseLogFileName + ext, FileMode.Append)); + long fileLength = new FileInfo(this.BaseLogFileName + ext).Length; + + string? line; + while ((line = reader.ReadLine()) != null) + { + int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char); + if (fileLength + lengthToWrite > this.SizeTheshold) + { + writer.Dispose(); + + try + { + for (int j = this.FilesToKeep; j >= 1; j--) + { + string dst = this.BaseLogFileName + "." + (j - 1) + ext; + string src = this.BaseLogFileName + "." + (j - 2) + ext; + if (File.Exists(dst)) + { + File.Delete(dst); + } + + if (File.Exists(src)) + { + File.Move(src, dst); + } + } + + File.Move(this.BaseLogFileName + ext, this.BaseLogFileName + ".0" + ext); + } + catch (IOException e) + { + this.EventLogger.LogEvent("Failed to roll log: " + e.Message); + } + + // even if the log rotation fails, create a new one, or else + // we'll infinitely try to roll. + writer = this.CreateWriter(new FileStream(this.BaseLogFileName + ext, FileMode.Create)); + fileLength = new FileInfo(this.BaseLogFileName + ext).Length; + } + + writer.WriteLine(line); + fileLength += lengthToWrite; + } + + reader.Dispose(); + writer.Dispose(); + } + } + + /// + /// Roll log when a service is newly started. + /// + public class RollingLogAppender : SimpleLogAppender + { + public RollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) + : base(logDirectory, baseName, FileMode.Append, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) + { + } + + public override void Log(StreamReader outputReader, StreamReader errorReader) + { + if (!this.OutFileDisabled) + { + this.MoveFile(this.OutputLogFileName, this.OutputLogFileName + ".old"); + } + + if (!this.ErrFileDisabled) + { + this.MoveFile(this.ErrorLogFileName, this.ErrorLogFileName + ".old"); + } + + base.Log(outputReader, errorReader); + } + } + + public class RollingSizeTimeLogAppender : AbstractFileLogAppender + { + public static int BytesPerKB = 1024; + + public int SizeTheshold { get; private set; } + + public string FilePattern { get; private set; } + + public TimeSpan? AutoRollAtTime { get; private set; } + + public int? ZipOlderThanNumDays { get; private set; } + + public string ZipDateFormat { get; private set; } + + public RollingSizeTimeLogAppender( + string logDirectory, + string baseName, + bool outFileDisabled, + bool errFileDisabled, + string outFilePattern, + string errFilePattern, + int sizeThreshold, + string filePattern, + TimeSpan? autoRollAtTime, + int? zipolderthannumdays, + string zipdateformat) + : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) + { + this.SizeTheshold = sizeThreshold; + this.FilePattern = filePattern; + this.AutoRollAtTime = autoRollAtTime; + this.ZipOlderThanNumDays = zipolderthannumdays; + this.ZipDateFormat = zipdateformat; + } + + protected override void LogOutput(StreamReader outputReader) + { + new Thread(() => this.CopyStreamWithRotation(outputReader, this.OutFilePattern)).Start(); + } + + protected override void LogError(StreamReader errorReader) + { + new Thread(() => this.CopyStreamWithRotation(errorReader, this.ErrFilePattern)).Start(); + } + + private void CopyStreamWithRotation(StreamReader reader, string extension) + { + // lock required as the timer thread and the thread that will write to the stream could try and access the file stream at the same time + var fileLock = new object(); + + var baseDirectory = Path.GetDirectoryName(this.BaseLogFileName)!; + var baseFileName = Path.GetFileName(this.BaseLogFileName); + var logFile = this.BaseLogFileName + extension; + + var writer = this.CreateWriter(new FileStream(logFile, FileMode.Append)); + var fileLength = new FileInfo(logFile).Length; + + // We auto roll at time is configured then we need to create a timer and wait until time is elasped and roll the file over + if (this.AutoRollAtTime is TimeSpan autoRollAtTime) + { + // Run at start + var tickTime = this.SetupRollTimer(autoRollAtTime); + var timer = new System.Timers.Timer(tickTime); + timer.Elapsed += (s, e) => + { + try + { + timer.Stop(); + lock (fileLock) + { + writer.Dispose(); + + var now = DateTime.Now.AddDays(-1); + var nextFileNumber = this.GetNextFileNumber(extension, baseDirectory, baseFileName, now); + var nextFileName = Path.Combine(baseDirectory, string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(this.FilePattern), nextFileNumber, extension)); + File.Move(logFile, nextFileName); + + writer = this.CreateWriter(new FileStream(logFile, FileMode.Create)); + fileLength = new FileInfo(logFile).Length; + } + + // Next day so check if file can be zipped + this.ZipFiles(baseDirectory, extension, baseFileName); + } + catch (Exception ex) + { + this.EventLogger.LogEvent($"Failed to to trigger auto roll at time event due to: {ex.Message}"); + } + finally + { + // Recalculate the next interval + timer.Interval = this.SetupRollTimer(autoRollAtTime); + timer.Start(); + } + }; + timer.Start(); + } + + string? line; + while ((line = reader.ReadLine()) != null) + { + lock (fileLock) + { + int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char); + if (fileLength + lengthToWrite > this.SizeTheshold) + { + try + { + // roll file + var now = DateTime.Now; + var nextFileNumber = this.GetNextFileNumber(extension, baseDirectory, baseFileName, now); + var nextFileName = Path.Combine( + baseDirectory, + string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(this.FilePattern), nextFileNumber, extension)); + File.Move(logFile, nextFileName); + + // even if the log rotation fails, create a new one, or else + // we'll infinitely try to roll. + writer = this.CreateWriter(new FileStream(logFile, FileMode.Create)); + fileLength = new FileInfo(logFile).Length; + } + catch (Exception e) + { + this.EventLogger.LogEvent($"Failed to roll size time log: {e.Message}"); + } + } + + writer.WriteLine(line); + fileLength += lengthToWrite; + } + } + + reader.Dispose(); + writer.Dispose(); + } + + private void ZipFiles(string directory, string fileExtension, string zipFileBaseName) + { + if (this.ZipOlderThanNumDays is null || this.ZipOlderThanNumDays <= 0) + { + return; + } + + try + { + foreach (string path in Directory.GetFiles(directory, "*" + fileExtension)) + { + var fileInfo = new FileInfo(path); + if (fileInfo.LastWriteTimeUtc >= DateTime.UtcNow.AddDays(-this.ZipOlderThanNumDays.Value)) + { + continue; + } + + string sourceFileName = Path.GetFileName(path); + string zipFilePattern = fileInfo.LastAccessTimeUtc.ToString(this.ZipDateFormat); + string zipFilePath = Path.Combine(directory, $"{zipFileBaseName}.{zipFilePattern}.zip"); + this.ZipOneFile(path, sourceFileName, zipFilePath); + + File.Delete(path); + } + } + catch (Exception e) + { + this.EventLogger.LogEvent($"Failed to Zip files. Error {e.Message}"); + } + } + +#if VNEXT + private void ZipOneFile(string sourceFilePath, string entryName, string zipFilePath) + { + ZipArchive? zipArchive = null; + try + { + zipArchive = ZipFile.Open(zipFilePath, ZipArchiveMode.Update); + + if (zipArchive.GetEntry(entryName) is null) + { + zipArchive.CreateEntryFromFile(sourceFilePath, entryName); + } + } + catch (Exception e) + { + this.EventLogger.LogEvent($"Failed to Zip the File {sourceFilePath}. Error {e.Message}"); + } + finally + { + zipArchive?.Dispose(); + } + } +#else + private void ZipOneFile(string sourceFilePath, string entryName, string zipFilePath) + { + ZipFile? zipFile = null; + try + { + zipFile = new ZipFile(File.Open(zipFilePath, FileMode.OpenOrCreate)); + zipFile.BeginUpdate(); + + if (zipFile.FindEntry(entryName, false) < 0) + { + zipFile.Add(sourceFilePath, entryName); + } + + zipFile.CommitUpdate(); + } + catch (Exception e) + { + this.EventLogger.LogEvent($"Failed to Zip the File {sourceFilePath}. Error {e.Message}"); + zipFile?.AbortUpdate(); + } + finally + { + zipFile?.Close(); + } + } +#endif + + private double SetupRollTimer(TimeSpan autoRollAtTime) + { + var nowTime = DateTime.Now; + var scheduledTime = new DateTime( + nowTime.Year, + nowTime.Month, + nowTime.Day, + autoRollAtTime.Hours, + autoRollAtTime.Minutes, + autoRollAtTime.Seconds, + 0); + if (nowTime > scheduledTime) + { + scheduledTime = scheduledTime.AddDays(1); + } + + double tickTime = (scheduledTime - DateTime.Now).TotalMilliseconds; + return tickTime; + } + + private int GetNextFileNumber(string ext, string baseDirectory, string baseFileName, DateTime now) + { + var nextFileNumber = 0; + var files = Directory.GetFiles(baseDirectory, string.Format("{0}.{1}.#*{2}", baseFileName, now.ToString(this.FilePattern), ext)); + if (files.Length == 0) + { + nextFileNumber = 1; + } + else + { + foreach (var f in files) + { + try + { + var filenameOnly = Path.GetFileNameWithoutExtension(f); + var hashIndex = filenameOnly.IndexOf('#'); + var lastNumberAsString = filenameOnly.Substring(hashIndex + 1, 4); + if (int.TryParse(lastNumberAsString, out int lastNumber)) + { + if (lastNumber > nextFileNumber) + { + nextFileNumber = lastNumber; + } + } + else + { + throw new IOException($"File {f} does not follow the pattern provided"); + } + } + catch (Exception e) + { + throw new IOException($"Failed to process file {f} due to error {e.Message}", e); + } + } + + if (nextFileNumber == 0) + { + throw new IOException("Cannot roll the file because matching pattern not found"); + } + + nextFileNumber++; + } + + return nextFileNumber; + } + } +} diff --git a/src/Core/WinSWCore/Native/Kernel32.cs b/src/Core/WinSWCore/Native/Kernel32.cs old mode 100755 new mode 100644 index 4ce74ed..733467b --- a/src/Core/WinSWCore/Native/Kernel32.cs +++ b/src/Core/WinSWCore/Native/Kernel32.cs @@ -1,11 +1,11 @@ -using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; - -namespace WinSW.Native -{ - internal static class Kernel32 - { - [DllImport(Libraries.Kernel32)] - internal static extern bool SetStdHandle(int stdHandle, SafeFileHandle handle); - } -} +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +namespace WinSW.Native +{ + internal static class Kernel32 + { + [DllImport(Libraries.Kernel32)] + internal static extern bool SetStdHandle(int stdHandle, SafeFileHandle handle); + } +} diff --git a/src/Core/WinSWCore/PeriodicRollingCalendar.cs b/src/Core/WinSWCore/PeriodicRollingCalendar.cs index 41b868c..03a84f6 100644 --- a/src/Core/WinSWCore/PeriodicRollingCalendar.cs +++ b/src/Core/WinSWCore/PeriodicRollingCalendar.cs @@ -1,108 +1,108 @@ -using System; - -namespace WinSW -{ - // This is largely borrowed from the logback Rolling Calendar. - public class PeriodicRollingCalendar - { - private readonly string format; - private readonly long period; - private DateTime currentRoll; - private DateTime nextRoll; - - public PeriodicRollingCalendar(string format, long period) - { - this.format = format; - this.period = period; - this.currentRoll = DateTime.Now; - } - - public void Init() - { - this.PeriodicityType = this.DeterminePeriodicityType(); - this.nextRoll = this.NextTriggeringTime(this.currentRoll, this.period); - } - - public enum Periodicity - { - ERRONEOUS, - TOP_OF_MILLISECOND, - TOP_OF_SECOND, - TOP_OF_MINUTE, - TOP_OF_HOUR, - TOP_OF_DAY - } - - private static readonly Periodicity[] ValidOrderedList = - { - Periodicity.TOP_OF_MILLISECOND, Periodicity.TOP_OF_SECOND, Periodicity.TOP_OF_MINUTE, Periodicity.TOP_OF_HOUR, Periodicity.TOP_OF_DAY - }; - - private Periodicity DeterminePeriodicityType() - { - PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.format, this.period); - DateTime epoch = new DateTime(1970, 1, 1); - - foreach (Periodicity i in ValidOrderedList) - { - string r0 = epoch.ToString(this.format); - periodicRollingCalendar.PeriodicityType = i; - - DateTime next = periodicRollingCalendar.NextTriggeringTime(epoch, 1); - string r1 = next.ToString(this.format); - - if (r0 != r1) - { - return i; - } - } - - return Periodicity.ERRONEOUS; - } - - private DateTime NextTriggeringTime(DateTime input, long increment) => this.PeriodicityType switch - { - Periodicity.TOP_OF_MILLISECOND => - new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second, input.Millisecond) - .AddMilliseconds(increment), - - Periodicity.TOP_OF_SECOND => - new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second) - .AddSeconds(increment), - - Periodicity.TOP_OF_MINUTE => - new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, 0) - .AddMinutes(increment), - - Periodicity.TOP_OF_HOUR => - new DateTime(input.Year, input.Month, input.Day, input.Hour, 0, 0) - .AddHours(increment), - - Periodicity.TOP_OF_DAY => - new DateTime(input.Year, input.Month, input.Day) - .AddDays(increment), - - _ => throw new Exception("invalid periodicity type: " + this.PeriodicityType), - }; - - public Periodicity PeriodicityType { get; set; } - - public bool ShouldRoll - { - get - { - DateTime now = DateTime.Now; - if (now > this.nextRoll) - { - this.currentRoll = now; - this.nextRoll = this.NextTriggeringTime(now, this.period); - return true; - } - - return false; - } - } - - public string Format => this.currentRoll.ToString(this.format); - } -} +using System; + +namespace WinSW +{ + // This is largely borrowed from the logback Rolling Calendar. + public class PeriodicRollingCalendar + { + private readonly string format; + private readonly long period; + private DateTime currentRoll; + private DateTime nextRoll; + + public PeriodicRollingCalendar(string format, long period) + { + this.format = format; + this.period = period; + this.currentRoll = DateTime.Now; + } + + public void Init() + { + this.PeriodicityType = this.DeterminePeriodicityType(); + this.nextRoll = this.NextTriggeringTime(this.currentRoll, this.period); + } + + public enum Periodicity + { + ERRONEOUS, + TOP_OF_MILLISECOND, + TOP_OF_SECOND, + TOP_OF_MINUTE, + TOP_OF_HOUR, + TOP_OF_DAY + } + + private static readonly Periodicity[] ValidOrderedList = + { + Periodicity.TOP_OF_MILLISECOND, Periodicity.TOP_OF_SECOND, Periodicity.TOP_OF_MINUTE, Periodicity.TOP_OF_HOUR, Periodicity.TOP_OF_DAY + }; + + private Periodicity DeterminePeriodicityType() + { + PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.format, this.period); + DateTime epoch = new DateTime(1970, 1, 1); + + foreach (Periodicity i in ValidOrderedList) + { + string r0 = epoch.ToString(this.format); + periodicRollingCalendar.PeriodicityType = i; + + DateTime next = periodicRollingCalendar.NextTriggeringTime(epoch, 1); + string r1 = next.ToString(this.format); + + if (r0 != r1) + { + return i; + } + } + + return Periodicity.ERRONEOUS; + } + + private DateTime NextTriggeringTime(DateTime input, long increment) => this.PeriodicityType switch + { + Periodicity.TOP_OF_MILLISECOND => + new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second, input.Millisecond) + .AddMilliseconds(increment), + + Periodicity.TOP_OF_SECOND => + new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second) + .AddSeconds(increment), + + Periodicity.TOP_OF_MINUTE => + new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, 0) + .AddMinutes(increment), + + Periodicity.TOP_OF_HOUR => + new DateTime(input.Year, input.Month, input.Day, input.Hour, 0, 0) + .AddHours(increment), + + Periodicity.TOP_OF_DAY => + new DateTime(input.Year, input.Month, input.Day) + .AddDays(increment), + + _ => throw new Exception("invalid periodicity type: " + this.PeriodicityType), + }; + + public Periodicity PeriodicityType { get; set; } + + public bool ShouldRoll + { + get + { + DateTime now = DateTime.Now; + if (now > this.nextRoll) + { + this.currentRoll = now; + this.nextRoll = this.NextTriggeringTime(now, this.period); + return true; + } + + return false; + } + } + + public string Format => this.currentRoll.ToString(this.format); + } +} diff --git a/src/Core/WinSWCore/ServiceDescriptor.cs b/src/Core/WinSWCore/ServiceDescriptor.cs old mode 100755 new mode 100644 diff --git a/src/Core/WinSWCore/Wmi.cs b/src/Core/WinSWCore/Wmi.cs old mode 100755 new mode 100644 index 5259a19..fadec91 --- a/src/Core/WinSWCore/Wmi.cs +++ b/src/Core/WinSWCore/Wmi.cs @@ -1,227 +1,227 @@ -using System; -using System.Management; -using System.Reflection; -using System.Text; -using DynamicProxy; - -namespace WMI -{ - // https://docs.microsoft.com/windows/win32/cimwin32prov/create-method-in-class-win32-service - public enum ReturnValue : uint - { - Success = 0, - NotSupported = 1, - AccessDenied = 2, - DependentServicesRunning = 3, - InvalidServiceControl = 4, - ServiceCannotAcceptControl = 5, - ServiceNotActive = 6, - ServiceRequestTimeout = 7, - UnknownFailure = 8, - PathNotFound = 9, - ServiceAlreadyRunning = 10, - ServiceDatabaseLocked = 11, - ServiceDependencyDeleted = 12, - ServiceDependencyFailure = 13, - ServiceDisabled = 14, - ServiceLogonFailure = 15, - ServiceMarkedForDeletion = 16, - ServiceNoThread = 17, - StatusCircularDependency = 18, - StatusDuplicateName = 19, - StatusInvalidName = 20, - StatusInvalidParameter = 21, - StatusInvalidServiceAccount = 22, - StatusServiceExists = 23, - ServiceAlreadyPaused = 24, - - NoSuchService = 200 - } - - /// - /// Signals a problem in WMI related operations - /// - public class WmiException : Exception - { - public readonly ReturnValue ErrorCode; - - public WmiException(string message, ReturnValue code) - : base(message) - { - this.ErrorCode = code; - } - - public WmiException(ReturnValue code) - : this(code.ToString(), code) - { - } - } - - /// - /// Associated a WMI class name to the proxy interface (which should extend from IWmiCollection) - /// - public class WmiClassName : Attribute - { - public readonly string Name; - - public WmiClassName(string name) => this.Name = name; - } - - /// - /// Marker interface to denote a collection in WMI. - /// - public interface IWmiCollection - { - } - - /// - /// Marker interface to denote an individual managed object - /// - public interface IWmiObject - { - } - - public sealed class WmiRoot - { - private readonly ManagementScope wmiScope; - - public WmiRoot() - { - ConnectionOptions options = new ConnectionOptions - { - EnablePrivileges = true, - Impersonation = ImpersonationLevel.Impersonate, - Authentication = AuthenticationLevel.PacketPrivacy, - }; - - this.wmiScope = new ManagementScope(@"\\.\root\cimv2", options); - this.wmiScope.Connect(); - } - - private static string Capitalize(string s) - { - return char.ToUpper(s[0]) + s.Substring(1); - } - - private abstract class BaseHandler : IProxyInvocationHandler - { - public abstract object? Invoke(object proxy, MethodInfo method, object[] arguments); - - protected void CheckError(ManagementBaseObject result) - { - uint code = (uint)result["returnValue"]; - if (code != 0) - { - throw new WmiException((ReturnValue)code); - } - } - - protected ManagementBaseObject GetMethodParameters(ManagementObject wmiObject, string methodName, ParameterInfo[] methodParameters, object[] arguments) - { - ManagementBaseObject wmiParameters = wmiObject.GetMethodParameters(methodName); - for (int i = 0; i < arguments.Length; i++) - { - string capitalizedName = Capitalize(methodParameters[i].Name!); - wmiParameters[capitalizedName] = arguments[i]; - } - - return wmiParameters; - } - } - - private class InstanceHandler : BaseHandler, IWmiObject - { - private readonly ManagementObject wmiObject; - - public InstanceHandler(ManagementObject wmiObject) => this.wmiObject = wmiObject; - - public override object? Invoke(object proxy, MethodInfo method, object[] arguments) - { - if (method.DeclaringType == typeof(IWmiObject)) - { - return method.Invoke(this, arguments); - } - - // TODO: proper property support - if (method.Name.StartsWith("set_")) - { - this.wmiObject[method.Name.Substring(4)] = arguments[0]; - return null; - } - - if (method.Name.StartsWith("get_")) - { - return this.wmiObject[method.Name.Substring(4)]; - } - - string methodName = method.Name; - using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null : - this.GetMethodParameters(this.wmiObject, methodName, method.GetParameters(), arguments); - using ManagementBaseObject result = this.wmiObject.InvokeMethod(methodName, wmiParameters, null); - this.CheckError(result); - return null; - } - } - - private class ClassHandler : BaseHandler - { - private readonly ManagementClass wmiClass; - private readonly string className; - - public ClassHandler(ManagementScope wmiScope, string className) - { - this.wmiClass = new ManagementClass(wmiScope, new ManagementPath(className), null); - this.className = className; - } - - public override object? Invoke(object proxy, MethodInfo method, object[] arguments) - { - ParameterInfo[] methodParameters = method.GetParameters(); - - if (method.Name == nameof(IWin32Services.Select)) - { - // select method to find instances - StringBuilder query = new StringBuilder("SELECT * FROM ").Append(this.className).Append(" WHERE "); - for (int i = 0; i < arguments.Length; i++) - { - if (i != 0) - { - query.Append(" AND "); - } - - query.Append(' ').Append(Capitalize(methodParameters[i].Name!)).Append(" = '").Append(arguments[i]).Append('\''); - } - - using ManagementObjectSearcher searcher = new ManagementObjectSearcher(this.wmiClass.Scope, new ObjectQuery(query.ToString())); - using ManagementObjectCollection results = searcher.Get(); - - // TODO: support collections - foreach (ManagementObject wmiObject in results) - { - return ProxyFactory.Create(new InstanceHandler(wmiObject), method.ReturnType, true); - } - - return null; - } - - string methodName = method.Name; - using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null : - this.GetMethodParameters(this.wmiClass, methodName, methodParameters, arguments); - using ManagementBaseObject result = this.wmiClass.InvokeMethod(methodName, wmiParameters, null); - this.CheckError(result); - return null; - } - } - - /// - /// Obtains an object that corresponds to a table in WMI, which is a collection of a managed object. - /// - public T GetCollection() - where T : IWmiCollection - { - WmiClassName className = (WmiClassName)typeof(T).GetCustomAttributes(typeof(WmiClassName), false)[0]; - - return (T)ProxyFactory.Create(new ClassHandler(this.wmiScope, className.Name), typeof(T), true); - } - } -} +using System; +using System.Management; +using System.Reflection; +using System.Text; +using DynamicProxy; + +namespace WMI +{ + // https://docs.microsoft.com/windows/win32/cimwin32prov/create-method-in-class-win32-service + public enum ReturnValue : uint + { + Success = 0, + NotSupported = 1, + AccessDenied = 2, + DependentServicesRunning = 3, + InvalidServiceControl = 4, + ServiceCannotAcceptControl = 5, + ServiceNotActive = 6, + ServiceRequestTimeout = 7, + UnknownFailure = 8, + PathNotFound = 9, + ServiceAlreadyRunning = 10, + ServiceDatabaseLocked = 11, + ServiceDependencyDeleted = 12, + ServiceDependencyFailure = 13, + ServiceDisabled = 14, + ServiceLogonFailure = 15, + ServiceMarkedForDeletion = 16, + ServiceNoThread = 17, + StatusCircularDependency = 18, + StatusDuplicateName = 19, + StatusInvalidName = 20, + StatusInvalidParameter = 21, + StatusInvalidServiceAccount = 22, + StatusServiceExists = 23, + ServiceAlreadyPaused = 24, + + NoSuchService = 200 + } + + /// + /// Signals a problem in WMI related operations + /// + public class WmiException : Exception + { + public readonly ReturnValue ErrorCode; + + public WmiException(string message, ReturnValue code) + : base(message) + { + this.ErrorCode = code; + } + + public WmiException(ReturnValue code) + : this(code.ToString(), code) + { + } + } + + /// + /// Associated a WMI class name to the proxy interface (which should extend from IWmiCollection) + /// + public class WmiClassName : Attribute + { + public readonly string Name; + + public WmiClassName(string name) => this.Name = name; + } + + /// + /// Marker interface to denote a collection in WMI. + /// + public interface IWmiCollection + { + } + + /// + /// Marker interface to denote an individual managed object + /// + public interface IWmiObject + { + } + + public sealed class WmiRoot + { + private readonly ManagementScope wmiScope; + + public WmiRoot() + { + ConnectionOptions options = new ConnectionOptions + { + EnablePrivileges = true, + Impersonation = ImpersonationLevel.Impersonate, + Authentication = AuthenticationLevel.PacketPrivacy, + }; + + this.wmiScope = new ManagementScope(@"\\.\root\cimv2", options); + this.wmiScope.Connect(); + } + + private static string Capitalize(string s) + { + return char.ToUpper(s[0]) + s.Substring(1); + } + + private abstract class BaseHandler : IProxyInvocationHandler + { + public abstract object? Invoke(object proxy, MethodInfo method, object[] arguments); + + protected void CheckError(ManagementBaseObject result) + { + uint code = (uint)result["returnValue"]; + if (code != 0) + { + throw new WmiException((ReturnValue)code); + } + } + + protected ManagementBaseObject GetMethodParameters(ManagementObject wmiObject, string methodName, ParameterInfo[] methodParameters, object[] arguments) + { + ManagementBaseObject wmiParameters = wmiObject.GetMethodParameters(methodName); + for (int i = 0; i < arguments.Length; i++) + { + string capitalizedName = Capitalize(methodParameters[i].Name!); + wmiParameters[capitalizedName] = arguments[i]; + } + + return wmiParameters; + } + } + + private class InstanceHandler : BaseHandler, IWmiObject + { + private readonly ManagementObject wmiObject; + + public InstanceHandler(ManagementObject wmiObject) => this.wmiObject = wmiObject; + + public override object? Invoke(object proxy, MethodInfo method, object[] arguments) + { + if (method.DeclaringType == typeof(IWmiObject)) + { + return method.Invoke(this, arguments); + } + + // TODO: proper property support + if (method.Name.StartsWith("set_")) + { + this.wmiObject[method.Name.Substring(4)] = arguments[0]; + return null; + } + + if (method.Name.StartsWith("get_")) + { + return this.wmiObject[method.Name.Substring(4)]; + } + + string methodName = method.Name; + using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null : + this.GetMethodParameters(this.wmiObject, methodName, method.GetParameters(), arguments); + using ManagementBaseObject result = this.wmiObject.InvokeMethod(methodName, wmiParameters, null); + this.CheckError(result); + return null; + } + } + + private class ClassHandler : BaseHandler + { + private readonly ManagementClass wmiClass; + private readonly string className; + + public ClassHandler(ManagementScope wmiScope, string className) + { + this.wmiClass = new ManagementClass(wmiScope, new ManagementPath(className), null); + this.className = className; + } + + public override object? Invoke(object proxy, MethodInfo method, object[] arguments) + { + ParameterInfo[] methodParameters = method.GetParameters(); + + if (method.Name == nameof(IWin32Services.Select)) + { + // select method to find instances + StringBuilder query = new StringBuilder("SELECT * FROM ").Append(this.className).Append(" WHERE "); + for (int i = 0; i < arguments.Length; i++) + { + if (i != 0) + { + query.Append(" AND "); + } + + query.Append(' ').Append(Capitalize(methodParameters[i].Name!)).Append(" = '").Append(arguments[i]).Append('\''); + } + + using ManagementObjectSearcher searcher = new ManagementObjectSearcher(this.wmiClass.Scope, new ObjectQuery(query.ToString())); + using ManagementObjectCollection results = searcher.Get(); + + // TODO: support collections + foreach (ManagementObject wmiObject in results) + { + return ProxyFactory.Create(new InstanceHandler(wmiObject), method.ReturnType, true); + } + + return null; + } + + string methodName = method.Name; + using ManagementBaseObject? wmiParameters = arguments.Length == 0 ? null : + this.GetMethodParameters(this.wmiClass, methodName, methodParameters, arguments); + using ManagementBaseObject result = this.wmiClass.InvokeMethod(methodName, wmiParameters, null); + this.CheckError(result); + return null; + } + } + + /// + /// Obtains an object that corresponds to a table in WMI, which is a collection of a managed object. + /// + public T GetCollection() + where T : IWmiCollection + { + WmiClassName className = (WmiClassName)typeof(T).GetCustomAttributes(typeof(WmiClassName), false)[0]; + + return (T)ProxyFactory.Create(new ClassHandler(this.wmiScope, className.Name), typeof(T), true); + } + } +} diff --git a/src/Core/WinSWCore/WmiSchema.cs b/src/Core/WinSWCore/WmiSchema.cs old mode 100755 new mode 100644 index 2686204..a9e962f --- a/src/Core/WinSWCore/WmiSchema.cs +++ b/src/Core/WinSWCore/WmiSchema.cs @@ -1,72 +1,72 @@ -namespace WMI -{ - public enum ServiceType - { - KernalDriver = 1, - FileSystemDriver = 2, - Adapter = 4, - RecognizerDriver = 8, - OwnProcess = 16, - ShareProcess = 32, - InteractiveProcess = 256, - } - - public enum ErrorControl - { - UserNotNotified = 0, - UserNotified = 1, - SystemRestartedWithLastKnownGoodConfiguration = 2, - SystemAttemptsToStartWithAGoodConfiguration = 3 - } - - public enum StartMode - { - /// - /// Device driver started by the operating system loader. This value is valid only for driver services. - /// - Boot, - - /// - /// Device driver started by the operating system initialization process. This value is valid only for driver services. - /// - System, - - /// - /// Service to be started automatically by the Service Control Manager during system startup. - /// - Automatic, - - /// - /// Service to be started by the Service Control Manager when a process calls the StartService method. - /// - Manual, - - /// - /// Service that can no longer be started. - /// - Disabled, - } - - [WmiClassName("Win32_Service")] - public interface IWin32Services : IWmiCollection - { - // ReturnValue Create(bool desktopInteract, string displayName, int errorControl, string loadOrderGroup, string loadOrderGroupDependencies, string name, string pathName, string serviceDependencies, string serviceType, string startMode, string startName, string startPassword); - void Create(string name, string displayName, string pathName, ServiceType serviceType, ErrorControl errorControl, string startMode, bool desktopInteract, string? startName, string? startPassword, string[] serviceDependencies); - - void Create(string name, string displayName, string pathName, ServiceType serviceType, ErrorControl errorControl, string startMode, bool desktopInteract, string[] serviceDependencies); - - IWin32Service? Select(string name); - } - - // https://docs.microsoft.com/windows/win32/cimwin32prov/win32-service - public interface IWin32Service : IWmiObject - { - bool Started { get; } - - void Delete(); - - void StartService(); - - void StopService(); - } -} +namespace WMI +{ + public enum ServiceType + { + KernalDriver = 1, + FileSystemDriver = 2, + Adapter = 4, + RecognizerDriver = 8, + OwnProcess = 16, + ShareProcess = 32, + InteractiveProcess = 256, + } + + public enum ErrorControl + { + UserNotNotified = 0, + UserNotified = 1, + SystemRestartedWithLastKnownGoodConfiguration = 2, + SystemAttemptsToStartWithAGoodConfiguration = 3 + } + + public enum StartMode + { + /// + /// Device driver started by the operating system loader. This value is valid only for driver services. + /// + Boot, + + /// + /// Device driver started by the operating system initialization process. This value is valid only for driver services. + /// + System, + + /// + /// Service to be started automatically by the Service Control Manager during system startup. + /// + Automatic, + + /// + /// Service to be started by the Service Control Manager when a process calls the StartService method. + /// + Manual, + + /// + /// Service that can no longer be started. + /// + Disabled, + } + + [WmiClassName("Win32_Service")] + public interface IWin32Services : IWmiCollection + { + // ReturnValue Create(bool desktopInteract, string displayName, int errorControl, string loadOrderGroup, string loadOrderGroupDependencies, string name, string pathName, string serviceDependencies, string serviceType, string startMode, string startName, string startPassword); + void Create(string name, string displayName, string pathName, ServiceType serviceType, ErrorControl errorControl, string startMode, bool desktopInteract, string? startName, string? startPassword, string[] serviceDependencies); + + void Create(string name, string displayName, string pathName, ServiceType serviceType, ErrorControl errorControl, string startMode, bool desktopInteract, string[] serviceDependencies); + + IWin32Service? Select(string name); + } + + // https://docs.microsoft.com/windows/win32/cimwin32prov/win32-service + public interface IWin32Service : IWmiObject + { + bool Started { get; } + + void Delete(); + + void StartService(); + + void StopService(); + } +} diff --git a/src/winsw.sln b/src/winsw.sln index 3aba401..e41dfdf 100644 --- a/src/winsw.sln +++ b/src/winsw.sln @@ -1,87 +1,87 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winsw", "Core\ServiceWrapper\winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winswTests", "Test\winswTests\winswTests.csproj", "{93843402-842B-44B4-B303-AEE829BE0B43}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedDirectoryMapper", "Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj", "{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{077C2CEC-B687-4B53-86E9-C1A1BF5554E5}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{BC4AD891-E87E-4F30-867C-FD8084A29E5D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{5297623A-1A95-4F89-9AAE-DA634081EC86}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinSWCore", "Core\WinSWCore\WinSWCore.csproj", "{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RunawayProcessKiller", "Plugins\RunawayProcessKiller\RunawayProcessKiller.csproj", "{57284B7A-82A4-407A-B706-EBEA6BF8EA13}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AA414F46-B863-473A-A0E0-C2971B3396AE}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - ..\examples\sample-allOptions.xml = ..\examples\sample-allOptions.xml - ..\examples\sample-minimal.xml = ..\examples\sample-minimal.xml - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|Win32 = Debug|Win32 - Release|Any CPU = Release|Any CPU - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Win32.ActiveCfg = Debug|Any CPU - {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Win32.Build.0 = Debug|Any CPU - {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Any CPU.Build.0 = Release|Any CPU - {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Win32.ActiveCfg = Release|Any CPU - {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Win32.Build.0 = Release|Any CPU - {93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Win32.ActiveCfg = Debug|Any CPU - {93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Win32.Build.0 = Debug|Any CPU - {93843402-842B-44B4-B303-AEE829BE0B43}.Release|Any CPU.ActiveCfg = Release|Any CPU - {93843402-842B-44B4-B303-AEE829BE0B43}.Release|Any CPU.Build.0 = Release|Any CPU - {93843402-842B-44B4-B303-AEE829BE0B43}.Release|Win32.ActiveCfg = Release|Any CPU - {93843402-842B-44B4-B303-AEE829BE0B43}.Release|Win32.Build.0 = Release|Any CPU - {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Win32.ActiveCfg = Debug|Any CPU - {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Win32.Build.0 = Debug|Any CPU - {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Any CPU.Build.0 = Release|Any CPU - {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Win32.ActiveCfg = Release|Any CPU - {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Win32.Build.0 = Release|Any CPU - {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Win32.ActiveCfg = Debug|Any CPU - {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Win32.Build.0 = Debug|Any CPU - {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Any CPU.Build.0 = Release|Any CPU - {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Win32.ActiveCfg = Release|Any CPU - {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Win32.Build.0 = Release|Any CPU - {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Any CPU.Build.0 = Debug|Any CPU - {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Win32.ActiveCfg = Debug|Any CPU - {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Win32.Build.0 = Debug|Any CPU - {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Any CPU.ActiveCfg = Release|Any CPU - {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Any CPU.Build.0 = Release|Any CPU - {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Win32.ActiveCfg = Release|Any CPU - {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Win32.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {0DE77F55-ADE5-43C1-999A-0BC81153B039} = {5297623A-1A95-4F89-9AAE-DA634081EC86} - {93843402-842B-44B4-B303-AEE829BE0B43} = {077C2CEC-B687-4B53-86E9-C1A1BF5554E5} - {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5} = {BC4AD891-E87E-4F30-867C-FD8084A29E5D} - {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06} = {5297623A-1A95-4F89-9AAE-DA634081EC86} - {57284B7A-82A4-407A-B706-EBEA6BF8EA13} = {BC4AD891-E87E-4F30-867C-FD8084A29E5D} - EndGlobalSection -EndGlobal +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winsw", "Core\ServiceWrapper\winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winswTests", "Test\winswTests\winswTests.csproj", "{93843402-842B-44B4-B303-AEE829BE0B43}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedDirectoryMapper", "Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj", "{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{077C2CEC-B687-4B53-86E9-C1A1BF5554E5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{BC4AD891-E87E-4F30-867C-FD8084A29E5D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{5297623A-1A95-4F89-9AAE-DA634081EC86}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinSWCore", "Core\WinSWCore\WinSWCore.csproj", "{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RunawayProcessKiller", "Plugins\RunawayProcessKiller\RunawayProcessKiller.csproj", "{57284B7A-82A4-407A-B706-EBEA6BF8EA13}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AA414F46-B863-473A-A0E0-C2971B3396AE}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + ..\examples\sample-allOptions.xml = ..\examples\sample-allOptions.xml + ..\examples\sample-minimal.xml = ..\examples\sample-minimal.xml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Win32 = Debug|Win32 + Release|Any CPU = Release|Any CPU + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Win32.ActiveCfg = Debug|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Win32.Build.0 = Debug|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Any CPU.Build.0 = Release|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Win32.ActiveCfg = Release|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Win32.Build.0 = Release|Any CPU + {93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Win32.ActiveCfg = Debug|Any CPU + {93843402-842B-44B4-B303-AEE829BE0B43}.Debug|Win32.Build.0 = Debug|Any CPU + {93843402-842B-44B4-B303-AEE829BE0B43}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93843402-842B-44B4-B303-AEE829BE0B43}.Release|Any CPU.Build.0 = Release|Any CPU + {93843402-842B-44B4-B303-AEE829BE0B43}.Release|Win32.ActiveCfg = Release|Any CPU + {93843402-842B-44B4-B303-AEE829BE0B43}.Release|Win32.Build.0 = Release|Any CPU + {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Win32.ActiveCfg = Debug|Any CPU + {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Debug|Win32.Build.0 = Debug|Any CPU + {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Any CPU.Build.0 = Release|Any CPU + {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Win32.ActiveCfg = Release|Any CPU + {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}.Release|Win32.Build.0 = Release|Any CPU + {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Win32.ActiveCfg = Debug|Any CPU + {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Debug|Win32.Build.0 = Debug|Any CPU + {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Any CPU.Build.0 = Release|Any CPU + {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Win32.ActiveCfg = Release|Any CPU + {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}.Release|Win32.Build.0 = Release|Any CPU + {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Win32.ActiveCfg = Debug|Any CPU + {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Debug|Win32.Build.0 = Debug|Any CPU + {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Any CPU.Build.0 = Release|Any CPU + {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Win32.ActiveCfg = Release|Any CPU + {57284B7A-82A4-407A-B706-EBEA6BF8EA13}.Release|Win32.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0DE77F55-ADE5-43C1-999A-0BC81153B039} = {5297623A-1A95-4F89-9AAE-DA634081EC86} + {93843402-842B-44B4-B303-AEE829BE0B43} = {077C2CEC-B687-4B53-86E9-C1A1BF5554E5} + {CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5} = {BC4AD891-E87E-4F30-867C-FD8084A29E5D} + {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06} = {5297623A-1A95-4F89-9AAE-DA634081EC86} + {57284B7A-82A4-407A-B706-EBEA6BF8EA13} = {BC4AD891-E87E-4F30-867C-FD8084A29E5D} + EndGlobalSection +EndGlobal