From e94628badf55ef4dae431abb0b8c5692e495ebe7 Mon Sep 17 00:00:00 2001 From: kohsuke Date: Fri, 26 Sep 2008 23:31:19 +0000 Subject: [PATCH] initial import git-svn-id: https://svn.kenai.com/svn/winsw~subversion/trunk@2 c8b2a3fe-9b5b-6a51-a37e-dc31b0e308fa --- DynamicProxy.cs | 348 +++++++++++++++++++++++++++++++++++++ Main.cs | 335 +++++++++++++++++++++++++++++++++++ Properties/AssemblyInfo.cs | 33 ++++ Wmi.cs | 216 +++++++++++++++++++++++ WmiSchema.cs | 64 +++++++ pom.xml | 63 +++++++ winsw.csproj | 64 +++++++ winsw.sln | 30 ++++ winsw.xml | 7 + 9 files changed, 1160 insertions(+) create mode 100644 DynamicProxy.cs create mode 100644 Main.cs create mode 100644 Properties/AssemblyInfo.cs create mode 100755 Wmi.cs create mode 100755 WmiSchema.cs create mode 100755 pom.xml create mode 100644 winsw.csproj create mode 100644 winsw.sln create mode 100755 winsw.xml diff --git a/DynamicProxy.cs b/DynamicProxy.cs new file mode 100644 index 0000000..64625e9 --- /dev/null +++ b/DynamicProxy.cs @@ -0,0 +1,348 @@ +using System; +using System.Reflection; +using System.Collections; +using System.Reflection.Emit; +using System.Threading; + +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); + } + + /// + /// Factory class used to cache Types instances + /// + public class MetaDataFactory + { + private static Hashtable typeMap = new Hashtable(); + + /// + /// Class constructor. Private because this is a static class. + /// + private MetaDataFactory() + { + } + + /// + /// Method to add a new Type to the cache, using the type's fully qualified + /// name as the key + /// + ///Type to cache + public static void Add(Type interfaceType) + { + if (interfaceType != null) + { + lock (typeMap.SyncRoot) + { + if (!typeMap.ContainsKey(interfaceType.FullName)) + { + typeMap.Add(interfaceType.FullName, interfaceType); + } + } + } + } + + /// + /// Method to return the method of a given type at a specified index. + /// + ///Fully qualified name of the method to return + ///Index to use to return MethodInfo + ///MethodInfo + public static MethodInfo GetMethod(string name, int i) + { + Type type = null; + lock (typeMap.SyncRoot) + { + type = (Type)typeMap[name]; + } + + return type.GetMethods()[i]; + } + + public static PropertyInfo GetProperty(string name, int i) + { + Type type = null; + lock (typeMap.SyncRoot) + { + type = (Type)typeMap[name]; + } + + return type.GetProperties()[i]; + } + } + + /// + /// + public class ProxyFactory + { + private static ProxyFactory instance; + private static Object lockObj = new Object(); + + private Hashtable typeMap = Hashtable.Synchronized(new Hashtable()); + private static readonly Hashtable opCodeTypeMapper = new Hashtable(); + + private const string PROXY_SUFFIX = "Proxy"; + private const string ASSEMBLY_NAME = "ProxyAssembly"; + private const string MODULE_NAME = "ProxyModule"; + private const string HANDLER_NAME = "handler"; + + // Initialize the value type mapper. This is needed for methods with intrinsic + // return types, used in the Emit process. + static ProxyFactory() + { + opCodeTypeMapper.Add(typeof(System.Boolean), OpCodes.Ldind_I1); + opCodeTypeMapper.Add(typeof(System.Int16), OpCodes.Ldind_I2); + opCodeTypeMapper.Add(typeof(System.Int32), OpCodes.Ldind_I4); + opCodeTypeMapper.Add(typeof(System.Int64), OpCodes.Ldind_I8); + opCodeTypeMapper.Add(typeof(System.Double), OpCodes.Ldind_R8); + opCodeTypeMapper.Add(typeof(System.Single), OpCodes.Ldind_R4); + opCodeTypeMapper.Add(typeof(System.UInt16), OpCodes.Ldind_U2); + opCodeTypeMapper.Add(typeof(System.UInt32), OpCodes.Ldind_U4); + } + + private ProxyFactory() + { + } + + public static ProxyFactory GetInstance() + { + if (instance == null) + { + CreateInstance(); + } + + return instance; + } + + private static void CreateInstance() + { + lock (lockObj) + { + if (instance == null) + { + instance = new ProxyFactory(); + } + } + } + + public Object Create(IProxyInvocationHandler handler, Type objType, bool isObjInterface) + { + string typeName = objType.FullName + PROXY_SUFFIX; + Type type = (Type)typeMap[typeName]; + + // check to see if the type was in the cache. If the type was not cached, then + // create a new instance of the dynamic type and add it to the cache. + if (type == null) + { + if (isObjInterface) + { + type = CreateType(handler, new Type[] { objType }, typeName); + } + else + { + type = CreateType(handler, objType.GetInterfaces(), typeName); + } + + typeMap.Add(typeName, type); + } + + // return a new instance of the type. + return Activator.CreateInstance(type, new object[] { handler }); + } + + public Object Create(IProxyInvocationHandler handler, Type objType) + { + return Create(handler, objType, false); + } + + private Type CreateType(IProxyInvocationHandler handler, Type[] interfaces, string dynamicTypeName) + { + Type retVal = null; + + if (handler != null && interfaces != null) + { + Type objType = typeof(System.Object); + Type handlerType = typeof(IProxyInvocationHandler); + + AppDomain domain = Thread.GetDomain(); + AssemblyName assemblyName = new AssemblyName(); + assemblyName.Name = ASSEMBLY_NAME; + assemblyName.Version = new Version(1, 0, 0, 0); + + // create a new assembly for this proxy, one that isn't presisted on the file system + AssemblyBuilder assemblyBuilder = domain.DefineDynamicAssembly( + assemblyName, AssemblyBuilderAccess.Run); + // assemblyName, AssemblyBuilderAccess.RunAndSave,"."); // to save it to the disk + + // create a new module for this proxy + ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(MODULE_NAME); + + // Set the class to be public and sealed + TypeAttributes typeAttributes = + TypeAttributes.Class | 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( + HANDLER_NAME, handlerType, FieldAttributes.Private); + + + // build a constructor that takes the delegate object as the only argument + //ConstructorInfo defaultObjConstructor = objType.GetConstructor( new Type[0] ); + ConstructorInfo superConstructor = objType.GetConstructor(new Type[0]); + ConstructorBuilder delegateConstructor = typeBuilder.DefineConstructor( + MethodAttributes.Public, CallingConventions.Standard, new Type[] { handlerType }); + + #region( "Constructor IL Code" ) + 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, superConstructor); + // Constructor return + constructorIL.Emit(OpCodes.Ret); + #endregion + + // 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); + } + + retVal = typeBuilder.CreateType(); + + // assemblyBuilder.Save(dynamicTypeName + ".dll"); + } + + return retVal; + } + + private static readonly MethodInfo INVOKE_METHOD = typeof(IProxyInvocationHandler).GetMethod("Invoke"); + private static readonly MethodInfo GET_METHODINFO_METHOD = typeof(MetaDataFactory).GetMethod("GetMethod", new Type[] { typeof(string), typeof(int) }); + + private void GenerateMethod( Type interfaceType, FieldBuilder handlerField, TypeBuilder typeBuilder ) { + MetaDataFactory.Add( interfaceType ); + MethodInfo[] interfaceMethods = interfaceType.GetMethods(); + PropertyInfo[] props = interfaceType.GetProperties(); + + 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 ); + + #region( "Handler Method IL Code" ) + ILGenerator methodIL = methodBuilder.GetILGenerator(); + + // load "this" + methodIL.Emit( OpCodes.Ldarg_0 ); + // load the handler + methodIL.Emit( OpCodes.Ldfld, handlerField ); + // load "this" since its needed for the call to invoke + methodIL.Emit( OpCodes.Ldarg_0 ); + // load the name of the interface, used to get the MethodInfo object + // from MetaDataFactory + methodIL.Emit( OpCodes.Ldstr, interfaceType.FullName ); + // load the index, used to get the MethodInfo object + // from MetaDataFactory + methodIL.Emit( OpCodes.Ldc_I4, i ); + // invoke GetMethod in MetaDataFactory + methodIL.Emit( OpCodes.Call, GET_METHODINFO_METHOD); + + // load the number of parameters onto the stack + methodIL.Emit( OpCodes.Ldc_I4, numOfParams ); + // create a new array, using the size that was just pused on the stack + 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 ); // this copies the array + methodIL.Emit( OpCodes.Ldc_I4, j ); + methodIL.Emit( OpCodes.Ldarg, j + 1 ); + if ( methodParameters[j].IsValueType ) { + methodIL.Emit( OpCodes.Box, methodParameters[j] ); + } + methodIL.Emit( OpCodes.Stelem_Ref ); + } + + // call the Invoke method + methodIL.Emit( OpCodes.Callvirt, INVOKE_METHOD ); + + if ( methodInfo.ReturnType != typeof(void) ) { + // if the return type if a value type, then unbox the return value + // so that we don't get junk. + if ( methodInfo.ReturnType.IsValueType ) { + methodIL.Emit( OpCodes.Unbox, methodInfo.ReturnType ); + if ( methodInfo.ReturnType.IsEnum ) { + methodIL.Emit( OpCodes.Ldind_I4 ); + } else if ( !methodInfo.ReturnType.IsPrimitive ) { + methodIL.Emit( OpCodes.Ldobj, methodInfo.ReturnType ); + } else { + methodIL.Emit( (OpCode) opCodeTypeMapper[ 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 ); + #endregion + } + + //for (int i = 0; i < props.Length; i++) + //{ + // PropertyInfo p = props[i]; + + // PropertyBuilder pb = typeBuilder.DefineProperty(p.Name, p.Attributes, p.PropertyType, new Type[] { p.PropertyType }); + // pb.SetGetMethod((MethodBuilder)methodTable[p.GetGetMethod()]); + // pb.SetSetMethod((MethodBuilder)methodTable[p.GetSetMethod()]); + //} + + // Iterate through the parent interfaces and recursively call this method + foreach ( Type parentType in interfaceType.GetInterfaces() ) { + GenerateMethod( parentType, handlerField, typeBuilder ); + } + } + } +} diff --git a/Main.cs b/Main.cs new file mode 100644 index 0000000..4f8910b --- /dev/null +++ b/Main.cs @@ -0,0 +1,335 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.ServiceProcess; +using System.Text; +using System.IO; +using WMI; +using System.Xml; +using System.Threading; +using Microsoft.Win32; + +namespace winsw +{ + /// + /// In-memory representation of the configuration file. + /// + public class ServiceDescriptor + { + private readonly XmlDocument dom = new XmlDocument(); + + /// + /// Where did we find the configuration file? + /// + public readonly string BasePath; + + public static string ExecutablePath + { + get + { + // this returns the executable name as given by the calling process, so + // it needs to be absolutized. + string p = Environment.GetCommandLineArgs()[0]; + return Path.Combine(Environment.CurrentDirectory, p); + + } + } + + public ServiceDescriptor() + { + // find co-located configuration xml. We search up to the ancestor directories to simplify debugging, + // as well as trimming off ".vshost" suffix (which is used during debugging) + string p = ExecutablePath; + string baseName = Path.GetFileNameWithoutExtension(p); + if (baseName.EndsWith(".vshost")) baseName = baseName.Substring(0, baseName.Length - 7); + while (true) + { + p = Path.GetDirectoryName(p); + if (File.Exists(Path.Combine(p, baseName + ".xml"))) + break; + } + + // register the base directory as environment variable so that future expansions can refer to this. + Environment.SetEnvironmentVariable("BASE", p); + + BasePath = Path.Combine(p, baseName); + + dom.Load(BasePath+".xml"); + } + + private string SingleElement(string tagName) + { + var n = dom.SelectSingleNode("//" + tagName); + if (n == null) throw new InvalidDataException("<" + tagName + "> is missing in configuration XML"); + return Environment.ExpandEnvironmentVariables(n.InnerText); + } + + /// + /// Path to the executable. + /// + public string Executable + { + get + { + return SingleElement("executable"); + } + } + + /// + /// Arguments + /// + public string Arguments + { + get + { + return SingleElement("arguments"); + } + } + + public string Id + { + get + { + return SingleElement("id"); + } + } + + public string Caption + { + get + { + return SingleElement("name"); + } + } + + public string Description + { + get + { + return SingleElement("description"); + } + } + + /// + /// True if the service can interact with the desktop. + /// + public bool Interactive + { + get + { + return dom.SelectSingleNode("//interactive") != null; + } + } + + /// + /// Environment variable overrides + /// + public Dictionary EnvironmentVariables + { + get + { + Dictionary map = new Dictionary(); + foreach (XmlNode n in dom.SelectNodes("//env")) + { + map[n.Attributes["name"].Value] = Environment.ExpandEnvironmentVariables(n.Attributes["value"].Value); + } + return map; + } + } + } + + public class WrapperService : ServiceBase + { + private Process process = new Process(); + private ServiceDescriptor descriptor; + + /// + /// Indicates to the watch dog thread that we are going to terminate the process, + /// so don't try to kill us when the child exits. + /// + private bool orderlyShutdown; + + public WrapperService() + { + this.descriptor = new ServiceDescriptor(); + this.ServiceName = descriptor.Id; + this.CanStop = true; + this.CanPauseAndContinue = false; + this.AutoLog = true; + } + + /// + /// Copy stuff from StreamReader to StreamWriter + /// + private void CopyStream(StreamReader i, StreamWriter o) + { + char[] buf = new char[1024]; + while (true) + { + int sz = i.Read(buf, 0, buf.Length); + if (sz == 0) break; + o.Write(buf, 0, sz); + o.Flush(); + } + i.Close(); + o.Close(); + } + + protected override void OnStart(string[] args) + { + EventLog.WriteEntry("Starting "+descriptor.Executable+' '+descriptor.Arguments); + string baseName = descriptor.BasePath; + + var ps = process.StartInfo; + ps.FileName = descriptor.Executable; + ps.Arguments = descriptor.Arguments; + ps.CreateNoWindow = false; + ps.UseShellExecute = false; + ps.RedirectStandardInput = true; // this creates a pipe for stdin to the new process, instead of having it inherit our stdin. + ps.RedirectStandardOutput = true; + ps.RedirectStandardError = true; + + var envs = descriptor.EnvironmentVariables; + foreach (string key in envs.Keys) + ps.EnvironmentVariables[key] = envs[key]; + + process.Start(); + + // send stdout and stderr to its respective output file. + new Thread(delegate() { CopyStream(process.StandardOutput, new StreamWriter(new FileStream(baseName + ".out.log", FileMode.Append))); }).Start(); + new Thread(delegate() { CopyStream(process.StandardError, new StreamWriter(new FileStream(baseName + ".err.log", FileMode.Append))); }).Start(); + + // monitor the completion of the process + new Thread(delegate() + { + process.WaitForExit(); + if (!orderlyShutdown) + { + EventLog.WriteEntry("Child process terminated with " + process.ExitCode,EventLogEntryType.Warning); + Environment.Exit(process.ExitCode); + } + }).Start(); + + process.StandardInput.Close(); // nothing for you to read! + } + + protected override void OnStop() + { + try + { + EventLog.WriteEntry("Stopping "+descriptor.Id); + orderlyShutdown = true; + process.Kill(); + } + catch (InvalidOperationException) + { + // already terminated + } + process.Dispose(); + } + + + + public static int Main(string[] args) + { + try + { + Run(args); + return 0; + } + catch (WmiException e) + { + Console.Error.WriteLine(e); + return (int)e.ErrorCode; + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return -1; + } + } + + private static void ThrowNoSuchService() + { + throw new WmiException(ReturnValue.NoSuchService); + } + + public static void Run(string[] args) + { + if (args.Length > 0) + { + var d = new ServiceDescriptor(); + Win32Services svc = new WmiRoot().GetCollection(); + Win32Service s = svc.Select(d.Id); + + args[0] = args[0].ToLower(); + if (args[0] == "install") + { + svc.Create( + d.Id, + d.Caption, + ServiceDescriptor.ExecutablePath, + WMI.ServiceType.OwnProcess, + ErrorControl.UserNotified, + StartMode.Automatic, + d.Interactive); + // update the description + /* Somehow this doesn't work, even though it doesn't report an error + Win32Service s = svc.Select(d.Id); + s.Description = d.Description; + s.Commit(); + */ + + // so using a classic method to set the description. Ugly. + Registry.LocalMachine.OpenSubKey("System").OpenSubKey("CurrentControlSet").OpenSubKey("Services") + .OpenSubKey(d.Id, true).SetValue("Description", d.Description); + } + if (args[0] == "uninstall") + { + if (s == null) + return; // there's no such service, so consider it already uninstalled + try + { + s.Delete(); + } + catch (WmiException e) + { + if (e.ErrorCode == ReturnValue.ServiceMarkedForDeletion) + return; // it's already uninstalled, so consider it a success + throw e; + } + } + if (args[0] == "start") + { + if (s == null) ThrowNoSuchService(); + s.StartService(); + } + if (args[0] == "stop") + { + if (s == null) ThrowNoSuchService(); + s.StopService(); + } + if (args[0] == "status") + { + if (s == null) + Console.WriteLine("NonExistent"); + else if (s.Started) + Console.WriteLine("Started"); + else + Console.WriteLine("Stopped"); + } + if (args[0] == "test") + { + WrapperService wsvc = new WrapperService(); + wsvc.OnStart(args); + Thread.Sleep(1000); + wsvc.OnStop(); + } + return; + } + ServiceBase.Run(new WrapperService()); + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c33402c --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Windows Service Wrapper")] +[assembly: AssemblyDescription("Allows arbitrary process to run as a Windows service by wrapping it")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Sun Microsystems, Inc.")] +[assembly: AssemblyProduct("Windows Service Wrapper")] +[assembly: AssemblyCopyright("Copyright 2008 Sun Micorsystems, Inc.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("59ce18df-cacb-4360-bb80-798bd6459ca3")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Wmi.cs b/Wmi.cs new file mode 100755 index 0000000..9122d3d --- /dev/null +++ b/Wmi.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Management; +using DynamicProxy; + +namespace WMI +{ + //Reference: http://msdn2.microsoft.com/en-us/library/aa389390(VS.85).aspx + + public enum ReturnValue + { + 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 msg, ReturnValue code) + : base(msg) + { + 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 + { + /// + /// Reflect updates made to this object to the WMI provider. + /// + void Commit(); + } + + public class WmiRoot + { + private readonly ManagementScope scope; + + public WmiRoot() : this(null) { } + + public WmiRoot(string machineName) + { + ConnectionOptions options = new ConnectionOptions(); + + string path; + + if (machineName != null) + path = String.Format(@"\\{0}\root\cimv2", machineName); + else + path = @"\root\cimv2"; + scope = new ManagementScope(path, options); + scope.Connect(); + } + + private static string capitalize(string s) + { + return char.ToUpper(s[0]) + s.Substring(1); + } + + abstract class BaseHandler : IProxyInvocationHandler + { + public abstract object Invoke(object proxy, MethodInfo method, object[] args); + + protected void CheckError(ManagementBaseObject result) + { + int code = Convert.ToInt32(result["returnValue"]); + if (code != 0) + throw new WmiException((ReturnValue)code); + } + } + + class InstanceHandler : BaseHandler, IWmiObject + { + private readonly ManagementObject mo; + + public InstanceHandler(ManagementObject o) { this.mo = o; } + + public override object Invoke(object proxy, MethodInfo method, object[] args) + { + if (method.DeclaringType == typeof(IWmiObject)) + { + return method.Invoke(this, args); + } + + // TODO: proper property support + if (method.Name.StartsWith("set_")) + { + mo[method.Name.Substring(4)] = args[0]; + return null; + } + if (method.Name.StartsWith("get_")) + { + return mo[method.Name.Substring(4)]; + } + + // method invocations + ParameterInfo[] methodArgs = method.GetParameters(); + + ManagementBaseObject wmiArgs = mo.GetMethodParameters(method.Name); + for (int i = 0; i < args.Length; i++) + wmiArgs[capitalize(methodArgs[i].Name)] = args[i]; + + CheckError(mo.InvokeMethod(method.Name, wmiArgs, null)); + return null; + } + + public void Commit() + { + mo.Put(); + } + } + + class ClassHandler : BaseHandler + { + private readonly ManagementClass mc; + private readonly string wmiClass; + + public ClassHandler(ManagementClass mc, string wmiClass) { this.mc = mc; this.wmiClass = wmiClass; } + + public override object Invoke(object proxy, MethodInfo method, object[] args) + { + ParameterInfo[] methodArgs = method.GetParameters(); + + if (method.Name.StartsWith("Select")) + { + // select method to find instances + string query = "SELECT * FROM " + wmiClass + " WHERE "; + for (int i = 0; i < args.Length; i++) + { + if (i != 0) query += " AND "; + query += ' ' + capitalize(methodArgs[i].Name) + " = '" + args[i] + "'"; + } + + ManagementObjectSearcher searcher = new ManagementObjectSearcher(mc.Scope, new ObjectQuery(query)); + ManagementObjectCollection results = searcher.Get(); + // TODO: support collections + foreach (ManagementObject manObject in results) + return ProxyFactory.GetInstance().Create(new InstanceHandler(manObject), method.ReturnType, true); + return null; + } + + ManagementBaseObject wmiArgs = mc.GetMethodParameters(method.Name); + for (int i = 0; i < args.Length; i++) + wmiArgs[capitalize(methodArgs[i].Name)] = args[i]; + + CheckError(mc.InvokeMethod(method.Name, wmiArgs, null)); + 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 cn = (WmiClassName)typeof(T).GetCustomAttributes(typeof(WmiClassName), false)[0]; + + ObjectGetOptions getOptions = new ObjectGetOptions(); + ManagementPath path = new ManagementPath(cn.Name); + ManagementClass manClass = new ManagementClass(scope, path, getOptions); + return (T)ProxyFactory.GetInstance().Create(new ClassHandler(manClass, cn.Name), typeof(T), true); + } + } +} diff --git a/WmiSchema.cs b/WmiSchema.cs new file mode 100755 index 0000000..c8caf6a --- /dev/null +++ b/WmiSchema.cs @@ -0,0 +1,64 @@ + +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 Win32Services : 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, StartMode startMode, bool desktopInteract); + + Win32Service Select(string name); + } + + public interface Win32Service : IWmiObject + { + string Description { get; set; } + bool Started { get; } + void Delete(); + void StartService(); + void StopService(); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100755 index 0000000..e192669 --- /dev/null +++ b/pom.xml @@ -0,0 +1,63 @@ + + 4.0.0 + com.sun.winsw + winsw + pom + 1.0 + Windows service wrapper + + + + java.net-m2-repository + java-net:/maven2-repository/trunk/www/repository/ + + + + + + + + org.jvnet.maven-antrun-extended-plugin + maven-antrun-extended-plugin + + + package + + run + + + + + + + + + + + + + org.jvnet.wagon-svn + wagon-svn + 1.8 + + + + + + + maven2-repository.dev.java.net + Java.net Repository for Maven + http://download.java.net/maven/2/ + + + + + + maven2-repository.dev.java.net + Java.net Repository for Maven + http://download.java.net/maven/2/ + + + \ No newline at end of file diff --git a/winsw.csproj b/winsw.csproj new file mode 100644 index 0000000..4514e86 --- /dev/null +++ b/winsw.csproj @@ -0,0 +1,64 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {0DE77F55-ADE5-43C1-999A-0BC81153B039} + Exe + Properties + winsw + winsw + v2.0 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + + + Component + + + + + + + + + + + \ No newline at end of file diff --git a/winsw.sln b/winsw.sln new file mode 100644 index 0000000..8c2835e --- /dev/null +++ b/winsw.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winsw", "winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|Win32 = Debug|Win32 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + 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|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Debug|Win32.ActiveCfg = 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|Mixed Platforms.ActiveCfg = Release|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0DE77F55-ADE5-43C1-999A-0BC81153B039}.Release|Win32.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/winsw.xml b/winsw.xml new file mode 100755 index 0000000..046be1f --- /dev/null +++ b/winsw.xml @@ -0,0 +1,7 @@ + + winsw + Winsw test service(2) + This service is a do-nothing test app. Really. + C:\development\jdk6u7\bin\java.exe + -classpath c:\cygwin\home\kohsuke\ws\hello-world\out\production\hello-world test.Main + \ No newline at end of file