Browse Source

commit: init,with basic service support and raw ramdisk implement.

HOME 3 years ago
parent
commit
2b3171a5e3

+ 6 - 0
NuGet.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?> 
+<configuration> 
+  <config> 
+    <add key="repositorypath" value="C:\NuGetLocalRepo" /> 
+  </config> 
+</configuration>

+ 37 - 0
SimpleVirtualDrive.sln

@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29920.165
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SvdCli", "SvdCli\SvdCli.csproj", "{24CC0601-E903-4BA2-8918-036E4D43CA3F}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		Release|Any CPU = Release|Any CPU
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{24CC0601-E903-4BA2-8918-036E4D43CA3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{24CC0601-E903-4BA2-8918-036E4D43CA3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{24CC0601-E903-4BA2-8918-036E4D43CA3F}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{24CC0601-E903-4BA2-8918-036E4D43CA3F}.Debug|x64.Build.0 = Debug|Any CPU
+		{24CC0601-E903-4BA2-8918-036E4D43CA3F}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{24CC0601-E903-4BA2-8918-036E4D43CA3F}.Debug|x86.Build.0 = Debug|Any CPU
+		{24CC0601-E903-4BA2-8918-036E4D43CA3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{24CC0601-E903-4BA2-8918-036E4D43CA3F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{24CC0601-E903-4BA2-8918-036E4D43CA3F}.Release|x64.ActiveCfg = Release|Any CPU
+		{24CC0601-E903-4BA2-8918-036E4D43CA3F}.Release|x64.Build.0 = Release|Any CPU
+		{24CC0601-E903-4BA2-8918-036E4D43CA3F}.Release|x86.ActiveCfg = Release|Any CPU
+		{24CC0601-E903-4BA2-8918-036E4D43CA3F}.Release|x86.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {B8883D92-DD89-4F08-9658-44766362E004}
+	EndGlobalSection
+EndGlobal

+ 6 - 0
SvdCli/App.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
+    </startup>
+</configuration>

+ 37 - 0
SvdCli/App/SvdService.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Net;
+using SvdCli.DriverAdapt;
+using SvdCli.Storage;
+
+namespace SvdCli.App
+{
+    internal class SvdService
+    {
+        private readonly IStorage _storage;
+        private readonly EndPoint _serverEndPoint;
+
+        private readonly VirtualDrive _virtualDrive;
+
+        public SvdService(IStorage storage, EndPoint serverEndPoint)
+        {
+            _storage = storage ?? throw new ArgumentNullException(nameof(storage));
+            _serverEndPoint = serverEndPoint;
+
+            _virtualDrive = new VirtualDrive(_storage);
+        }
+
+        public void Start()
+        {
+            if (null != _serverEndPoint) throw new NotImplementedException();
+
+            _virtualDrive.Attach();
+        }
+
+        public void Stop()
+        {
+            if (null != _serverEndPoint) throw new NotImplementedException();
+
+            _virtualDrive.Detach();
+        }
+    }
+}

+ 221 - 0
SvdCli/CliProgram.cs

@@ -0,0 +1,221 @@
+using SvdCli.App;
+using SvdCli.ServiceUtils;
+using SvdCli.Storage;
+using SvdCli.WindowsService;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Security;
+using System.ServiceProcess;
+using System.Text.RegularExpressions;
+
+namespace SvdCli
+{
+    internal static class CliProgram
+    {
+        private static void Main(string[] args)
+        {
+            if (args.Length < 3)
+            {
+                Console.WriteLine(@"Usage:");
+                Console.WriteLine(@"");
+                Console.WriteLine(@"Create Raw 50GB C:\Path\To\Your\Image.img");
+                Console.WriteLine(@"Create Bdd 10GB 64KB C:\Path\To\Your\Bdd.bdd");
+                Console.WriteLine(@"Create Bdd 64KB C:\Path\To\Your\Image.img C:\Path\To\Your\Bdd.bdd");
+                Console.WriteLine(@"Compact Bdd C:\Path\To\Your\Bdd.bdd");
+                Console.WriteLine(@"");
+                Console.WriteLine(@"[Install ServiceName|Service] <Mount|Server 0.0.0.0 2333> RamDisk 10GB");
+                Console.WriteLine(@"[Install ServiceName|Service] <Mount|Server 0.0.0.0 2333> RamDisk 10GB C:\Path\To\Your\Bdd.Bdd [ReadOnly]");
+                Console.WriteLine(@"[Install ServiceName|Service] <Mount|Server 0.0.0.0 2333> Image C:\Path\To\Your\Image.img [ReadOnly]");
+                Console.WriteLine(@"[Install ServiceName|Service] <Mount|Server 0.0.0.0 2333> Image C:\Path\To\Your\Bdd.bdd [ReadOnly]");
+                Console.WriteLine(@"[Install ServiceName|Service] Mount Net 192.168.233.233 2333");
+                Console.WriteLine(@"[Install ServiceName|Service] Server 192.168.233.233 2333 Dispatch 64KB C:\Path\To\Your\Image.img C:\Path\To\Your\Dir\");
+                Console.WriteLine(@"");
+                Console.WriteLine(@"Uninstall ServiceName           Safe:Restrict to register by this program");
+                Console.WriteLine(@"");
+                Console.Write("Press ENTER to exit");
+                Console.ReadLine();
+                return;
+            }
+
+            EndPoint serverEndPoint = null;
+            IStorage storage = null;
+            switch (args[0].ToLower())
+            {
+                case "install":
+                    {
+                        var serviceName = args[1];
+                        var serviceArgs = new List<string>() { Assembly.GetExecutingAssembly().Location, "Service" };
+                        serviceArgs.AddRange(args.Skip(2));
+                        ServiceHelper.Install(serviceName, "", string.Join(" ", serviceArgs), "", ServiceStartType.Auto);
+                    }
+                    break;
+
+                case "uninstall":
+                    {
+                        var serviceName = args[1];
+                        var binPath = ServiceHelper.GetBinPath(serviceName);
+
+                        if (true == binPath?.Contains(Path.GetFileName(Assembly.GetExecutingAssembly().Location))) ServiceHelper.Uninstall(serviceName);
+                        else throw new SecurityException("Specified service must register by this program");
+                    }
+                    break;
+
+                case "service":
+                    args = args.Skip(1).ToArray();
+
+                    switch (args[0].ToLower())
+                    {
+                        default: throw new InvalidOperationException("Invalid param: OP");
+                        case "server":
+                            serverEndPoint = new IPEndPoint(IPAddress.Parse(args[1]), int.Parse(args[2]));
+                            if (args[3].ToLower() == "dispatch")
+                            {
+                                //Start TCP Server and listening
+                                //Accept and Get Client MAC
+                                //Create Bdd If Not Exist
+                                //Serving Bdd in `forked' Process use Socket.DuplicateAndClose
+                                throw new NotImplementedException();
+                            }
+                            args = args.Skip(3).ToArray();
+                            goto case "mount";
+                        case "mount":
+                            storage = ParseMount(args.Skip(1).ToArray());
+                            break;
+                    }
+
+                    ServiceBase.Run(new ServiceBase[] { new SvdServiceForWindows(new SvdService(storage, serverEndPoint)) });
+                    break;
+
+                default:
+                    switch (args[0].ToLower())
+                    {
+                        case "create":
+                            ParseCreate(args.Skip(1).ToArray());
+                            return;
+
+                        case "compact":
+                            ParseCompact(args.Skip(1).ToArray());
+                            return;
+
+                        case "server":
+                            serverEndPoint = new IPEndPoint(IPAddress.Parse(args[1]), int.Parse(args[2]));
+                            args = args.Skip(3).ToArray();
+
+                            goto case "mount";
+                        case "mount":
+                            storage = ParseMount(args.Skip(1).ToArray());
+                            break;
+                    }
+
+                    var svc = new SvdService(storage, serverEndPoint);
+                    svc.Start();
+                    Console.WriteLine();
+                    Console.Write("Press ENTER to Stop");
+                    Console.ReadLine();
+                    svc.Stop();
+                    break;
+            }
+        }
+
+        private static void ParseCreate(string[] args)
+        {
+            //Raw 50GB C:\Path\To\Your\Image.img
+            //Bdd 10GB 64KB C:\Path\To\Your\Bdd.bdd
+            //Bdd 64KB C:\Path\To\Your\Image.img C:\Path\To\Your\Bdd.bdd
+
+            switch (args[0].ToLower())
+            {
+                case "raw":
+                case "bdd":
+                default: throw new ArgumentException("Unknown image type");
+            }
+        }
+
+        private static IStorage ParseMount(string[] args)
+        {
+            //RamDisk 10GB
+            //RamDisk 10GB C:\Path\To\Your\Bdd.Bdd [ReadOnly]
+            //Image C:\Path\To\Your\Image.img [ReadOnly]
+            //Image C:\Path\To\Your\Bdd.bdd [ReadOnly]
+            //Net 192.168.233.233 2333
+
+            switch (args[0].ToLower())
+            {
+                case "ramdisk":
+
+                    if (ParseDiskSize(args[1], out var value, out var unit))
+                    {
+                        if (false == string.IsNullOrWhiteSpace(unit) && false == unit.StartsWith("g"))
+                            throw new ArgumentException("RamDisk only GB supported");
+                        var storage = new UnmanagedGigabyteRamDisk(value);
+
+                        //TODO: load bdd
+                        if (args.Length > 2) throw new NotImplementedException();
+
+                        if (args.Length > 3 && args[3].ToLower() == "readonly") storage.WriteProtect = true;
+
+                        return storage;
+                    }
+
+                    throw new ArgumentException("Failure to parse args");
+
+                case "image":
+                case "net":
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
+        }
+
+        private static void ParseCompact(string[] args)
+        {
+            //Bdd C:\Path\To\Your\Bdd.bdd
+            throw new NotImplementedException();
+        }
+
+        //////////////////////////////////////////////
+
+        private static readonly Regex DiskSizeRegex = new Regex(@"^(\d+)([KkMmGg]{1}[Bb]{0,1}){0,1}$", RegexOptions.Compiled);
+
+        private static bool ParseDiskSize(string input, out int value, out string unit)
+        {
+            var m = DiskSizeRegex.Match(input);
+            if (false == m.Success)
+            {
+                value = 0;
+                unit = null;
+                return false;
+            }
+
+            unit = m.Groups[2].Value.ToLower();
+            value = int.Parse(m.Groups[1].Value);
+            return true;
+        }
+
+        private static long CalcDiskSize(int value, string unit)
+        {
+            switch (unit.ToLower())
+            {
+                case "":
+                    return value;
+
+                case "k":
+                case "kb":
+                    return value * CapacityUnits.KiloByte;
+
+                case "m":
+                case "mb":
+                    return value * CapacityUnits.MegaByte;
+
+                case "g":
+                case "gb":
+                    return value * CapacityUnits.Gigabyte;
+
+                default: throw new ArgumentOutOfRangeException();
+            }
+        }
+    }
+}

+ 79 - 0
SvdCli/DriverAdapt/VirtualDrive.cs

@@ -0,0 +1,79 @@
+using System;
+using System.Linq;
+using Spd;
+using Spd.Interop;
+using SvdCli.Storage;
+
+namespace SvdCli.DriverAdapt
+{
+    public class VirtualDrive
+    {
+        private readonly IStorage _storage;
+        private readonly SpdImplement _spdImplement;
+        private readonly StorageUnitHost _host;
+
+        public long Size { get; }
+
+        public VirtualDrive(IStorage storage)
+        {
+            _storage = storage;
+            _spdImplement = new SpdImplement(_storage);
+            _host = new StorageUnitHost(_spdImplement)
+            {
+                ProductId = storage.ProductId,
+                ProductRevisionLevel = storage.ProductVersion,
+                WriteProtected = storage.WriteProtect,
+                UnmapSupported = storage.SupportTrim,
+                BlockCount = storage.BlockCount,
+                BlockLength = storage.BlockSize,
+                MaxTransferLength = storage.MaxTransferLength,
+                CacheSupported = true,
+                EjectDisabled = false,
+                Guid = Guid.NewGuid(),
+            };
+
+            Size = storage.BlockSize * storage.BlockSize;
+        }
+
+        public void Attach()
+        {
+            _storage.Init();
+            _host.Start();
+        }
+
+        public void Detach()
+        {
+            _host.Shutdown();
+            _storage.Exit();
+        }
+
+        private class SpdImplement : StorageUnitBase
+        {
+            private readonly IStorage _storage;
+
+            public SpdImplement(IStorage storage) => _storage = storage;
+
+            public override void Read(byte[] buffer, ulong blockAddress, uint blockCount, bool flush, ref StorageUnitStatus status)
+            {
+                if (flush) _storage.Flush();
+                _storage.Read(buffer, blockAddress, blockCount);
+            }
+
+            public override void Write(byte[] buffer, ulong blockAddress, uint blockCount, bool flush, ref StorageUnitStatus status)
+            {
+                _storage.Write(buffer, blockAddress, blockCount);
+                if (flush) _storage.Flush();
+            }
+
+            public override void Flush(ulong blockAddress, uint blockCount, ref StorageUnitStatus status)
+            {
+                _storage.Flush();
+            }
+
+            public override void Unmap(UnmapDescriptor[] descriptors, ref StorageUnitStatus status)
+            {
+                _storage.Trim(descriptors.Select(p => new TrimDescriptor(p.BlockAddress, p.BlockCount)).ToArray());
+            }
+        }
+    }
+}

+ 36 - 0
SvdCli/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("SvdCli")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SvdCli")]
+[assembly: AssemblyCopyright("Copyright ©  2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("24cc0601-e903-4ba2-8918-036e4d43ca3f")]
+
+// 程序集的版本信息由下列四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
+//通过使用 "*",如下所示:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("0.9.0.0")]
+[assembly: AssemblyFileVersion("0.9.0.0")]

+ 6 - 0
SvdCli/ServiceUtils/SOURCE.txt

@@ -0,0 +1,6 @@
+From 
+
+https://www.cnblogs.com/ahdung/p/4587003.html
+https://cloud.tencent.com/developer/article/1336800
+
+With Modify

+ 12 - 0
SvdCli/ServiceUtils/ServiceAccount.cs

@@ -0,0 +1,12 @@
+namespace SvdCli.ServiceUtils
+{
+    /// <summary>
+    /// 服务运行帐户
+    /// </summary>
+    public enum ServiceAccount
+    {
+        LocalSystem,
+        LocalService,
+        NetworkService,
+    }
+}

+ 423 - 0
SvdCli/ServiceUtils/ServiceHelper.cs

@@ -0,0 +1,423 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using Microsoft.Win32;
+
+namespace SvdCli.ServiceUtils
+{
+    /// <summary>
+    /// Windows 服务辅助类
+    /// </summary>
+    public static class ServiceHelper
+    {
+        /// <summary> 安装服务 </summary>
+        /// <param name="serviceName">服务名</param>
+        /// <param name="displayName">友好名称</param>
+        /// <param name="binaryFilePath">映像文件路径,可带参数</param>
+        /// <param name="description">服务描述</param>
+        /// <param name="startType">启动类型</param>
+        /// <param name="account">启动账户</param>
+        /// <param name="dependencies">依赖服务</param>
+        public static void Install(string serviceName, string displayName, string binaryFilePath, string description, ServiceStartType startType, ServiceAccount account = ServiceAccount.LocalSystem, string[] dependencies = null)
+        {
+            IntPtr scm = OpenSCManager();
+
+            IntPtr service = IntPtr.Zero;
+            try
+            {
+                service = Win32Class.CreateService(scm, serviceName, displayName, Win32Class.SERVICE_ALL_ACCESS, Win32Class.SERVICE_WIN32_OWN_PROCESS, startType, Win32Class.SERVICE_ERROR_NORMAL, binaryFilePath, null, IntPtr.Zero, ProcessDependencies(dependencies), GetServiceAccountName(account), null);
+
+                if (service == IntPtr.Zero)
+                {
+                    if (Marshal.GetLastWin32Error() == 0x431)//ERROR_SERVICE_EXISTS
+                    { throw new ApplicationException("服务已存在!"); }
+
+                    throw new ApplicationException("服务安装失败!");
+                }
+
+                //设置服务描述
+                Win32Class.SERVICE_DESCRIPTION sd = new Win32Class.SERVICE_DESCRIPTION();
+                try
+                {
+                    sd.description = Marshal.StringToHGlobalUni(description);
+                    Win32Class.ChangeServiceConfig2(service, 1, ref sd);
+                }
+                finally
+                {
+                    Marshal.FreeHGlobal(sd.description); //释放
+                }
+            }
+            finally
+            {
+                if (service != IntPtr.Zero)
+                {
+                    Win32Class.CloseServiceHandle(service);
+                }
+                Win32Class.CloseServiceHandle(scm);
+            }
+        }
+
+        public static string GetBinPath(string serviceName)
+        {
+            var key = Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\services\{serviceName}");
+
+            return key?.GetValue("ImagePath") == null
+                ? null
+                : key.GetValue("ImagePath").ToString();
+        }
+
+        /// <summary> 卸载服务 </summary>
+        /// <param name="serviceName">服务名</param>
+        public static void Uninstall(string serviceName)
+        {
+            IntPtr scmHandle = IntPtr.Zero;
+            IntPtr service = IntPtr.Zero;
+
+            try
+            {
+                service = OpenService(serviceName, out scmHandle);
+
+                StopService(service); //停止服务。里面会递归停止从属服务
+
+                if (!Win32Class.DeleteService(service) && Marshal.GetLastWin32Error() != 0x430) //忽略已标记为删除的服务。ERROR_SERVICE_MARKED_FOR_DELETE
+                {
+                    throw new ApplicationException("删除服务失败!");
+                }
+            }
+            catch (ServiceNotExistException) { } //忽略服务不存在的情况
+            finally
+            {
+                if (service != IntPtr.Zero)
+                {
+                    Win32Class.CloseServiceHandle(service);
+                    Win32Class.CloseServiceHandle(scmHandle);//放if里面是因为如果服务打开失败,在OpenService里就已释放SCM
+                }
+            }
+        }
+
+        /// <summary> 转换帐户枚举为有效参数 </summary>
+        private static string GetServiceAccountName(ServiceAccount account)
+        {
+            if (account == ServiceAccount.LocalService)
+            {
+                return @"NT AUTHORITY\LocalService";
+            }
+            if (account == ServiceAccount.NetworkService)
+            {
+                return @"NT AUTHORITY\NetworkService";
+            }
+            return null;
+        }
+
+        /// <summary> 处理依赖服务参数 </summary>
+        private static string ProcessDependencies(string[] dependencies)
+        {
+            if (dependencies == null || dependencies.Length == 0)
+            {
+                return null;
+            }
+
+            StringBuilder sb = new StringBuilder();
+            foreach (string s in dependencies)
+            {
+                sb.Append(s).Append('\0');
+            }
+            sb.Append('\0');
+
+            return sb.ToString();
+        }
+
+        /// <summary> 打开服务管理器 </summary>
+        private static IntPtr OpenSCManager()
+        {
+            IntPtr scm = Win32Class.OpenSCManager(null, null, Win32Class.SC_MANAGER_ALL_ACCESS);
+
+            if (scm == IntPtr.Zero)
+            {
+                throw new ApplicationException("打开服务管理器失败!");
+            }
+
+            return scm;
+        }
+
+        /// <summary> 打开服务 </summary>
+        /// <param name="serviceName">服务名称</param>
+        /// <param name="scmHandle">服务管理器句柄。供调用者释放</param>
+        private static IntPtr OpenService(string serviceName, out IntPtr scmHandle)
+        {
+            scmHandle = OpenSCManager();
+
+            IntPtr service = Win32Class.OpenService(scmHandle, serviceName, Win32Class.SERVICE_ALL_ACCESS);
+
+            if (service == IntPtr.Zero)
+            {
+                int errCode = Marshal.GetLastWin32Error();
+
+                Win32Class.CloseServiceHandle(scmHandle); //关闭SCM
+
+                if (errCode == 0x424) //ERROR_SERVICE_DOES_NOT_EXIST
+                {
+                    throw new ServiceNotExistException();
+                }
+
+                throw new Win32Exception();
+            }
+
+            return service;
+        }
+
+        /// <summary> 停止服务 </summary>
+        private static void StopService(IntPtr service)
+        {
+            ServiceState currState = GetServiceStatus(service);
+
+            if (currState == ServiceState.Stopped)
+            {
+                return;
+            }
+
+            if (currState != ServiceState.StopPending)
+            {
+                //递归停止从属服务
+                string[] childSvs = EnumDependentServices(service, EnumServiceState.Active);
+                if (childSvs.Length != 0)
+                {
+                    IntPtr scm = OpenSCManager();
+                    try
+                    {
+                        foreach (string childSv in childSvs)
+                        {
+                            StopService(Win32Class.OpenService(scm, childSv, Win32Class.SERVICE_STOP));
+                        }
+                    }
+                    finally
+                    {
+                        Win32Class.CloseServiceHandle(scm);
+                    }
+                }
+
+                Win32Class.SERVICE_STATUS status = new Win32Class.SERVICE_STATUS();
+                Win32Class.ControlService(service, Win32Class.SERVICE_CONTROL_STOP, ref status); //发送停止指令
+            }
+
+            if (!WaitForStatus(service, ServiceState.Stopped, new TimeSpan(0, 0, 30)))
+            {
+                throw new ApplicationException("停止服务失败!");
+            }
+        }
+
+        /// <summary> 遍历从属服务 </summary>
+        /// <param name="serviceHandle"></param>
+        /// <param name="state">选择性遍历(活动、非活动、全部)</param>
+        private static string[] EnumDependentServices(IntPtr serviceHandle, EnumServiceState state)
+        {
+            int bytesNeeded = 0; //存放从属服务的空间大小,由API返回
+            int numEnumerated = 0; //从属服务数,由API返回
+
+            //先尝试以空结构获取,如获取成功说明从属服务为空,否则拿到上述俩值
+            if (Win32Class.EnumDependentServices(serviceHandle, state, IntPtr.Zero, 0, ref bytesNeeded, ref numEnumerated))
+            {
+                return new string[0];
+            }
+            if (Marshal.GetLastWin32Error() != 0xEA) //仅当错误值不是大小不够(ERROR_MORE_DATA)时才抛异常
+            {
+                throw new Win32Exception();
+            }
+
+            //在非托管区域创建指针
+            IntPtr structsStart = Marshal.AllocHGlobal(new IntPtr(bytesNeeded));
+            try
+            {
+                //往上述指针处塞存放从属服务的结构组,每个从属服务是一个结构
+                if (!Win32Class.EnumDependentServices(serviceHandle, state, structsStart, bytesNeeded, ref bytesNeeded, ref numEnumerated))
+                {
+                    throw new Win32Exception();
+                }
+
+                string[] dependentServices = new string[numEnumerated];
+                int sizeOfStruct = Marshal.SizeOf(typeof(Win32Class.ENUM_SERVICE_STATUS)); //每个结构的大小
+                long structsStartAsInt64 = structsStart.ToInt64();
+                for (int i = 0; i < numEnumerated; i++)
+                {
+                    Win32Class.ENUM_SERVICE_STATUS structure = new Win32Class.ENUM_SERVICE_STATUS();
+                    IntPtr ptr = new IntPtr(structsStartAsInt64 + i * sizeOfStruct); //根据起始指针、结构次序和结构大小推算各结构起始指针
+                    Marshal.PtrToStructure(ptr, structure); //根据指针拿到结构
+                    dependentServices[i] = structure.serviceName; //从结构中拿到服务名
+                }
+
+                return dependentServices;
+            }
+            finally
+            {
+                Marshal.FreeHGlobal(structsStart);
+            }
+        }
+
+        /// <summary> 获取服务状态 </summary>
+        private static ServiceState GetServiceStatus(IntPtr service)
+        {
+            Win32Class.SERVICE_STATUS status = new Win32Class.SERVICE_STATUS();
+
+            if (!Win32Class.QueryServiceStatus(service, ref status))
+            {
+                throw new ApplicationException("获取服务状态出错!");
+            }
+
+            return status.currentState;
+        }
+
+        /// <summary> 等候服务至目标状态 </summary>
+        private static bool WaitForStatus(IntPtr serviceHandle, ServiceState desiredStatus, TimeSpan timeout)
+        {
+            DateTime startTime = DateTime.Now;
+
+            while (GetServiceStatus(serviceHandle) != desiredStatus)
+            {
+                if (DateTime.Now - startTime > timeout) { return false; }
+
+                Thread.Sleep(200);
+            }
+
+            return true;
+        }
+
+        /// <summary> Win32 API相关 </summary>
+        private static class Win32Class
+        {
+            #region 常量定义
+
+            /// <summary>
+            /// 打开服务管理器时请求的权限:全部
+            /// </summary>
+            public const int SC_MANAGER_ALL_ACCESS = 0xF003F;
+
+            /// <summary>
+            /// 服务类型:自有进程类服务
+            /// </summary>
+            public const int SERVICE_WIN32_OWN_PROCESS = 0x10;
+
+            /// <summary>
+            /// 打开服务时请求的权限:全部
+            /// </summary>
+            public const int SERVICE_ALL_ACCESS = 0xF01FF;
+
+            /// <summary>
+            /// 打开服务时请求的权限:停止
+            /// </summary>
+            public const int SERVICE_STOP = 0x20;
+
+            /// <summary>
+            /// 服务操作标记:停止
+            /// </summary>
+            public const int SERVICE_CONTROL_STOP = 0x1;
+
+            /// <summary>
+            /// 服务出错行为标记
+            /// </summary>
+            public const int SERVICE_ERROR_NORMAL = 0x1;
+
+            #endregion 常量定义
+
+            #region API所需类和结构定义
+
+            /// <summary>
+            /// 服务状态结构体
+            /// </summary>
+            [StructLayout(LayoutKind.Sequential)]
+            public struct SERVICE_STATUS
+            {
+                public int serviceType;
+                public ServiceState currentState;
+                public int controlsAccepted;
+                public int win32ExitCode;
+                public int serviceSpecificExitCode;
+                public int checkPoint;
+                public int waitHint;
+            }
+
+            /// <summary>
+            /// 服务描述结构体
+            /// </summary>
+            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+            public struct SERVICE_DESCRIPTION
+            {
+                public IntPtr description;
+            }
+
+            /// <summary>
+            /// 服务状态结构体。遍历API会用到
+            /// </summary>
+            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+            public class ENUM_SERVICE_STATUS
+            {
+                public string serviceName;
+                public string displayName;
+                public int serviceType;
+                public int currentState;
+                public int controlsAccepted;
+                public int win32ExitCode;
+                public int serviceSpecificExitCode;
+                public int checkPoint;
+                public int waitHint;
+            }
+
+            #endregion API所需类和结构定义
+
+            #region API定义
+
+            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+            public static extern bool ChangeServiceConfig2(IntPtr serviceHandle, uint infoLevel, ref SERVICE_DESCRIPTION serviceDesc);
+
+            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+            public static extern IntPtr OpenSCManager(string machineName, string databaseName, int dwDesiredAccess);
+
+            [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+            public static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, int dwDesiredAccess);
+
+            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+            public static extern IntPtr CreateService(IntPtr hSCManager, string lpServiceName, string lpDisplayName, int dwDesiredAccess, int dwServiceType, ServiceStartType dwStartType, int dwErrorControl, string lpBinaryPathName, string lpLoadOrderGroup, IntPtr lpdwTagId, string lpDependencies, string lpServiceStartName, string lpPassword);
+
+            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+            public static extern bool CloseServiceHandle(IntPtr handle);
+
+            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+            public static extern bool QueryServiceStatus(IntPtr hService, ref SERVICE_STATUS lpServiceStatus);
+
+            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+            public static extern bool DeleteService(IntPtr serviceHandle);
+
+            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+            public static extern bool ControlService(IntPtr hService, int dwControl, ref SERVICE_STATUS lpServiceStatus);
+
+            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+            public static extern bool EnumDependentServices(IntPtr serviceHandle, EnumServiceState serviceState, IntPtr bufferOfENUM_SERVICE_STATUS, int bufSize, ref int bytesNeeded, ref int numEnumerated);
+
+            #endregion API定义
+        }
+
+        /// <summary> 服务状态枚举。用于遍历从属服务API </summary>
+        private enum EnumServiceState
+        {
+            Active = 1,
+            //InActive = 2,
+            //All = 3
+        }
+
+        /// <summary> 服务状态 </summary>
+        private enum ServiceState
+        {
+            Stopped = 1,
+
+            //StartPending = 2,
+            StopPending = 3,
+
+            //Running = 4,
+            //ContinuePending = 5,
+            //PausePending = 6,
+            //Paused = 7
+        }
+    }
+}

+ 26 - 0
SvdCli/ServiceUtils/ServiceNotExistException.cs

@@ -0,0 +1,26 @@
+using System;
+
+namespace SvdCli.ServiceUtils
+{
+    #region 异常定义
+
+    /// <summary>
+    /// 服务不存在异常
+    /// </summary>
+    public class ServiceNotExistException : ApplicationException
+    {
+        public ServiceNotExistException() : base("服务不存在!")
+        {
+        }
+
+        public ServiceNotExistException(string message) : base(message)
+        {
+        }
+    }
+
+    #endregion 异常定义
+
+    #region 枚举定义
+
+    #endregion 枚举定义
+}

+ 14 - 0
SvdCli/ServiceUtils/ServiceStartType.cs

@@ -0,0 +1,14 @@
+namespace SvdCli.ServiceUtils
+{
+    /// <summary>
+    /// 服务启动类型
+    /// </summary>
+    public enum ServiceStartType
+    {
+        Boot,
+        System,
+        Auto,
+        Manual,
+        Disabled
+    }
+}

+ 9 - 0
SvdCli/Storage/CapacityUnits.cs

@@ -0,0 +1,9 @@
+namespace SvdCli.Storage
+{
+    public static class CapacityUnits
+    {
+        public const int KiloByte = 1024;
+        public const int MegaByte = 1024 * KiloByte;
+        public const int Gigabyte = 1024 * MegaByte;
+    }
+}

+ 27 - 0
SvdCli/Storage/IStorage.cs

@@ -0,0 +1,27 @@
+namespace SvdCli.Storage
+{
+    public interface IStorage
+    {
+        string ProductId { get; }
+        string ProductVersion { get; }
+
+        uint BlockSize { get; }
+        ulong BlockCount { get; }
+        uint MaxTransferLength { get; }
+
+        bool SupportTrim { get; }
+        bool WriteProtect { get; }
+
+        void Init();
+
+        void Exit();
+
+        void Read(byte[] buffer, ulong blockIndex, uint blockCount);
+
+        void Write(byte[] buffer, ulong blockIndex, uint blockCount);
+
+        void Flush();
+
+        void Trim(params TrimDescriptor[] descriptors);
+    }
+}

+ 120 - 0
SvdCli/Storage/RamStorage.cs

@@ -0,0 +1,120 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace SvdCli.Storage
+{
+    public class UnmanagedGigabyteRamDisk : IStorage
+    {
+        private readonly IntPtr[] _blocks;
+        private bool _isInit;
+        public string ProductId { get; } = "Svd RamDisk";
+        public string ProductVersion { get; } = "1.0";
+        public string TypeDescription { get; } = "RamDisk";
+        public uint BlockSize { get; } = 512;
+        public ulong BlockCount { get; }
+        public uint MaxTransferLength { get; } = 64 * CapacityUnits.KiloByte;
+        public bool SupportTrim { get; } = false;
+        public bool WriteProtect { get; set; } = false;
+
+        public void Init()
+        {
+            if (_isInit) return;
+
+            try
+            {
+                for (var i = 0; i < _blocks.Length; i++)
+                {
+                    _blocks[i] = Marshal.AllocHGlobal(CapacityUnits.Gigabyte);
+                }
+
+                _isInit = true;
+            }
+            catch (OutOfMemoryException)
+            {
+                Exit();
+                throw;
+            }
+        }
+
+        public void Exit()
+        {
+            foreach (var block in _blocks)
+            {
+                if (block != IntPtr.Zero) Marshal.FreeHGlobal(block);
+            }
+            GC.Collect();
+            GC.WaitForPendingFinalizers();
+            _isInit = false;
+        }
+
+        public UnmanagedGigabyteRamDisk(int sizeInGigabyte)
+        {
+            _blocks = new IntPtr[sizeInGigabyte];
+            var size = (ulong)sizeInGigabyte * CapacityUnits.Gigabyte;
+            BlockCount = size / BlockSize;
+        }
+
+        private void ReadInternal(byte[] buffer, int offset, int count, int gigabyteIndex, int gigabyteOffset)
+        {
+            Marshal.Copy(_blocks[gigabyteIndex] + gigabyteOffset, buffer, offset, count);
+        }
+
+        private void WriteInternal(byte[] buffer, int offset, int count, int gigabyteIndex, int gigabyteOffset)
+        {
+            Marshal.Copy(buffer, offset, _blocks[gigabyteIndex] + gigabyteOffset, count);
+        }
+
+        public void Read(byte[] buffer, ulong blockIndex, uint blockCount)
+        {
+            var rawOffset = blockIndex * BlockSize;
+            var rawSize = blockCount * BlockSize;
+
+            for (var i = 0; i < rawSize;)
+            {
+                var gigabyteIndex = (int)(rawOffset / CapacityUnits.Gigabyte);
+                var gigabyteOffset = (int)(rawOffset % CapacityUnits.Gigabyte);
+                var bytesToRead = CapacityUnits.Gigabyte - gigabyteOffset;
+
+                if (i + bytesToRead > rawSize)
+                {
+                    bytesToRead = (int)(rawSize - i);
+                }
+
+                ReadInternal(buffer, i, bytesToRead, gigabyteIndex, gigabyteOffset);
+
+                i += bytesToRead;
+                rawOffset += (ulong)bytesToRead;
+            }
+        }
+
+        public void Write(byte[] buffer, ulong blockIndex, uint blockCount)
+        {
+            var rawOffset = blockIndex * BlockSize;
+
+            for (var i = 0; i < buffer.Length;)
+            {
+                var gigabyteIndex = (int)(rawOffset / CapacityUnits.Gigabyte);
+                var gigabyteOffset = (int)(rawOffset % CapacityUnits.Gigabyte);
+                var bytesToWrite = CapacityUnits.Gigabyte - gigabyteOffset;
+
+                if (i + bytesToWrite > buffer.Length)
+                {
+                    bytesToWrite = buffer.Length - i;
+                }
+
+                WriteInternal(buffer, i, bytesToWrite, gigabyteIndex, gigabyteOffset);
+
+                i += bytesToWrite;
+                rawOffset += (ulong)bytesToWrite;
+            }
+        }
+
+        public void Flush()
+        {
+        }
+
+        public void Trim(params TrimDescriptor[] descriptors)
+        {
+        }
+    }
+}

+ 18 - 0
SvdCli/Storage/TrimDescriptor.cs

@@ -0,0 +1,18 @@
+namespace SvdCli.Storage
+{
+    public class TrimDescriptor
+    {
+        public TrimDescriptor(ulong blockIndex, uint blockCount)
+        {
+            BlockIndex = blockIndex;
+            BlockCount = blockCount;
+        }
+
+        public TrimDescriptor()
+        {
+        }
+
+        public ulong BlockIndex { get; set; }
+        public uint BlockCount { get; set; }
+    }
+}

+ 94 - 0
SvdCli/SvdCli.csproj

@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props" Condition="Exists('C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props')" />
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{24CC0601-E903-4BA2-8918-036E4D43CA3F}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>SvdCli</RootNamespace>
+    <AssemblyName>Svd</AssemblyName>
+    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Prefer32Bit>false</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Prefer32Bit>false</Prefer32Bit>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.ServiceProcess" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+    <Reference Include="winspd-msil">
+      <HintPath>C:\Program Files (x86)\WinSpd\bin\winspd-msil.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="CliProgram.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ServiceUtils\ServiceAccount.cs" />
+    <Compile Include="ServiceUtils\ServiceHelper.cs" />
+    <Compile Include="ServiceUtils\ServiceNotExistException.cs" />
+    <Compile Include="ServiceUtils\ServiceStartType.cs" />
+    <Compile Include="Storage\CapacityUnits.cs" />
+    <Compile Include="Storage\IStorage.cs" />
+    <Compile Include="Storage\RamStorage.cs" />
+    <Compile Include="Storage\TrimDescriptor.cs" />
+    <Compile Include="App\SvdService.cs" />
+    <Compile Include="WindowsService\SvdServiceForWindows.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="DriverAdapt\VirtualDrive.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="ServiceUtils\SOURCE.txt" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <PropertyGroup>
+    <PostBuildEvent>if $(ConfigurationName) == Release setlocal enabledelayedexpansion enableextensions
+if $(ConfigurationName) == Release set DLL_LIST=
+if $(ConfigurationName) == Release for %25%25x in ($(TargetDir)*.dll) do set DLL_LIST=!DLL_LIST! "%25%25x"
+if $(ConfigurationName) == Release echo dlls: !DLL_LIST!
+if $(ConfigurationName) == Release if not exist "$(TargetDir)Packed" md "$(TargetDir)Packed"
+if $(ConfigurationName) == Release $(ILRepack) /ndebug "/out:$(TargetDir)Packed\$(TargetFileName)" "$(TargetPath)" !DLL_LIST!
+if $(ConfigurationName) == Release if exist "$(TargetDir)Packed\$(TargetFileName).config" del "$(TargetDir)Packed\$(TargetFileName).config"</PostBuildEvent>
+  </PropertyGroup>
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props')" Text="$([System.String]::Format('$(ErrorText)', 'C:\NuGetLocalRepo\ILRepack.2.0.18\build\ILRepack.props'))" />
+  </Target>
+</Project>

+ 25 - 0
SvdCli/WindowsService/SvdServiceForWindows.cs

@@ -0,0 +1,25 @@
+using System.ServiceProcess;
+using SvdCli.App;
+
+namespace SvdCli.WindowsService
+{
+    internal class SvdServiceForWindows : ServiceBase
+    {
+        private readonly SvdService _service;
+
+        public SvdServiceForWindows(SvdService service)
+        {
+            _service = service;
+        }
+
+        protected override void OnStart(string[] args)
+        {
+            _service.Start();
+        }
+
+        protected override void OnStop()
+        {
+            _service.Stop();
+        }
+    }
+}

+ 4 - 0
SvdCli/packages.config

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="ILRepack" version="2.0.18" targetFramework="net461" />
+</packages>