diff --git a/product/dropkick.tests/Tasks/WinService/WinTestsWithAuthentication.cs b/product/dropkick.tests/Tasks/WinService/WinTestsWithAuthentication.cs new file mode 100644 index 00000000..c6a6e82a --- /dev/null +++ b/product/dropkick.tests/Tasks/WinService/WinTestsWithAuthentication.cs @@ -0,0 +1,126 @@ +using dropkick.DeploymentModel; +using dropkick.Tasks.WinService; +using dropkick.Wmi; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace dropkick.tests.Tasks.WinService +{ + [TestFixture] + [Category("Integration")] + public class WinTestsWithAuthentication + { + public class WmiAuthenticationInfo + { + public string MachineName { get; set; } + public string WmiUserName { get; set; } + public string WmiPassword { get; set; } + public string ServiceUserName { get; set; } + public string ServicePassword { get; set; } + } + + private WmiAuthenticationInfo GetAuthenticationInfo() + { + string path = System.IO.Path.GetFullPath("WmiAuthenticationInfo.xml"); + if (!System.IO.File.Exists(path)) + { + throw new Exception("Please create a settings file first at: " + path); + } + var serializer = new XmlSerializer(typeof(WmiAuthenticationInfo)); + using (var reader = new System.IO.StreamReader(path)) + { + return (WmiAuthenticationInfo)serializer.Deserialize(reader); + } + } + + [Test] + [Explicit] + [Category("Integration")] + public void Start() + { + var authInfo = GetAuthenticationInfo(); + + WmiService.WithAuthentication(authInfo.WmiUserName, authInfo.WmiPassword); + + var t = new WinServiceStopTask(authInfo.MachineName, "IISADMIN"); + var verifyStopResult = t.VerifyCanRun(); + Log(verifyStopResult); + AssertSuccess(verifyStopResult); + + var stopResult = t.Execute(); + Log(stopResult); + AssertSuccess(stopResult); + + var t2 = new WinServiceStartTask(authInfo.MachineName, "IISADMIN"); + var verifyStartResult = t2.VerifyCanRun(); + Log(verifyStartResult); + AssertSuccess(verifyStartResult); + + var startResult = t2.Execute(); + Log(startResult); + AssertSuccess(startResult); + } + + [Test] + [Explicit] + public void RemoteCreate() + { + var authInfo = GetAuthenticationInfo(); + + WmiService.WithAuthentication(authInfo.WmiUserName, authInfo.WmiPassword); + var t = new WinServiceCreateTask(authInfo.MachineName, "DropKicKTestService"); + + t.ServiceLocation = "C:\\Test\\TestService.exe"; + t.StartMode = ServiceStartMode.Automatic; + t.UserName = authInfo.ServiceUserName; + t.Password = authInfo.ServicePassword; + + DeploymentResult o = t.VerifyCanRun(); + AssertSuccess(o); + var result = t.Execute(); + Log(result); + AssertSuccess(result); + } + + [Test] + [Explicit] + [Category("Integration")] + public void RemoteDelete() + { + var authInfo = GetAuthenticationInfo(); + + WmiService.WithAuthentication(authInfo.ServiceUserName, authInfo.ServicePassword); + + var t = new WinServiceDeleteTask(authInfo.MachineName, "DropkicKTestService"); + + DeploymentResult o = t.VerifyCanRun(); + Log(o); + AssertSuccess(o); + var result = t.Execute(); + Log(result); + AssertSuccess(result); + } + + private void AssertSuccess(DeploymentResult result) + { + Assert.IsFalse(result.Any(i => i.Status == DeploymentItemStatus.Alert || i.Status == DeploymentItemStatus.Error)); + } + + private void Log(DeploymentResult result) + { + if (result != null) + { + foreach (var item in result) + { + Debug.WriteLine(item.Message); + } + } + } + + } +} diff --git a/product/dropkick.tests/dropkick.tests.csproj b/product/dropkick.tests/dropkick.tests.csproj index 902aaa25..afa676b5 100644 --- a/product/dropkick.tests/dropkick.tests.csproj +++ b/product/dropkick.tests/dropkick.tests.csproj @@ -138,6 +138,7 @@ + diff --git a/product/dropkick/Configuration/Dsl/Authentication/Extension.cs b/product/dropkick/Configuration/Dsl/Authentication/Extension.cs new file mode 100644 index 00000000..e60e86a5 --- /dev/null +++ b/product/dropkick/Configuration/Dsl/Authentication/Extension.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using dropkick.Wmi; +using dropkick.StringInterpolation; + +namespace dropkick.Configuration.Dsl.Authentication +{ + public static class Extension + { + public static ProtoServer WithAuthentication(this ProtoServer server, string remoteUserName, string remotePassword) + { + var interpolator = new CaseInsensitiveInterpolator(); + remoteUserName = interpolator.ReplaceTokens(HUB.Settings, remoteUserName); + remotePassword = interpolator.ReplaceTokens(HUB.Settings, remotePassword); + WmiService.WithAuthentication(remoteUserName, remotePassword); + return server; + } + } +} diff --git a/product/dropkick/Configuration/Dsl/Files/Extension.cs b/product/dropkick/Configuration/Dsl/Files/Extension.cs index 92d94c86..1d9aba9a 100644 --- a/product/dropkick/Configuration/Dsl/Files/Extension.cs +++ b/product/dropkick/Configuration/Dsl/Files/Extension.cs @@ -98,5 +98,11 @@ public static ExistsOptions Exists(this ProtoServer protoserver, string reason, protoserver.RegisterProtoTask(proto); return proto; } + + public static void OpenFolderShareWithAuthentication(this ProtoServer protoServer, string folderName, string userName, string password) + { + var task = new OpenFolderShareAuthenticationProtoTask(folderName, userName, password); + protoServer.RegisterProtoTask(task); + } } } \ No newline at end of file diff --git a/product/dropkick/Configuration/Dsl/Files/OpenFolderShareAuthenticationProtoTask.cs b/product/dropkick/Configuration/Dsl/Files/OpenFolderShareAuthenticationProtoTask.cs new file mode 100644 index 00000000..0db88863 --- /dev/null +++ b/product/dropkick/Configuration/Dsl/Files/OpenFolderShareAuthenticationProtoTask.cs @@ -0,0 +1,31 @@ +using dropkick.Tasks; +using dropkick.Tasks.Files; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace dropkick.Configuration.Dsl.Files +{ + public class OpenFolderShareAuthenticationProtoTask : BaseProtoTask + { + private readonly string _folderName; + private readonly string _userName; + private readonly string _password; + + public OpenFolderShareAuthenticationProtoTask(string folderName, string userName, string password) + { + _folderName = ReplaceTokens(folderName); + _userName = userName; + _password = password; + } + + public override void RegisterRealTasks(dropkick.DeploymentModel.PhysicalServer server) + { + string to = server.MapPath(_folderName); + + var task = new OpenFolderShareAuthenticationTask(to, _userName, _password); + server.AddTask(task); + } + } +} diff --git a/product/dropkick/Configuration/Dsl/WinService/Extension.cs b/product/dropkick/Configuration/Dsl/WinService/Extension.cs index e6db68e3..7554592d 100644 --- a/product/dropkick/Configuration/Dsl/WinService/Extension.cs +++ b/product/dropkick/Configuration/Dsl/WinService/Extension.cs @@ -17,6 +17,6 @@ public static class Extension public static WinServiceOptions WinService(this ProtoServer protoServer, string serviceName) { return new ProtoWinServiceTask(protoServer, serviceName); - } + } } } \ No newline at end of file diff --git a/product/dropkick/Configuration/Dsl/WinService/ProtoWinServiceTask.cs b/product/dropkick/Configuration/Dsl/WinService/ProtoWinServiceTask.cs index 9e524782..25df944c 100644 --- a/product/dropkick/Configuration/Dsl/WinService/ProtoWinServiceTask.cs +++ b/product/dropkick/Configuration/Dsl/WinService/ProtoWinServiceTask.cs @@ -27,7 +27,6 @@ public ProtoWinServiceTask(ProtoServer protoServer, string serviceName) _serviceName = serviceName; } - public WinServiceOptions Do(Action registerAdditionalActions) { _protoServer.RegisterProtoTask(new ProtoWinServiceStopTask(_serviceName)); @@ -35,7 +34,7 @@ public WinServiceOptions Do(Action registerAdditionalActions) //child task registerAdditionalActions(_protoServer); - + _protoServer.RegisterProtoTask(new ProtoWinServiceStartTask(_serviceName)); return this; diff --git a/product/dropkick/FileSystem/DotNetPath.cs b/product/dropkick/FileSystem/DotNetPath.cs index 3b017316..8b6d7ff8 100644 --- a/product/dropkick/FileSystem/DotNetPath.cs +++ b/product/dropkick/FileSystem/DotNetPath.cs @@ -25,7 +25,7 @@ public class DotNetPath : Path { #region Path Members - public string GetPhysicalPath(PhysicalServer site, string path,bool forceLocalPath) + public string GetPhysicalPath(PhysicalServer site, string path, bool forceLocalPath) { var standardizedPath = path; if (!IsUncPath(standardizedPath)) diff --git a/product/dropkick/FileSystem/FileShareAuthenticator.cs b/product/dropkick/FileSystem/FileShareAuthenticator.cs new file mode 100644 index 00000000..c28fa0f9 --- /dev/null +++ b/product/dropkick/FileSystem/FileShareAuthenticator.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace dropkick.FileSystem +{ + public static class FileShareAuthenticator + { + public static FileShareAuthenticationContext BeginFileShareAuthentication(string remoteUnc, string userName, string password) + { + string error = PinvokeWindowsNetworking.connectToRemote(remoteUnc, userName, password); + if (!string.IsNullOrEmpty(error)) + { + throw new Exception("Error calling PinvokeWindowsNetworking.connectToRemote: " + error); + } + return new FileShareAuthenticationContext(remoteUnc, userName, password); + } + + public class FileShareAuthenticationContext : IDisposable + { + private readonly string _remoteUnc; + private readonly string _userName; + private readonly string _password; + private bool _active; + + public FileShareAuthenticationContext(string remoteUnc, string userName, string password) + { + _remoteUnc = remoteUnc; + _userName = userName; + _password = password; + _active = true; + } + + public void Dispose() + { + if (_active) + { + var error = PinvokeWindowsNetworking.disconnectRemote(_remoteUnc); + if (!string.IsNullOrEmpty(error)) + { + throw new Exception("PinvokeWindowsNetworking.disconnectRemote failed: " + error); + } + _active = false; + } + } + } + } +} diff --git a/product/dropkick/FileSystem/PinvokeWindowsNetworking.cs b/product/dropkick/FileSystem/PinvokeWindowsNetworking.cs new file mode 100644 index 00000000..db868893 --- /dev/null +++ b/product/dropkick/FileSystem/PinvokeWindowsNetworking.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace dropkick.FileSystem +{ + //http://www.dimitrioskouzisloukas.com/blog/index.php?blog=2&title=c_windows_networking_library&more=1&c=1&tb=1&pb=1 + //http://lookfwd.doitforme.gr/blog/media/PinvokeWindowsNetworking.cs + public class PinvokeWindowsNetworking + { + #region Consts + const int RESOURCE_CONNECTED = 0x00000001; + const int RESOURCE_GLOBALNET = 0x00000002; + const int RESOURCE_REMEMBERED = 0x00000003; + + const int RESOURCETYPE_ANY = 0x00000000; + const int RESOURCETYPE_DISK = 0x00000001; + const int RESOURCETYPE_PRINT = 0x00000002; + + const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000; + const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001; + const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002; + const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003; + const int RESOURCEDISPLAYTYPE_FILE = 0x00000004; + const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005; + + const int RESOURCEUSAGE_CONNECTABLE = 0x00000001; + const int RESOURCEUSAGE_CONTAINER = 0x00000002; + + + const int CONNECT_INTERACTIVE = 0x00000008; + const int CONNECT_PROMPT = 0x00000010; + const int CONNECT_REDIRECT = 0x00000080; + const int CONNECT_UPDATE_PROFILE = 0x00000001; + const int CONNECT_COMMANDLINE = 0x00000800; + const int CONNECT_CMD_SAVECRED = 0x00001000; + + const int CONNECT_LOCALDRIVE = 0x00000100; + #endregion + + #region Errors + const int NO_ERROR = 0; + + const int ERROR_ACCESS_DENIED = 5; + const int ERROR_ALREADY_ASSIGNED = 85; + const int ERROR_BAD_DEVICE = 1200; + const int ERROR_BAD_NET_NAME = 67; + const int ERROR_BAD_PROVIDER = 1204; + const int ERROR_CANCELLED = 1223; + const int ERROR_EXTENDED_ERROR = 1208; + const int ERROR_INVALID_ADDRESS = 487; + const int ERROR_INVALID_PARAMETER = 87; + const int ERROR_INVALID_PASSWORD = 1216; + const int ERROR_MORE_DATA = 234; + const int ERROR_NO_MORE_ITEMS = 259; + const int ERROR_NO_NET_OR_BAD_PATH = 1203; + const int ERROR_NO_NETWORK = 1222; + + const int ERROR_BAD_PROFILE = 1206; + const int ERROR_CANNOT_OPEN_PROFILE = 1205; + const int ERROR_DEVICE_IN_USE = 2404; + const int ERROR_NOT_CONNECTED = 2250; + const int ERROR_OPEN_FILES = 2401; + + private struct ErrorClass + { + public int num; + public string message; + public ErrorClass(int num, string message) + { + this.num = num; + this.message = message; + } + } + + + // Created with excel formula: + // ="new ErrorClass("&A1&", """&PROPER(SUBSTITUTE(MID(A1,7,LEN(A1)-6), "_", " "))&"""), " + private static ErrorClass[] ERROR_LIST = new ErrorClass[] { + new ErrorClass(ERROR_ACCESS_DENIED, "Error: Access Denied"), + new ErrorClass(ERROR_ALREADY_ASSIGNED, "Error: Already Assigned"), + new ErrorClass(ERROR_BAD_DEVICE, "Error: Bad Device"), + new ErrorClass(ERROR_BAD_NET_NAME, "Error: Bad Net Name"), + new ErrorClass(ERROR_BAD_PROVIDER, "Error: Bad Provider"), + new ErrorClass(ERROR_CANCELLED, "Error: Cancelled"), + new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"), + new ErrorClass(ERROR_INVALID_ADDRESS, "Error: Invalid Address"), + new ErrorClass(ERROR_INVALID_PARAMETER, "Error: Invalid Parameter"), + new ErrorClass(ERROR_INVALID_PASSWORD, "Error: Invalid Password"), + new ErrorClass(ERROR_MORE_DATA, "Error: More Data"), + new ErrorClass(ERROR_NO_MORE_ITEMS, "Error: No More Items"), + new ErrorClass(ERROR_NO_NET_OR_BAD_PATH, "Error: No Net Or Bad Path"), + new ErrorClass(ERROR_NO_NETWORK, "Error: No Network"), + new ErrorClass(ERROR_BAD_PROFILE, "Error: Bad Profile"), + new ErrorClass(ERROR_CANNOT_OPEN_PROFILE, "Error: Cannot Open Profile"), + new ErrorClass(ERROR_DEVICE_IN_USE, "Error: Device In Use"), + new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"), + new ErrorClass(ERROR_NOT_CONNECTED, "Error: Not Connected"), + new ErrorClass(ERROR_OPEN_FILES, "Error: Open Files"), + }; + + private static string getErrorForNumber(int errNum) + { + foreach (ErrorClass er in ERROR_LIST) + { + if (er.num == errNum) return er.message; + } + return new Win32Exception(errNum).Message; + //return "Error: Unknown, " + errNum; + } + #endregion + + [DllImport("Mpr.dll")] + private static extern int WNetUseConnection( + IntPtr hwndOwner, + NETRESOURCE lpNetResource, + string lpPassword, + string lpUserID, + int dwFlags, + string lpAccessName, + string lpBufferSize, + string lpResult + ); + + [DllImport("Mpr.dll")] + private static extern int WNetCancelConnection2( + string lpName, + int dwFlags, + int fForce + ); + + [StructLayout(LayoutKind.Sequential)] + private class NETRESOURCE + { + public int dwScope = 0; + public int dwType = 0; + public int dwDisplayType = 0; + public int dwUsage = 0; + public string lpLocalName = ""; + public string lpRemoteName = ""; + public string lpComment = ""; + public string lpProvider = ""; + } + + + public static string connectToRemote(string remoteUNC, string username, string password) + { + return connectToRemote(remoteUNC, username, password, false); + } + + public static string connectToRemote(string remoteUNC, string username, string password, bool promptUser) + { + NETRESOURCE nr = new NETRESOURCE(); + nr.dwType = RESOURCETYPE_DISK; + nr.lpRemoteName = remoteUNC; + // nr.lpLocalName = "F:"; + + int ret; + if (promptUser) + ret = WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null); + else + ret = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null); + + if (ret == NO_ERROR) return null; + return getErrorForNumber(ret); + } + + public static string disconnectRemote(string remoteUNC) + { + int ret = WNetCancelConnection2(remoteUNC, CONNECT_UPDATE_PROFILE, 1); + if (ret == NO_ERROR) return null; + return getErrorForNumber(ret); + } + } +} diff --git a/product/dropkick/Tasks/Files/OpenFolderShareAuthenticationTask.cs b/product/dropkick/Tasks/Files/OpenFolderShareAuthenticationTask.cs new file mode 100644 index 00000000..f96978ba --- /dev/null +++ b/product/dropkick/Tasks/Files/OpenFolderShareAuthenticationTask.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using dropkick; +using dropkick.DeploymentModel; +using dropkick.FileSystem; +using System.Text.RegularExpressions; + +namespace dropkick.Tasks.Files +{ + public class OpenFolderShareAuthenticationTask : Task + { + private readonly string _to; + private readonly string _userName; + private readonly string _password; + + public OpenFolderShareAuthenticationTask(string to, string userName, string password) + { + _to = to; + _userName = userName; + _password = password; + } + + public string Name + { + get { return "Creating new empty folder '{0}' with user name '{1}'".FormatWith(_to, _userName); } + } + + public DeploymentResult VerifyCanRun() + { + var result = new DeploymentResult(); + var to = new DotNetPath().GetFullPath(_to); + string toParent = GetRootShare(to); + try + { + using (var context = FileShareAuthenticator.BeginFileShareAuthentication(toParent, _userName, _password)) + { + result.AddGood(System.IO.Directory.Exists(to) ? "'{0}' already exists.".FormatWith(to) : Name); + } + } + catch (Exception err) + { + result.AddError("Failed to access '{0}' as user '{1}'".FormatWith(toParent, _userName), err); + } + //TODO figure out a good verify step... + return result; + } + + private string GetRootShare(string to) + { + var regex = new Regex(@"\\\\[^\\]*\\[^\\]*"); + var match = regex.Match(to); + if (!match.Success) + { + throw new Exception("Unable to parse root share from " + to); + } + return match.Value; + } + + public DeploymentResult Execute() + { + var result = new DeploymentResult(); + var to = new DotNetPath().GetFullPath(_to); + + var toParent = GetRootShare(to); + try + { + using (var context = FileShareAuthenticator.BeginFileShareAuthentication(toParent, _userName, _password)) + { + result.AddGood("'{0}' authenticated with {1}.".FormatWith(to, _userName)); + } + } + catch (Exception err) + { + result.AddError("Failed to access '{0}' as user '{1}'".FormatWith(toParent, _userName), err); + } + return result; + } + } +} diff --git a/product/dropkick/Tasks/WinService/BaseServiceTask.cs b/product/dropkick/Tasks/WinService/BaseServiceTask.cs index bc687fb9..3ced947c 100644 --- a/product/dropkick/Tasks/WinService/BaseServiceTask.cs +++ b/product/dropkick/Tasks/WinService/BaseServiceTask.cs @@ -28,15 +28,64 @@ protected BaseServiceTask(string machineName, string serviceName) public string MachineName { get; set; } public string ServiceName { get; set; } + protected bool ServiceIsRunning() + { + try + { + if (!dropkick.Wmi.WmiService.AuthenticationSpecified) + { + using (var c = new ServiceController(ServiceName, MachineName)) + { + return (c.Status == ServiceControllerStatus.Running); + } + } + else + { + var status = dropkick.Wmi.WmiService.QueryService(MachineName, ServiceName); + switch (status) + { + case Wmi.ServiceReturnCode.ServiceAlreadyRunning: + case Wmi.ServiceReturnCode.Success: + return true; + default: + return false; + } + } + } + catch (Exception) + { + return false; + } + } protected bool ServiceExists() { try { - using (var c = new ServiceController(ServiceName, MachineName)) + if(!dropkick.Wmi.WmiService.AuthenticationSpecified) + { + using (var c = new ServiceController(ServiceName, MachineName)) + { + ServiceControllerStatus currentStatus = c.Status; + return true; + } + } + else { - ServiceControllerStatus currentStatus = c.Status; - return true; + var status = dropkick.Wmi.WmiService.QueryService(MachineName, ServiceName); + switch(status) + { + case Wmi.ServiceReturnCode.DependentServicesRunning: + case Wmi.ServiceReturnCode.ServiceAlreadyPaused: + case Wmi.ServiceReturnCode.ServiceAlreadyRunning: + case Wmi.ServiceReturnCode.StatusServiceExists: + case Wmi.ServiceReturnCode.Success: + case Wmi.ServiceReturnCode.ServiceNotActive: + case Wmi.ServiceReturnCode.InvalidServiceControl: + return true; + default: + return false; + } } } catch (Exception) diff --git a/product/dropkick/Tasks/WinService/WinServiceCreateTask.cs b/product/dropkick/Tasks/WinService/WinServiceCreateTask.cs index ceedb5d2..98770e5e 100644 --- a/product/dropkick/Tasks/WinService/WinServiceCreateTask.cs +++ b/product/dropkick/Tasks/WinService/WinServiceCreateTask.cs @@ -65,7 +65,11 @@ public override DeploymentResult Execute() if (shouldPromptForPassword()) Password = _prompt.Prompt("Win Service '{0}' For User '{1}' Password".FormatWith(ServiceName, UserName)); - ServiceReturnCode returnCode = WmiService.Create(MachineName, ServiceName, ServiceDisplayName, ServiceLocation, + string displayName = ServiceDisplayName; + if(string.IsNullOrEmpty(displayName)) + displayName = ServiceName; + + ServiceReturnCode returnCode = WmiService.Create(MachineName, ServiceName, displayName, ServiceLocation, StartMode, UserName, Password, Dependencies); if (returnCode != ServiceReturnCode.Success) diff --git a/product/dropkick/Tasks/WinService/WinServiceDeleteTask.cs b/product/dropkick/Tasks/WinService/WinServiceDeleteTask.cs index 32c34f4f..00ea78dd 100644 --- a/product/dropkick/Tasks/WinService/WinServiceDeleteTask.cs +++ b/product/dropkick/Tasks/WinService/WinServiceDeleteTask.cs @@ -30,16 +30,29 @@ public override string Name public override DeploymentResult VerifyCanRun() { var result = new DeploymentResult(); - + if(!ServiceExists()) + { + result.AddNote("Cannot delete service '{0}', service does not exist".FormatWith(ServiceName)); + } return result; } public override DeploymentResult Execute() { - var result = new DeploymentResult(); - - ServiceReturnCode returnCode = WmiService.Delete(MachineName, ServiceName); - + var result = new DeploymentResult(); + + if (!ServiceExists()) + { + result.AddNote("Cannot delete service '{0}', service does not exist".FormatWith(ServiceName)); + } + else + { + ServiceReturnCode returnCode = WmiService.Delete(MachineName, ServiceName); + if(returnCode != ServiceReturnCode.Success) + { + result.AddAlert("Deleting service '{0}' failed: '{1}'".FormatWith(ServiceName, returnCode)); + } + } return result; } } diff --git a/product/dropkick/Tasks/WinService/WinServiceStartTask.cs b/product/dropkick/Tasks/WinService/WinServiceStartTask.cs index d580e463..7fb22bde 100644 --- a/product/dropkick/Tasks/WinService/WinServiceStartTask.cs +++ b/product/dropkick/Tasks/WinService/WinServiceStartTask.cs @@ -31,7 +31,7 @@ public override DeploymentResult VerifyCanRun() } else { - result.AddAlert(string.Format("Can't find service '{0}'", ServiceName)); + result.AddError(string.Format("Can't find service '{0}'", ServiceName)); } return result; @@ -43,26 +43,68 @@ public override DeploymentResult Execute() if (ServiceExists()) { - using (var c = new ServiceController(ServiceName, MachineName)) + if(!dropkick.Wmi.WmiService.AuthenticationSpecified) { - Logging.Coarse("[svc] Starting service '{0}'", ServiceName); - try + using (var c = new ServiceController(ServiceName, MachineName)) { - c.Start(); - LogCoarseGrain("[svc] Waiting up to 60 seconds because Windows can be silly"); - c.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(60)); + Logging.Coarse("[svc] Starting service '{0}'", ServiceName); + try + { + c.Start(); + LogCoarseGrain("[svc] Waiting up to 60 seconds because Windows can be silly"); + c.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(60)); + } + catch (InvalidOperationException ex) + { + result.AddError("The service '{0}' did not start, most likely due to a logon issue.".FormatWith(ServiceName), ex); + LogCoarseGrain("The service '{0}' did not start, most likely due to a logon issue.{1}{2}", ServiceName, Environment.NewLine, ex); + return result; + } + catch (TimeoutException) + { + result.AddAlert("Service '{0}' did not finish starting during the specified timeframe. You will need to manually verify if the service started successfully.", ServiceName); + LogCoarseGrain("Service '{0}' did not finish starting during the specified timeframe. You will need to manually verify if the service started successfully.", ServiceName); + return result; + } } - catch (InvalidOperationException ex) + } + else + { + Logging.Coarse("[svc] Starting service '{0}'", ServiceName); + if (ServiceIsRunning()) { - result.AddError("The service '{0}' did not start, most likely due to a logon issue.".FormatWith(ServiceName), ex); - LogCoarseGrain("The service '{0}' did not start, most likely due to a logon issue.{1}{2}", ServiceName, Environment.NewLine, ex); - return result; + LogCoarseGrain("[svc] Service '{0}' is already running".FormatWith(ServiceName)); } - catch (TimeoutException) + else { - result.AddAlert("Service '{0}' did not finish starting during the specified timeframe. You will need to manually verify if the service started successfully.", ServiceName); - LogCoarseGrain("Service '{0}' did not finish starting during the specified timeframe. You will need to manually verify if the service started successfully.", ServiceName); - return result; + try + { + var returnCode = dropkick.Wmi.WmiService.Start(MachineName, ServiceName); + switch(returnCode) + { + case Wmi.ServiceReturnCode.Success: + if(!ServiceIsRunning()) + { + result.AddError("Failed to start service '{0}', most likely due to a logon issue.".FormatWith(ServiceName)); + LogCoarseGrain("The service '{0}' did not start, most likely due to a logon issue.", ServiceName); + break; + } + else + { + //good! + } + break; + default: + result.AddError("Failed to start service '{0}', most likely due to a logon issue, result: {1}".FormatWith(ServiceName, returnCode)); + LogCoarseGrain("The service '{0}' did not start, most likely due to a logon issue.", ServiceName); + break; + } + } + catch(Exception ex) + { + result.AddError("The service '{0}' did not start, most likely due to a logon issue.".FormatWith(ServiceName), ex); + LogCoarseGrain("The service '{0}' did not start, most likely due to a logon issue.{1}{2}", ServiceName, Environment.NewLine, ex); + } } } result.AddGood("Started the service '{0}'", ServiceName); diff --git a/product/dropkick/Tasks/WinService/WinServiceStopTask.cs b/product/dropkick/Tasks/WinService/WinServiceStopTask.cs index c6e43db2..99f0b8b9 100644 --- a/product/dropkick/Tasks/WinService/WinServiceStopTask.cs +++ b/product/dropkick/Tasks/WinService/WinServiceStopTask.cs @@ -57,21 +57,49 @@ public override DeploymentResult Execute() if (ServiceExists()) { - using (var c = new ServiceController(ServiceName, MachineName)) + if(!dropkick.Wmi.WmiService.AuthenticationSpecified) { - Logging.Coarse("[svc] Stopping service '{0}'", ServiceName); - if (c.CanStop) + using (var c = new ServiceController(ServiceName, MachineName)) { - int pid = GetProcessId(ServiceName); + Logging.Coarse("[svc] Stopping service '{0}'", ServiceName); + if (c.CanStop) + { + int pid = GetProcessId(ServiceName); - c.Stop(); - c.WaitForStatus(ServiceControllerStatus.Stopped, 30.Seconds()); + c.Stop(); + c.WaitForStatus(ServiceControllerStatus.Stopped, 30.Seconds()); - WaitForProcessToDie(pid); + WaitForProcessToDie(pid); + } } + result.AddGood("Stopped Service '{0}'", ServiceName); + Logging.Coarse("[svc] Stopped service '{0}'", ServiceName); + } + else + { + if(!ServiceIsRunning()) + { + result.AddGood("Stopping Service '{0}', Service Already Stopped", ServiceName); + Logging.Coarse("[svc] Stopping service '{0}', service already stopped", ServiceName); + } + else + { + var status = dropkick.Wmi.WmiService.Stop(MachineName, ServiceName); + switch (status) + { + case Wmi.ServiceReturnCode.StatusServiceExists: + case Wmi.ServiceReturnCode.Success: + result.AddGood("Stopped Service '{0}'", ServiceName); + Logging.Coarse("[svc] Stopped service '{0}'", ServiceName); + break; + default: + //BAD + throw new Exception("Failed to stop service {0}: {1}".FormatWith(ServiceName, status)); + } + } + result.AddGood("Stopped Service '{0}'", ServiceName); + Logging.Coarse("[svc] Stopped service '{0}'", ServiceName); } - result.AddGood("Stopped Service '{0}'", ServiceName); - Logging.Coarse("[svc] Stopped service '{0}'", ServiceName); } else { diff --git a/product/dropkick/Wmi/WmiHelper.cs b/product/dropkick/Wmi/WmiHelper.cs index 578a0dad..a72724cd 100644 --- a/product/dropkick/Wmi/WmiHelper.cs +++ b/product/dropkick/Wmi/WmiHelper.cs @@ -5,6 +5,17 @@ namespace dropkick.Wmi public class WmiHelper { + [ThreadStatic] + private static string _wmiUserName; + + [ThreadStatic] + private static string _wmiPassword; + + public static bool AuthenticationSpecified + { + get { return !string.IsNullOrEmpty(_wmiUserName) || !string.IsNullOrEmpty(_wmiPassword); } + } + public static ManagementScope Connect(string machineName) { var scope = new ManagementScope(@"\\{0}\root\cimv2".FormatWith(machineName)) @@ -12,17 +23,26 @@ public static ManagementScope Connect(string machineName) Options = { Impersonation = ImpersonationLevel.Impersonate, - EnablePrivileges = true + EnablePrivileges = true, + Username = _wmiUserName, + Password = _wmiPassword } }; - + try { scope.Connect(); } catch (Exception exc) { - throw new SystemException("Problem connecting WMI scope on " + machineName + ".", exc); + if(string.IsNullOrEmpty(_wmiUserName)) + { + throw new SystemException("Problem connecting WMI scope on " + machineName + " with current user account.", exc); + } + else + { + throw new SystemException("Problem connecting WMI scope on " + machineName + " with user account " + _wmiUserName, exc); + } } return scope; @@ -100,5 +120,11 @@ public static int InvokeStaticMethod(string machineName, string className, strin return -1; } } + + public static void WithAuthentication(string remoteUserName, string remotePassword) + { + _wmiUserName = remoteUserName; + _wmiPassword = remotePassword; + } } } \ No newline at end of file diff --git a/product/dropkick/Wmi/WmiService.cs b/product/dropkick/Wmi/WmiService.cs index be8eb212..89accf64 100644 --- a/product/dropkick/Wmi/WmiService.cs +++ b/product/dropkick/Wmi/WmiService.cs @@ -8,7 +8,12 @@ namespace dropkick.Wmi public class WmiService { const string CLASSNAME = "Win32_Service"; - //private char NULL_VALUE = char(0); + //private char NULL_VALUE = char(0); + + public static bool AuthenticationSpecified + { + get { return WmiHelper.AuthenticationSpecified; } + } public static ServiceReturnCode Create(string machineName, string serviceName, string serviceDisplayName, string serviceLocation, ServiceStartMode startMode, string userName, @@ -129,6 +134,11 @@ public static ServiceReturnCode Resume(string machineName, string serviceName) { return ServiceReturnCode.UnknownFailure; } - } + } + + public static void WithAuthentication(string remoteUserName, string remotePassword) + { + WmiHelper.WithAuthentication(remoteUserName, remotePassword); + } } } \ No newline at end of file diff --git a/product/dropkick/dropkick.csproj b/product/dropkick/dropkick.csproj index e3305793..e980502f 100644 --- a/product/dropkick/dropkick.csproj +++ b/product/dropkick/dropkick.csproj @@ -91,9 +91,11 @@ + + @@ -139,6 +141,7 @@ + @@ -162,6 +165,7 @@ + @@ -350,6 +354,7 @@ +