From ea4e2d27987ccc3cc5cea0d26c598abcfc2001a0 Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Sun, 24 Sep 2017 00:29:22 +0200 Subject: [PATCH 01/19] [Feature]Added BinPath, Description, UserName and Delayed Auto Start information to Get-Service command --- .../commands/management/Service.cs | 246 +++++++++++++++++- .../resources/ServiceResources.resx | 3 + .../utils/PInvokeDllNames.cs | 2 + .../Set-Service.Tests.ps1 | 79 ++++++ 4 files changed, 329 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 568d1fe44ad..e7880667af3 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -666,7 +666,7 @@ protected override void ProcessRecord() { if (!DependentServices.IsPresent && !RequiredServices.IsPresent) { - WriteObject(service); + WriteObject(AddProperties(service)); } else { @@ -689,6 +689,207 @@ protected override void ProcessRecord() } #endregion Overrides + + /// + /// Adds UserName, Description, BinPath and Delay Autostart info to the ServiceController object. + /// + /// + /// + private PSObject AddProperties(ServiceController service) { + NakedWin32Handle hScManager = IntPtr.Zero; + NakedWin32Handle hService = IntPtr.Zero; + int lastError = 0; + PSObject serviceAsPSObj = PSObject.AsPSObject(service); + try + { + hScManager = NativeMethods.OpenSCManagerW( + lpMachineName: service.MachineName, + lpDatabaseName: null, + dwDesiredAccess: NativeMethods.SC_MANAGER_CONNECT + ); + if (IntPtr.Zero == hScManager) { + lastError = Marshal.GetLastWin32Error(); + Win32Exception exception = new Win32Exception(lastError); + WriteNonTerminatingError( + service, + service.MachineName, + exception, + "ComputerAccessDenied", + ServiceResources.ComputerAccessDenied, + ErrorCategory.PermissionDenied); + } + hService = NativeMethods.OpenServiceW( + hScManager, + service.ServiceName, + NativeMethods.SERVICE_QUERY_CONFIG + ); + if (IntPtr.Zero == hService) { + lastError = Marshal.GetLastWin32Error(); + Win32Exception exception = new Win32Exception(lastError); + WriteNonTerminatingError( + service, + exception, + "CouldNotGetServiceInfo", + ServiceResources.CouldNotGetServiceInfo, + ErrorCategory.PermissionDenied); + } + + // Description information + IntPtr lpBuffer = IntPtr.Zero; + DWORD bufferSizeNeeded = 0; + bool status = NativeMethods.QueryServiceConfig2W( + hService: hService, + dwInfoLevel: NativeMethods.SERVICE_CONFIG_DESCRIPTION, + lpBuffer: lpBuffer, + cbBufSize: 0, + pcbBytesNeeded: out bufferSizeNeeded); + + lastError = Marshal.GetLastWin32Error(); + if(lastError != NativeMethods.ERROR_INSUFFICIENT_BUFFER) { + Win32Exception exception = new Win32Exception(lastError); + WriteNonTerminatingError( + service, + exception, + "CouldNotGetServiceInfo", + ServiceResources.CouldNotGetServiceInfo, + ErrorCategory.PermissionDenied); + } + lpBuffer = Marshal.AllocHGlobal((int)bufferSizeNeeded); + + status = NativeMethods.QueryServiceConfig2W( + hService, + NativeMethods.SERVICE_CONFIG_DESCRIPTION, + lpBuffer, + bufferSizeNeeded, + out bufferSizeNeeded); + + if(!status) { + lastError = Marshal.GetLastWin32Error(); + Win32Exception exception = new Win32Exception(lastError); + WriteNonTerminatingError( + service, + exception, + "CouldNotGetServiceInfo", + ServiceResources.CouldNotGetServiceInfo, + ErrorCategory.PermissionDenied); + } + NativeMethods.SERVICE_DESCRIPTIONW description = (NativeMethods.SERVICE_DESCRIPTIONW)Marshal.PtrToStructure(lpBuffer, typeof(NativeMethods.SERVICE_DESCRIPTIONW)); + + // Delayed auto start information + lpBuffer = IntPtr.Zero; + bufferSizeNeeded = 0; + status = NativeMethods.QueryServiceConfig2W( + hService: hService, + dwInfoLevel: NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, + lpBuffer: lpBuffer, + cbBufSize: 0, + pcbBytesNeeded: out bufferSizeNeeded); + + lastError = Marshal.GetLastWin32Error(); + if(lastError != NativeMethods.ERROR_INSUFFICIENT_BUFFER) { + Win32Exception exception = new Win32Exception(lastError); + WriteNonTerminatingError( + service, + exception, + "CouldNotGetServiceInfo", + ServiceResources.CouldNotGetServiceInfo, + ErrorCategory.PermissionDenied); + + } + lpBuffer = Marshal.AllocHGlobal((int)bufferSizeNeeded); + + status = NativeMethods.QueryServiceConfig2W( + hService, + NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, + lpBuffer, + bufferSizeNeeded, + out bufferSizeNeeded); + if(!status) { + lastError = Marshal.GetLastWin32Error(); + Win32Exception exception = new Win32Exception(lastError); + WriteNonTerminatingError( + service, + exception, + "CouldNotGetServiceInfo", + ServiceResources.CouldNotGetServiceInfo, + ErrorCategory.PermissionDenied); + } + NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo = (NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)Marshal.PtrToStructure(lpBuffer, typeof(NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)); + + // General information + lpBuffer = IntPtr.Zero; + bufferSizeNeeded = 0; + status = NativeMethods.QueryServiceConfigW( + hSCManager: hService, + lpServiceConfig: lpBuffer, + cbBufSize: 0, + pcbBytesNeeded: out bufferSizeNeeded); + + lastError = Marshal.GetLastWin32Error(); + if(lastError != NativeMethods.ERROR_INSUFFICIENT_BUFFER) { + Win32Exception exception = new Win32Exception(lastError); + WriteNonTerminatingError( + service, + exception, + "CouldNotGetServiceInfo", + ServiceResources.CouldNotGetServiceInfo, + ErrorCategory.PermissionDenied); + + } + lpBuffer = Marshal.AllocHGlobal((int)bufferSizeNeeded); + + status = NativeMethods.QueryServiceConfigW( + hService, + lpBuffer, + bufferSizeNeeded, + out bufferSizeNeeded); + if(!status) { + lastError = Marshal.GetLastWin32Error(); + Win32Exception exception = new Win32Exception(lastError); + WriteNonTerminatingError( + service, + exception, + "CouldNotGetServiceInfo", + ServiceResources.CouldNotGetServiceInfo, + ErrorCategory.PermissionDenied); + } + + NativeMethods.QUERY_SERVICE_CONFIG serviceInfo = (NativeMethods.QUERY_SERVICE_CONFIG)Marshal.PtrToStructure(lpBuffer, typeof(NativeMethods.QUERY_SERVICE_CONFIG)); + + PSProperty noteProperty = new PSProperty("UserName", serviceInfo.lpServiceStartName); + serviceAsPSObj.Properties.Add(noteProperty, true); + serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#UserName"); + + noteProperty = new PSProperty("Description", description.lpDescription); + serviceAsPSObj.Properties.Add(noteProperty, true); + serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#Description"); + + noteProperty = new PSProperty("DelayedAutoStart", autostartInfo.fDelayedAutostart); + serviceAsPSObj.Properties.Add(noteProperty, true); + serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#DelayedAutoStart"); + + noteProperty = new PSProperty("BinPath", serviceInfo.lpBinaryPathName); + serviceAsPSObj.Properties.Add(noteProperty, true); + serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#BinPath"); + } + finally + { + if (IntPtr.Zero != hService) { + bool succeeded = NativeMethods.CloseServiceHandle(hService); + if (!succeeded) { + Diagnostics.Assert(lastError != 0, "ErrorCode not success"); + } + } + + if (IntPtr.Zero != hScManager) { + bool succeeded = NativeMethods.CloseServiceHandle(hScManager); + if (!succeeded) { + Diagnostics.Assert(lastError != 0, "ErrorCode not success"); + } + } + } // finally + return serviceAsPSObj; + } } #endregion GetServiceCommand @@ -2451,6 +2652,7 @@ internal static class NativeMethods // from winuser.h internal const int ERROR_SERVICE_ALREADY_RUNNING = 1056; internal const int ERROR_SERVICE_NOT_ACTIVE = 1062; + internal const int ERROR_INSUFFICIENT_BUFFER = 122; internal const DWORD SC_MANAGER_CONNECT = 1; internal const DWORD SC_MANAGER_CREATE_SERVICE = 2; internal const DWORD SC_MANAGER_ALL_ACCESS = 0xf003f; @@ -2462,6 +2664,9 @@ internal static class NativeMethods internal const DWORD SERVICE_DEMAND_START = 0x3; internal const DWORD SERVICE_DISABLED = 0x4; internal const DWORD SERVICE_CONFIG_DESCRIPTION = 1; + internal const DWORD SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3; + internal const DWORD SERVICE_CONFIG_SERVICE_SID_INFO = 5; + internal const DWORD SERVICE_WIN32_OWN_PROCESS = 0x10; internal const DWORD SERVICE_ERROR_NORMAL = 1; @@ -2482,6 +2687,25 @@ NakedWin32Handle OpenServiceW( DWORD dwDesiredAccess ); + [DllImport(PinvokeDllNames.QueryServiceConfigDllName, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern + bool QueryServiceConfigW( + NakedWin32Handle hSCManager, + IntPtr lpServiceConfig, + DWORD cbBufSize, + out DWORD pcbBytesNeeded + ); + + [DllImport(PinvokeDllNames.QueryServiceConfig2DllName, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern + bool QueryServiceConfig2W( + NakedWin32Handle hService, + DWORD dwInfoLevel, + IntPtr lpBuffer, + DWORD cbBufSize, + out DWORD pcbBytesNeeded + ); + [DllImport(PinvokeDllNames.CloseServiceHandleDllName, CharSet = CharSet.Unicode, SetLastError = true)] internal static extern bool CloseServiceHandle( @@ -2525,6 +2749,26 @@ internal struct SERVICE_DESCRIPTIONW internal string lpDescription; }; + [StructLayout(LayoutKind.Sequential)] + internal struct QUERY_SERVICE_CONFIG + { + internal uint dwServiceType; + internal uint dwStartType; + internal uint dwErrorControl; + [MarshalAs(UnmanagedType.LPWStr)] internal string lpBinaryPathName; + [MarshalAs(UnmanagedType.LPWStr)] internal string lpLoadOrderGroup; + internal uint dwTagId; + [MarshalAs(UnmanagedType.LPWStr)] internal string lpDependencies; + [MarshalAs(UnmanagedType.LPWStr)] internal string lpServiceStartName; + [MarshalAs(UnmanagedType.LPWStr)] internal string lpDisplayName; + }; + + [StructLayout(LayoutKind.Sequential)] + internal struct SERVICE_DELAYED_AUTO_START_INFO + { + internal bool fDelayedAutostart; + }; + [DllImport(PinvokeDllNames.CreateServiceWDllName, CharSet = CharSet.Unicode, SetLastError = true)] internal static extern NakedWin32Handle CreateServiceW( diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx index 8c94d5bb35a..f0d58c2c976 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx @@ -162,6 +162,9 @@ Service '{1} ({0})' cannot be configured due to the following error: {2} + + Service '{1} ({0})' cannot be queried due to the following error: {2} + Service '{1} ({0})' description cannot be configured due to the following error: {2} diff --git a/src/System.Management.Automation/utils/PInvokeDllNames.cs b/src/System.Management.Automation/utils/PInvokeDllNames.cs index 619349b888f..1841115f560 100644 --- a/src/System.Management.Automation/utils/PInvokeDllNames.cs +++ b/src/System.Management.Automation/utils/PInvokeDllNames.cs @@ -136,5 +136,7 @@ internal static class PinvokeDllNames internal const string Process32NextDllName = "api-ms-win-core-toolhelp-l1-1-0"; /*122*/ internal const string GetACPDllName = "api-ms-win-core-localization-l1-2-0.dll"; /*123*/ internal const string DeleteServiceDllName = "api-ms-win-service-management-l1-1-0.dll"; /*124*/ + internal const string QueryServiceConfigDllName = "api-ms-win-service-management-l2-1-0.dll"; /*125*/ + internal const string QueryServiceConfig2DllName = "api-ms-win-service-management-l2-1-0.dll"; /*126*/ } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 index cf78e8ba69a..7e0ec19855d 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 @@ -235,6 +235,85 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW { Remove-Service -Name "testremoveservice" -ErrorAction 'Stop' } | ShouldBeErrorId "InvalidOperationException,Microsoft.PowerShell.Commands.RemoveServiceCommand" } + It "Get-Service can get the username of a service" { + try { + $userName = "user1" + $testPass = "Secret123!" + $servicename = "testgetusername" + net user $userName $testPass /add > $null + $password = ConvertTo-SecureString $testPass -AsPlainText -Force + $creds = [pscredential]::new(".\$userName", $password) + $parameters = @{ + Name = $servicename; + BinaryPathName = "$PSHOME\powershell.exe"; + Credential = $creds + } + $service = New-Service @parameters + $service | Should Not BeNullOrEmpty + $service = Get-Service -Name $servicename + $service.UserName | Should BeExactly $creds.UserName + } + finally { + Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue + net user $userName /delete > $null + } + } + + It "Get-Service can get the BinPath of a service" { + try { + $servicename = "testbinpathservice" + $binpath = "$PSHOME\powershell.exe" + $parameters = @{ + Name = $servicename + BinaryPathName = $binpath + } + $service = New-Service @parameters + $service | Should Not BeNullOrEmpty + $service = Get-Service -Name $servicename + $service.BinPath | Should BeExactly $binpath + } + finally { + Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue + } + } + + It "Get-Service can get the description of a service" { + try { + $servicename = "testdescriptionservice" + $description = "This is a test description" + $parameters = @{ + Name = $servicename + Description = $description + BinaryPathName = "$PSHOME\powershell.exe" + } + $service = New-Service @parameters + $service | Should Not BeNullOrEmpty + $service = Get-Service -Name $servicename + $service.Description | Should BeExactly $description + } + finally { + Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue + } + } + + It "Get-Service can get the delayed autostart property of a service" { + try { + $servicename = "testgetdelayautostartservice" + $binpath = "$PSHOME\powershell.exe" + $parameters = @{ + Name = $servicename + BinaryPathName = $binpath + } + $service = New-Service @parameters + $service | Should Not BeNullOrEmpty + $service = Get-Service -Name $servicename + $service.DelayedAutoStart | Should BeExactly $false + } + finally { + Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue + } + } + It "Set-Service can accept pipeline input of a ServiceController" { try { $servicename = "testsetservice" From d3f64b196686e247b2069393049e3159ef9f5e88 Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Sun, 24 Sep 2017 20:38:17 +0200 Subject: [PATCH 02/19] [Feature]StartupType added with support for automatic delayed start of services --- .../commands/management/Service.cs | 298 ++++++++++-------- .../Set-Service.Tests.ps1 | 106 ++----- 2 files changed, 187 insertions(+), 217 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index e7880667af3..fb5e380a3f5 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -733,129 +733,27 @@ private PSObject AddProperties(ServiceController service) { ServiceResources.CouldNotGetServiceInfo, ErrorCategory.PermissionDenied); } + IntPtr structurePointer = IntPtr.Zero; - // Description information - IntPtr lpBuffer = IntPtr.Zero; - DWORD bufferSizeNeeded = 0; - bool status = NativeMethods.QueryServiceConfig2W( - hService: hService, - dwInfoLevel: NativeMethods.SERVICE_CONFIG_DESCRIPTION, - lpBuffer: lpBuffer, - cbBufSize: 0, - pcbBytesNeeded: out bufferSizeNeeded); - - lastError = Marshal.GetLastWin32Error(); - if(lastError != NativeMethods.ERROR_INSUFFICIENT_BUFFER) { - Win32Exception exception = new Win32Exception(lastError); - WriteNonTerminatingError( - service, - exception, - "CouldNotGetServiceInfo", - ServiceResources.CouldNotGetServiceInfo, - ErrorCategory.PermissionDenied); - } - lpBuffer = Marshal.AllocHGlobal((int)bufferSizeNeeded); - - status = NativeMethods.QueryServiceConfig2W( - hService, - NativeMethods.SERVICE_CONFIG_DESCRIPTION, - lpBuffer, - bufferSizeNeeded, - out bufferSizeNeeded); - - if(!status) { - lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); - WriteNonTerminatingError( - service, - exception, - "CouldNotGetServiceInfo", - ServiceResources.CouldNotGetServiceInfo, - ErrorCategory.PermissionDenied); - } - NativeMethods.SERVICE_DESCRIPTIONW description = (NativeMethods.SERVICE_DESCRIPTIONW)Marshal.PtrToStructure(lpBuffer, typeof(NativeMethods.SERVICE_DESCRIPTIONW)); - - // Delayed auto start information - lpBuffer = IntPtr.Zero; - bufferSizeNeeded = 0; - status = NativeMethods.QueryServiceConfig2W( - hService: hService, - dwInfoLevel: NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, - lpBuffer: lpBuffer, - cbBufSize: 0, - pcbBytesNeeded: out bufferSizeNeeded); - - lastError = Marshal.GetLastWin32Error(); - if(lastError != NativeMethods.ERROR_INSUFFICIENT_BUFFER) { - Win32Exception exception = new Win32Exception(lastError); - WriteNonTerminatingError( - service, - exception, - "CouldNotGetServiceInfo", - ServiceResources.CouldNotGetServiceInfo, - ErrorCategory.PermissionDenied); - - } - lpBuffer = Marshal.AllocHGlobal((int)bufferSizeNeeded); + bool queryStatus = NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DESCRIPTION, out structurePointer); + NativeMethods.SERVICE_DESCRIPTIONW description = (NativeMethods.SERVICE_DESCRIPTIONW)Marshal.PtrToStructure(structurePointer, typeof(NativeMethods.SERVICE_DESCRIPTIONW)); - status = NativeMethods.QueryServiceConfig2W( - hService, - NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, - lpBuffer, - bufferSizeNeeded, - out bufferSizeNeeded); - if(!status) { - lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); - WriteNonTerminatingError( - service, - exception, - "CouldNotGetServiceInfo", - ServiceResources.CouldNotGetServiceInfo, - ErrorCategory.PermissionDenied); - } - NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo = (NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)Marshal.PtrToStructure(lpBuffer, typeof(NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)); - - // General information - lpBuffer = IntPtr.Zero; - bufferSizeNeeded = 0; - status = NativeMethods.QueryServiceConfigW( - hSCManager: hService, - lpServiceConfig: lpBuffer, - cbBufSize: 0, - pcbBytesNeeded: out bufferSizeNeeded); - - lastError = Marshal.GetLastWin32Error(); - if(lastError != NativeMethods.ERROR_INSUFFICIENT_BUFFER) { - Win32Exception exception = new Win32Exception(lastError); - WriteNonTerminatingError( - service, - exception, - "CouldNotGetServiceInfo", - ServiceResources.CouldNotGetServiceInfo, - ErrorCategory.PermissionDenied); + queryStatus = queryStatus & NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, out structurePointer); + NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo = (NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)Marshal.PtrToStructure(structurePointer, typeof(NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)); - } - lpBuffer = Marshal.AllocHGlobal((int)bufferSizeNeeded); + queryStatus = queryStatus & NativeMethods.QueryServiceConfig(hService, out structurePointer); + NativeMethods.QUERY_SERVICE_CONFIG serviceInfo = (NativeMethods.QUERY_SERVICE_CONFIG)Marshal.PtrToStructure(structurePointer, typeof(NativeMethods.QUERY_SERVICE_CONFIG)); - status = NativeMethods.QueryServiceConfigW( - hService, - lpBuffer, - bufferSizeNeeded, - out bufferSizeNeeded); - if(!status) { - lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + if(!queryStatus) { WriteNonTerminatingError( - service, - exception, - "CouldNotGetServiceInfo", - ServiceResources.CouldNotGetServiceInfo, - ErrorCategory.PermissionDenied); + service: service, + innerException: null, + errorId: "CouldNotGetServiceInfo", + errorMessage: ServiceResources.CouldNotGetServiceInfo, + category: ErrorCategory.PermissionDenied + ); } - NativeMethods.QUERY_SERVICE_CONFIG serviceInfo = (NativeMethods.QUERY_SERVICE_CONFIG)Marshal.PtrToStructure(lpBuffer, typeof(NativeMethods.QUERY_SERVICE_CONFIG)); - PSProperty noteProperty = new PSProperty("UserName", serviceInfo.lpServiceStartName); serviceAsPSObj.Properties.Add(noteProperty, true); serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#UserName"); @@ -871,6 +769,10 @@ private PSObject AddProperties(ServiceController service) { noteProperty = new PSProperty("BinPath", serviceInfo.lpBinaryPathName); serviceAsPSObj.Properties.Add(noteProperty, true); serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#BinPath"); + + noteProperty = new PSProperty("StartupType", NativeMethods.GetServiceStartupType(service.StartType, autostartInfo.fDelayedAutostart)); + serviceAsPSObj.Properties.Add(noteProperty, true); + serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#StartupType"); } finally { @@ -1707,17 +1609,7 @@ public string Description [Parameter] [Alias("StartMode", "SM", "ST")] [ValidateNotNullOrEmpty] - public ServiceStartMode StartupType - { - get { return startupType; } - set - { - startupType = value; - } - } - // We set the initial value to an invalid value so that we can - // distinguish when this is and is not set. - internal ServiceStartMode startupType = (ServiceStartMode)(-1); + public ServiceStartupType StartupType { get; set; } = (ServiceStartupType)(-1); /// @@ -1889,7 +1781,7 @@ protected override void ProcessRecord() // Modify startup type or display name or credential if (!String.IsNullOrEmpty(DisplayName) - || (ServiceStartMode)(-1) != StartupType || null != Credential) + || (ServiceStartupType)(-1) != StartupType || null != Credential) { DWORD dwStartType = NativeMethods.SERVICE_NO_CHANGE; if (!NativeMethods.TryGetNativeStartupType(StartupType, out dwStartType)) @@ -1957,7 +1849,31 @@ protected override void ProcessRecord() ErrorCategory.PermissionDenied); } + // Set the delayed auto start + NativeMethods.SERVICE_DELAYED_AUTO_START_INFO ds = new NativeMethods.SERVICE_DELAYED_AUTO_START_INFO(); + ds.fDelayedAutostart = StartupType == ServiceStartupType.AutomaticDelayedStart; + size = Marshal.SizeOf(ds); + buffer = Marshal.AllocCoTaskMem(size); + Marshal.StructureToPtr(ds, buffer, false); + status = NativeMethods.ChangeServiceConfig2W( + hService, + NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, + buffer); + + if (!status) + { + int lastError = Marshal.GetLastWin32Error(); + Win32Exception exception = new Win32Exception(lastError); + WriteNonTerminatingError( + Name, + DisplayName, + Name, + exception, + "CouldNotNewServiceDescription", + ServiceResources.CouldNotNewServiceDescription, + ErrorCategory.PermissionDenied); + } //Addition by v-ramch Mar 11 2008 //if Status parameter specified do the necessary action @@ -2135,12 +2051,7 @@ public string Description /// /// [Parameter] - public ServiceStartMode StartupType - { - get { return startupType; } - set { startupType = value; } - } - internal ServiceStartMode startupType = ServiceStartMode.Automatic; + public ServiceStartupType StartupType { get; set; } = ServiceStartupType.Automatic; /// /// Account under which the service should run @@ -2313,6 +2224,34 @@ protected override void BeginProcessing() ErrorCategory.PermissionDenied); } + // Set the delayed auto start + if(StartupType == ServiceStartupType.AutomaticDelayedStart) { + NativeMethods.SERVICE_DELAYED_AUTO_START_INFO ds = new NativeMethods.SERVICE_DELAYED_AUTO_START_INFO(); + ds.fDelayedAutostart = true; + size = Marshal.SizeOf(ds); + buffer = Marshal.AllocCoTaskMem(size); + Marshal.StructureToPtr(ds, buffer, false); + + succeeded = NativeMethods.ChangeServiceConfig2W( + hService, + NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, + buffer); + + if (!succeeded) + { + int lastError = Marshal.GetLastWin32Error(); + Win32Exception exception = new Win32Exception(lastError); + WriteNonTerminatingError( + Name, + DisplayName, + Name, + exception, + "CouldNotNewServiceDescription", + ServiceResources.CouldNotNewServiceDescription, + ErrorCategory.PermissionDenied); + } + } + // write the ServiceController for the new service using (ServiceController service = new ServiceController(Name)) // ensure dispose @@ -2850,6 +2789,52 @@ public static extern bool QueryInformationJobObject(SafeHandle hJob, int JobObje ref JOBOBJECT_BASIC_PROCESS_ID_LIST lpJobObjectInfo, int cbJobObjectLength, IntPtr lpReturnLength); + internal static bool QueryServiceConfig(NakedWin32Handle hService, out IntPtr structurePointer) + { + IntPtr lpBuffer = IntPtr.Zero; + DWORD bufferSize, bufferSizeNeeded = 0; + bool status = NativeMethods.QueryServiceConfigW( + hSCManager: hService, + lpServiceConfig: lpBuffer, + cbBufSize: 0, + pcbBytesNeeded: out bufferSizeNeeded); + + lpBuffer = Marshal.AllocHGlobal((int)bufferSizeNeeded); + bufferSize = bufferSizeNeeded; + + status = NativeMethods.QueryServiceConfigW( + hService, + lpBuffer, + bufferSize, + out bufferSizeNeeded); + structurePointer = lpBuffer; + return status; + } + + internal static bool QueryServiceConfig2(NakedWin32Handle hService, DWORD infolevel, out IntPtr structurePointer) + { + IntPtr lpBuffer = IntPtr.Zero; + DWORD bufferSize, bufferSizeNeeded = 0; + bool status = NativeMethods.QueryServiceConfig2W( + hService: hService, + dwInfoLevel: infolevel, + lpBuffer: lpBuffer, + cbBufSize: 0, + pcbBytesNeeded: out bufferSizeNeeded); + + lpBuffer = Marshal.AllocHGlobal((int)bufferSizeNeeded); + bufferSize = bufferSizeNeeded; + + status = NativeMethods.QueryServiceConfig2W( + hService, + infolevel, + lpBuffer, + bufferSize, + out bufferSizeNeeded); + structurePointer = lpBuffer; + return status; + } + /// /// Get appropriate win32 StartupType /// @@ -2862,22 +2847,23 @@ public static extern bool QueryInformationJobObject(SafeHandle hJob, int JobObje /// /// If a supported StartupType is provided, funciton returns true, otherwise false. /// - internal static bool TryGetNativeStartupType(ServiceStartMode StartupType, out DWORD dwStartType) + internal static bool TryGetNativeStartupType(ServiceStartupType StartupType, out DWORD dwStartType) { bool success = true; dwStartType = NativeMethods.SERVICE_NO_CHANGE; switch (StartupType) { - case ServiceStartMode.Automatic: + case ServiceStartupType.Automatic: + case ServiceStartupType.AutomaticDelayedStart: dwStartType = NativeMethods.SERVICE_AUTO_START; break; - case ServiceStartMode.Manual: + case ServiceStartupType.Manual: dwStartType = NativeMethods.SERVICE_DEMAND_START; break; - case ServiceStartMode.Disabled: + case ServiceStartupType.Disabled: dwStartType = NativeMethods.SERVICE_DISABLED; break; - case (ServiceStartMode)(-1): + case (ServiceStartupType)(-1): dwStartType = NativeMethods.SERVICE_NO_CHANGE; break; default: @@ -2886,8 +2872,42 @@ internal static bool TryGetNativeStartupType(ServiceStartMode StartupType, out D } return success; } + + internal static ServiceStartupType GetServiceStartupType(ServiceStartMode startMode, bool delayedAutoStart) + { + ServiceStartupType result = ServiceStartupType.Disabled; + switch(startMode) + { + case ServiceStartMode.Automatic: + result = delayedAutoStart ? ServiceStartupType.AutomaticDelayedStart : ServiceStartupType.Automatic; + break; + case ServiceStartMode.Manual: + result = ServiceStartupType.Manual; + break; + case ServiceStartMode.Disabled: + result = ServiceStartupType.Disabled; + break; + } + return result; + } } #endregion NativeMethods + + #region ServiceStartupType + /// + ///Enum for usage with StartupType. Automatic, Manual & Disabled index matched from System.ServiceProcess.ServiceStartMode + /// + public enum ServiceStartupType { + ///Automatic service + Automatic = 2, + ///Manual service + Manual = 3, + ///Disabled service + Disabled = 4, + ///Automatic (Delayed Start) service + AutomaticDelayedStart = 10 + } + #endregion ServiceStartupType } #endif // Not built on Unix diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 index 7e0ec19855d..3773f46186b 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 @@ -4,9 +4,15 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW if ( -not $IsWindows ) { $PSDefaultParameterValues["it:skip"] = $true } + $userName = "testuserservices" + $testPass = "Secret123!" + net user $userName $testPass /add > $null + $password = ConvertTo-SecureString $testPass -AsPlainText -Force + $creds = [pscredential]::new(".\$userName", $password) } AfterAll { $global:PSDefaultParameterValues = $originalDefaultParameterValues + net user $userName /delete > $null } It "SetServiceCommand can be used as API for '' with ''" -TestCases @( @@ -15,10 +21,8 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW @{parameter = "DisplayName" ; value = "hello"}, @{parameter = "Description" ; value = "hello world"}, @{parameter = "StartupType" ; value = "Automatic"}, - @{parameter = "StartupType" ; value = "Boot"}, @{parameter = "StartupType" ; value = "Disabled"}, @{parameter = "StartupType" ; value = "Manual"}, - @{parameter = "StartupType" ; value = "System"}, @{parameter = "Status" ; value = "Running"}, @{parameter = "Status" ; value = "Stopped"}, @{parameter = "Status" ; value = "Paused"}, @@ -97,10 +101,8 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW @{parameter = "DisplayName" ; value = "hello world"}, @{parameter = "Description" ; value = "this is a test"}, @{parameter = "StartupType" ; value = "Automatic"}, - @{parameter = "StartupType" ; value = "Boot"}, @{parameter = "StartupType" ; value = "Disabled"}, @{parameter = "StartupType" ; value = "Manual"}, - @{parameter = "StartupType" ; value = "System"}, @{parameter = "Credential" ; value = ( [System.Management.Automation.PSCredential]::new("username", (ConvertTo-SecureString "PlainTextPassword" -AsPlainText -Force))) @@ -235,82 +237,30 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW { Remove-Service -Name "testremoveservice" -ErrorAction 'Stop' } | ShouldBeErrorId "InvalidOperationException,Microsoft.PowerShell.Commands.RemoveServiceCommand" } - It "Get-Service can get the username of a service" { + It "Get-Service can get the '' of a service" -TestCases @( + @{property = "Description"; value = "This is a test description"} + @{property = "BinPath"; value = "$PSHOME\powershell.exe"; parameters = @{ BinaryPathName = "$PSHOME\powershell.exe" }}, + @{property = "UserName"; value = $creds.UserName; parameters = @{ Credential = $creds }}, + @{property = "StartupType"; value = "AutomaticDelayedStart";} + ) { + param($property, $value, $parameters) try { - $userName = "user1" - $testPass = "Secret123!" - $servicename = "testgetusername" - net user $userName $testPass /add > $null - $password = ConvertTo-SecureString $testPass -AsPlainText -Force - $creds = [pscredential]::new(".\$userName", $password) - $parameters = @{ - Name = $servicename; - BinaryPathName = "$PSHOME\powershell.exe"; - Credential = $creds - } - $service = New-Service @parameters - $service | Should Not BeNullOrEmpty - $service = Get-Service -Name $servicename - $service.UserName | Should BeExactly $creds.UserName - } - finally { - Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue - net user $userName /delete > $null - } - } - - It "Get-Service can get the BinPath of a service" { - try { - $servicename = "testbinpathservice" - $binpath = "$PSHOME\powershell.exe" - $parameters = @{ - Name = $servicename - BinaryPathName = $binpath - } - $service = New-Service @parameters - $service | Should Not BeNullOrEmpty - $service = Get-Service -Name $servicename - $service.BinPath | Should BeExactly $binpath - } - finally { - Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue - } - } - - It "Get-Service can get the description of a service" { - try { - $servicename = "testdescriptionservice" - $description = "This is a test description" - $parameters = @{ - Name = $servicename - Description = $description - BinaryPathName = "$PSHOME\powershell.exe" - } - $service = New-Service @parameters - $service | Should Not BeNullOrEmpty - $service = Get-Service -Name $servicename - $service.Description | Should BeExactly $description - } - finally { - Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue - } - } - - It "Get-Service can get the delayed autostart property of a service" { - try { - $servicename = "testgetdelayautostartservice" - $binpath = "$PSHOME\powershell.exe" - $parameters = @{ - Name = $servicename - BinaryPathName = $binpath + $servicename = "testgetservice" + $startparameters = @{Name = $servicename; BinaryPathName = "$PSHOME\powershell.exe"} + if($parameters -ne $null) { + foreach($key in $parameters.Keys) { + $startparameters.$key = $parameters.$key } - $service = New-Service @parameters - $service | Should Not BeNullOrEmpty - $service = Get-Service -Name $servicename - $service.DelayedAutoStart | Should BeExactly $false + } else { + $startparameters.$property = $value + } + $service = New-Service @startparameters + $service | Should Not BeNullOrEmpty + $service = Get-Service -Name $servicename + $service.$property | Should BeExactly $value } finally { - Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue + Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue } } @@ -339,11 +289,11 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW (ConvertTo-SecureString "PlainTextPassword" -AsPlainText -Force))); errorid = "CouldNotNewService,Microsoft.PowerShell.Commands.NewServiceCommand"}, @{cmdlet="New-Service"; name = 'badstarttype'; parameter = "StartupType"; value = "System"; - errorid = "CouldNotNewService,Microsoft.PowerShell.Commands.NewServiceCommand"}, + errorid = "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.NewServiceCommand"}, @{cmdlet="New-Service"; name = 'winmgmt' ; parameter = "DisplayName"; value = "foo"; errorid = "CouldNotNewService,Microsoft.PowerShell.Commands.NewServiceCommand"}, @{cmdlet="Set-Service"; name = 'winmgmt' ; parameter = "StartupType"; value = "Boot"; - errorid = "CouldNotSetService,Microsoft.PowerShell.Commands.SetServiceCommand"} + errorid = "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.SetServiceCommand"} ) { param($cmdlet, $name, $parameter, $value, $errorid) $parameters = @{$parameter = $value; Name = $name; ErrorAction = "Stop"} From 58b13038da9bf5bc5eb279b49ac924493bd7f979 Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Mon, 25 Sep 2017 17:39:16 +0200 Subject: [PATCH 03/19] [Feature]StartupType reverted back from a public property --- .../commands/management/Service.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index fb5e380a3f5..6f3f0c8f020 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -1609,7 +1609,17 @@ public string Description [Parameter] [Alias("StartMode", "SM", "ST")] [ValidateNotNullOrEmpty] - public ServiceStartupType StartupType { get; set; } = (ServiceStartupType)(-1); + public ServiceStartupType StartupType + { + get { return startupType; } + set + { + startupType = value; + } + } + // We set the initial value to an invalid value so that we can + // distinguish when this is and is not set. + internal ServiceStartupType startupType = (ServiceStartupType)(-1); /// @@ -2051,7 +2061,12 @@ public string Description /// /// [Parameter] - public ServiceStartupType StartupType { get; set; } = ServiceStartupType.Automatic; + public ServiceStartupType StartupType + { + get { return startupType; } + set { startupType = value; } + } + internal ServiceStartupType startupType = ServiceStartupType.Automatic; /// /// Account under which the service should run From fa97af5f1df6d3f56a3be5641bbedccd50c252d9 Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Mon, 25 Sep 2017 18:59:21 +0200 Subject: [PATCH 04/19] [Feature]Corrected error written if delayed auto start boolean could not be set --- .../commands/management/Service.cs | 8 ++++---- .../resources/ServiceResources.resx | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 6f3f0c8f020..2661e38de90 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -1880,8 +1880,8 @@ protected override void ProcessRecord() DisplayName, Name, exception, - "CouldNotNewServiceDescription", - ServiceResources.CouldNotNewServiceDescription, + "CouldNotSetServiceDelayedAutoStart", + ServiceResources.CouldNotSetServiceDelayedAutoStart, ErrorCategory.PermissionDenied); } @@ -2234,8 +2234,8 @@ protected override void BeginProcessing() DisplayName, Name, exception, - "CouldNotNewServiceDescription", - ServiceResources.CouldNotNewServiceDescription, + "CouldNotNewServiceDelayedAutoStart", + ServiceResources.CouldNotNewServiceDelayedAutoStart, ErrorCategory.PermissionDenied); } diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx index f0d58c2c976..24ee65b24ad 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx @@ -168,12 +168,18 @@ Service '{1} ({0})' description cannot be configured due to the following error: {2} + + Service '{1} ({0})' automatic (delayed start) cannot be configured due to the following error: {2} + Service '{1} ({0})' cannot be created due to the following error: {2} Service '{1} ({0})' was created, but its description cannot be configured due to the following error: {2} + + Service '{1} ({0})' was created, but its startuptype, automatic (delayed start), cannot be configured due to the following error: {2} + Service '{1} ({0})' cannot be removed due to the following error: {2} From 29ce3836db173f15067fcfd963f01a669fa324d2 Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Mon, 25 Sep 2017 19:22:12 +0200 Subject: [PATCH 05/19] [Feature]Corrected the exception message to be thrown where it was actually being called. --- .../commands/management/Service.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 2661e38de90..6ff68d23282 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -2234,8 +2234,8 @@ protected override void BeginProcessing() DisplayName, Name, exception, - "CouldNotNewServiceDelayedAutoStart", - ServiceResources.CouldNotNewServiceDelayedAutoStart, + "CouldNotNewServiceDescription", + ServiceResources.CouldNotNewServiceDescription, ErrorCategory.PermissionDenied); } @@ -2261,8 +2261,8 @@ protected override void BeginProcessing() DisplayName, Name, exception, - "CouldNotNewServiceDescription", - ServiceResources.CouldNotNewServiceDescription, + "CouldNotNewServiceDelayedAutoStart", + ServiceResources.CouldNotNewServiceDelayedAutoStart, ErrorCategory.PermissionDenied); } } From b758abef25790b91f89038769a6c59dfe8a975e6 Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Mon, 25 Sep 2017 20:32:21 +0200 Subject: [PATCH 06/19] [Feature]Correct tests for linux and osx. --- .../Set-Service.Tests.ps1 | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 index 3773f46186b..edd0f941e2f 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 @@ -4,15 +4,19 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW if ( -not $IsWindows ) { $PSDefaultParameterValues["it:skip"] = $true } - $userName = "testuserservices" - $testPass = "Secret123!" - net user $userName $testPass /add > $null - $password = ConvertTo-SecureString $testPass -AsPlainText -Force - $creds = [pscredential]::new(".\$userName", $password) + if($IsWindows) { + $userName = "testuserservices" + $testPass = "Secret123!" + net user $userName $testPass /add > $null + $password = ConvertTo-SecureString $testPass -AsPlainText -Force + $creds = [pscredential]::new(".\$userName", $password) + } } AfterAll { $global:PSDefaultParameterValues = $originalDefaultParameterValues - net user $userName /delete > $null + if($IsWindows) { + net user $userName /delete > $null + } } It "SetServiceCommand can be used as API for '' with ''" -TestCases @( From 5101150ca85b37d2b6713cd7b5f991f201c10bbf Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Sat, 30 Sep 2017 12:44:44 +0200 Subject: [PATCH 07/19] [Feature]Freed the memory after using pointers. Corrected documentation on AddProperties method --- .../commands/management/Service.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 6ff68d23282..516522c8537 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -691,15 +691,18 @@ protected override void ProcessRecord() #endregion Overrides /// - /// Adds UserName, Description, BinPath and Delay Autostart info to the ServiceController object. + /// Adds UserName, Description, BinPath, DelayedAutoStart and StartupType to a ServiceController object. /// /// - /// + /// ServiceController as PSObject with UserName, Description and StartupType added private PSObject AddProperties(ServiceController service) { NakedWin32Handle hScManager = IntPtr.Zero; NakedWin32Handle hService = IntPtr.Zero; int lastError = 0; PSObject serviceAsPSObj = PSObject.AsPSObject(service); + IntPtr descriptionStructPtr = IntPtr.Zero; + IntPtr delayedAutoStartStuctPtr = IntPtr.Zero; + IntPtr serviceConfigStructPtr = IntPtr.Zero; try { hScManager = NativeMethods.OpenSCManagerW( @@ -733,16 +736,15 @@ private PSObject AddProperties(ServiceController service) { ServiceResources.CouldNotGetServiceInfo, ErrorCategory.PermissionDenied); } - IntPtr structurePointer = IntPtr.Zero; - bool queryStatus = NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DESCRIPTION, out structurePointer); - NativeMethods.SERVICE_DESCRIPTIONW description = (NativeMethods.SERVICE_DESCRIPTIONW)Marshal.PtrToStructure(structurePointer, typeof(NativeMethods.SERVICE_DESCRIPTIONW)); + bool queryStatus = NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DESCRIPTION, out descriptionStructPtr); + NativeMethods.SERVICE_DESCRIPTIONW description = (NativeMethods.SERVICE_DESCRIPTIONW)Marshal.PtrToStructure(descriptionStructPtr, typeof(NativeMethods.SERVICE_DESCRIPTIONW)); - queryStatus = queryStatus & NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, out structurePointer); - NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo = (NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)Marshal.PtrToStructure(structurePointer, typeof(NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)); + queryStatus = queryStatus & NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, out delayedAutoStartStuctPtr); + NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo = (NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)Marshal.PtrToStructure(delayedAutoStartStuctPtr, typeof(NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)); - queryStatus = queryStatus & NativeMethods.QueryServiceConfig(hService, out structurePointer); - NativeMethods.QUERY_SERVICE_CONFIG serviceInfo = (NativeMethods.QUERY_SERVICE_CONFIG)Marshal.PtrToStructure(structurePointer, typeof(NativeMethods.QUERY_SERVICE_CONFIG)); + queryStatus = queryStatus & NativeMethods.QueryServiceConfig(hService, out serviceConfigStructPtr); + NativeMethods.QUERY_SERVICE_CONFIG serviceInfo = (NativeMethods.QUERY_SERVICE_CONFIG)Marshal.PtrToStructure(serviceConfigStructPtr, typeof(NativeMethods.QUERY_SERVICE_CONFIG)); if(!queryStatus) { WriteNonTerminatingError( @@ -776,6 +778,9 @@ private PSObject AddProperties(ServiceController service) { } finally { + Marshal.FreeHGlobal(descriptionStructPtr); + Marshal.FreeHGlobal(delayedAutoStartStuctPtr); + Marshal.FreeHGlobal(serviceConfigStructPtr); if (IntPtr.Zero != hService) { bool succeeded = NativeMethods.CloseServiceHandle(hService); if (!succeeded) { @@ -1871,6 +1876,7 @@ protected override void ProcessRecord() NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, buffer); + Marshal.FreeHGlobal(buffer); if (!status) { int lastError = Marshal.GetLastWin32Error(); @@ -2265,6 +2271,7 @@ protected override void BeginProcessing() ServiceResources.CouldNotNewServiceDelayedAutoStart, ErrorCategory.PermissionDenied); } + Marshal.FreeHGlobal(buffer); } // write the ServiceController for the new service From e6a56f980cb1730b4644fb6818b62de1bdcd94cd Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Sat, 30 Sep 2017 13:07:55 +0200 Subject: [PATCH 08/19] [Feature]Use the corrrect dereferencing method when freeing pointers --- .../commands/management/Service.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 516522c8537..e50f27a87d5 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -1876,7 +1876,7 @@ protected override void ProcessRecord() NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, buffer); - Marshal.FreeHGlobal(buffer); + Marshal.FreeCoTaskMem(buffer); if (!status) { int lastError = Marshal.GetLastWin32Error(); @@ -2271,7 +2271,7 @@ protected override void BeginProcessing() ServiceResources.CouldNotNewServiceDelayedAutoStart, ErrorCategory.PermissionDenied); } - Marshal.FreeHGlobal(buffer); + Marshal.FreeCoTaskMem(buffer); } // write the ServiceController for the new service From 175897aa3fbc45ae3294e2c5f0ce46fda8b5b075 Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Sun, 1 Oct 2017 13:48:43 +0200 Subject: [PATCH 09/19] [Feature]Changed to AllocCoTaskMem when reserving memory --- .../commands/management/Service.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index e50f27a87d5..b21e0e8bd2b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -778,9 +778,9 @@ private PSObject AddProperties(ServiceController service) { } finally { - Marshal.FreeHGlobal(descriptionStructPtr); - Marshal.FreeHGlobal(delayedAutoStartStuctPtr); - Marshal.FreeHGlobal(serviceConfigStructPtr); + Marshal.FreeCoTaskMem(descriptionStructPtr); + Marshal.FreeCoTaskMem(delayedAutoStartStuctPtr); + Marshal.FreeCoTaskMem(serviceConfigStructPtr); if (IntPtr.Zero != hService) { bool succeeded = NativeMethods.CloseServiceHandle(hService); if (!succeeded) { @@ -2821,7 +2821,7 @@ internal static bool QueryServiceConfig(NakedWin32Handle hService, out IntPtr st cbBufSize: 0, pcbBytesNeeded: out bufferSizeNeeded); - lpBuffer = Marshal.AllocHGlobal((int)bufferSizeNeeded); + lpBuffer = Marshal.AllocCoTaskMem((int)bufferSizeNeeded); bufferSize = bufferSizeNeeded; status = NativeMethods.QueryServiceConfigW( @@ -2844,7 +2844,7 @@ internal static bool QueryServiceConfig2(NakedWin32Handle hService, DWORD infole cbBufSize: 0, pcbBytesNeeded: out bufferSizeNeeded); - lpBuffer = Marshal.AllocHGlobal((int)bufferSizeNeeded); + lpBuffer = Marshal.AllocCoTaskMem((int)bufferSizeNeeded); bufferSize = bufferSizeNeeded; status = NativeMethods.QueryServiceConfig2W( From b4b06da4e0879c1e7e550bf15043693ca2101f76 Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Mon, 2 Oct 2017 08:14:58 +0200 Subject: [PATCH 10/19] [Feature]Corrected formatting --- .../commands/management/Service.cs | 2 +- .../Set-Service.Tests.ps1 | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index b21e0e8bd2b..cda2ca8c04d 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -2917,7 +2917,7 @@ internal static ServiceStartupType GetServiceStartupType(ServiceStartMode startM #region ServiceStartupType /// - ///Enum for usage with StartupType. Automatic, Manual & Disabled index matched from System.ServiceProcess.ServiceStartMode + ///Enum for usage with StartupType. Automatic, Manual and Disabled index matched from System.ServiceProcess.ServiceStartMode /// public enum ServiceStartupType { ///Automatic service diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 index edd0f941e2f..451819bbb11 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 @@ -5,17 +5,17 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW $PSDefaultParameterValues["it:skip"] = $true } if($IsWindows) { - $userName = "testuserservices" - $testPass = "Secret123!" - net user $userName $testPass /add > $null - $password = ConvertTo-SecureString $testPass -AsPlainText -Force - $creds = [pscredential]::new(".\$userName", $password) + $userName = "testuserservices" + $testPass = "Secret123!" + net user $userName $testPass /add > $null + $password = ConvertTo-SecureString $testPass -AsPlainText -Force + $creds = [pscredential]::new(".\$userName", $password) } } AfterAll { $global:PSDefaultParameterValues = $originalDefaultParameterValues if($IsWindows) { - net user $userName /delete > $null + net user $userName /delete > $null } } @@ -241,7 +241,7 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW { Remove-Service -Name "testremoveservice" -ErrorAction 'Stop' } | ShouldBeErrorId "InvalidOperationException,Microsoft.PowerShell.Commands.RemoveServiceCommand" } - It "Get-Service can get the '' of a service" -TestCases @( + It "Get-Service can get the '' of a service" -TestCases @( @{property = "Description"; value = "This is a test description"} @{property = "BinPath"; value = "$PSHOME\powershell.exe"; parameters = @{ BinaryPathName = "$PSHOME\powershell.exe" }}, @{property = "UserName"; value = $creds.UserName; parameters = @{ Credential = $creds }}, From 51930c9384a1ad0641b555818622f6cf5836045c Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Mon, 2 Oct 2017 08:18:48 +0200 Subject: [PATCH 11/19] [Feature]Added spaces before IsWindow check --- .../Microsoft.PowerShell.Management/Set-Service.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 index 451819bbb11..af1f4a7c795 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 @@ -4,7 +4,7 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW if ( -not $IsWindows ) { $PSDefaultParameterValues["it:skip"] = $true } - if($IsWindows) { + if ($IsWindows) { $userName = "testuserservices" $testPass = "Secret123!" net user $userName $testPass /add > $null @@ -14,7 +14,7 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW } AfterAll { $global:PSDefaultParameterValues = $originalDefaultParameterValues - if($IsWindows) { + if ($IsWindows) { net user $userName /delete > $null } } From 17922cf79c76065d93345a0794d738150670ee9e Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Mon, 2 Oct 2017 09:25:38 +0200 Subject: [PATCH 12/19] [Feature]Corrected indention in test --- .../Set-Service.Tests.ps1 | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 index af1f4a7c795..b9e81bdf7bd 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 @@ -242,29 +242,29 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW } It "Get-Service can get the '' of a service" -TestCases @( - @{property = "Description"; value = "This is a test description"} - @{property = "BinPath"; value = "$PSHOME\powershell.exe"; parameters = @{ BinaryPathName = "$PSHOME\powershell.exe" }}, - @{property = "UserName"; value = $creds.UserName; parameters = @{ Credential = $creds }}, - @{property = "StartupType"; value = "AutomaticDelayedStart";} - ) { - param($property, $value, $parameters) - try { - $servicename = "testgetservice" - $startparameters = @{Name = $servicename; BinaryPathName = "$PSHOME\powershell.exe"} - if($parameters -ne $null) { - foreach($key in $parameters.Keys) { - $startparameters.$key = $parameters.$key + @{property = "Description"; value = "This is a test description"} + @{property = "BinPath"; value = "$PSHOME\powershell.exe"; parameters = @{ BinaryPathName = "$PSHOME\powershell.exe" }}, + @{property = "UserName"; value = $creds.UserName; parameters = @{ Credential = $creds }}, + @{property = "StartupType"; value = "AutomaticDelayedStart";} + ) { + param($property, $value, $parameters) + try { + $servicename = "testgetservice" + $startparameters = @{Name = $servicename; BinaryPathName = "$PSHOME\powershell.exe"} + if($parameters -ne $null) { + foreach($key in $parameters.Keys) { + $startparameters.$key = $parameters.$key + } + } else { + $startparameters.$property = $value } - } else { - $startparameters.$property = $value - } - $service = New-Service @startparameters - $service | Should Not BeNullOrEmpty - $service = Get-Service -Name $servicename - $service.$property | Should BeExactly $value + $service = New-Service @startparameters + $service | Should Not BeNullOrEmpty + $service = Get-Service -Name $servicename + $service.$property | Should BeExactly $value } finally { - Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue + Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue } } From 1eef7421ed7820af851159339eb3bcb2b8dcdeb7 Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Mon, 2 Oct 2017 22:41:00 +0200 Subject: [PATCH 13/19] [Feature]Renamed variable names for readability and consistency. --- .../commands/management/Service.cs | 24 +++++++++++++------ .../resources/ServiceResources.resx | 2 +- .../Set-Service.Tests.ps1 | 8 +++---- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index cda2ca8c04d..ea95e9a6aeb 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -691,7 +691,7 @@ protected override void ProcessRecord() #endregion Overrides /// - /// Adds UserName, Description, BinPath, DelayedAutoStart and StartupType to a ServiceController object. + /// Adds UserName, Description, BinaryPathName, DelayedAutoStart and StartupType to a ServiceController object. /// /// /// ServiceController as PSObject with UserName, Description and StartupType added @@ -737,16 +737,16 @@ private PSObject AddProperties(ServiceController service) { ErrorCategory.PermissionDenied); } - bool queryStatus = NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DESCRIPTION, out descriptionStructPtr); + bool querySuccessful = NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DESCRIPTION, out descriptionStructPtr); NativeMethods.SERVICE_DESCRIPTIONW description = (NativeMethods.SERVICE_DESCRIPTIONW)Marshal.PtrToStructure(descriptionStructPtr, typeof(NativeMethods.SERVICE_DESCRIPTIONW)); - queryStatus = queryStatus & NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, out delayedAutoStartStuctPtr); + querySuccessful = querySuccessful && NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, out delayedAutoStartStuctPtr); NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo = (NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)Marshal.PtrToStructure(delayedAutoStartStuctPtr, typeof(NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)); - queryStatus = queryStatus & NativeMethods.QueryServiceConfig(hService, out serviceConfigStructPtr); + querySuccessful = querySuccessful && NativeMethods.QueryServiceConfig(hService, out serviceConfigStructPtr); NativeMethods.QUERY_SERVICE_CONFIG serviceInfo = (NativeMethods.QUERY_SERVICE_CONFIG)Marshal.PtrToStructure(serviceConfigStructPtr, typeof(NativeMethods.QUERY_SERVICE_CONFIG)); - if(!queryStatus) { + if(!querySuccessful) { WriteNonTerminatingError( service: service, innerException: null, @@ -768,9 +768,9 @@ private PSObject AddProperties(ServiceController service) { serviceAsPSObj.Properties.Add(noteProperty, true); serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#DelayedAutoStart"); - noteProperty = new PSProperty("BinPath", serviceInfo.lpBinaryPathName); + noteProperty = new PSProperty("BinaryPathName", serviceInfo.lpBinaryPathName); serviceAsPSObj.Properties.Add(noteProperty, true); - serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#BinPath"); + serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#BinaryPathName"); noteProperty = new PSProperty("StartupType", NativeMethods.GetServiceStartupType(service.StartType, autostartInfo.fDelayedAutostart)); serviceAsPSObj.Properties.Add(noteProperty, true); @@ -2814,6 +2814,7 @@ public static extern bool QueryInformationJobObject(SafeHandle hJob, int JobObje internal static bool QueryServiceConfig(NakedWin32Handle hService, out IntPtr structurePointer) { IntPtr lpBuffer = IntPtr.Zero; + structurePointer = IntPtr.Zero; DWORD bufferSize, bufferSizeNeeded = 0; bool status = NativeMethods.QueryServiceConfigW( hSCManager: hService, @@ -2821,6 +2822,10 @@ internal static bool QueryServiceConfig(NakedWin32Handle hService, out IntPtr st cbBufSize: 0, pcbBytesNeeded: out bufferSizeNeeded); + if (status != true && Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) { + return status; + } + lpBuffer = Marshal.AllocCoTaskMem((int)bufferSizeNeeded); bufferSize = bufferSizeNeeded; @@ -2836,6 +2841,7 @@ internal static bool QueryServiceConfig(NakedWin32Handle hService, out IntPtr st internal static bool QueryServiceConfig2(NakedWin32Handle hService, DWORD infolevel, out IntPtr structurePointer) { IntPtr lpBuffer = IntPtr.Zero; + structurePointer = IntPtr.Zero; DWORD bufferSize, bufferSizeNeeded = 0; bool status = NativeMethods.QueryServiceConfig2W( hService: hService, @@ -2844,6 +2850,10 @@ internal static bool QueryServiceConfig2(NakedWin32Handle hService, DWORD infole cbBufSize: 0, pcbBytesNeeded: out bufferSizeNeeded); + if (status != true && Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) { + return status; + } + lpBuffer = Marshal.AllocCoTaskMem((int)bufferSizeNeeded); bufferSize = bufferSizeNeeded; diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx index 24ee65b24ad..2553579c6ff 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx @@ -178,7 +178,7 @@ Service '{1} ({0})' was created, but its description cannot be configured due to the following error: {2} - Service '{1} ({0})' was created, but its startuptype, automatic (delayed start), cannot be configured due to the following error: {2} + Service '{1} ({0})' was created, but its StartupType 'Automatic (Delayed Start)' could not be configured due to the following error: {2} Service '{1} ({0})' cannot be removed due to the following error: {2} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 index b9e81bdf7bd..a52bd62b6ba 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 @@ -242,10 +242,10 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW } It "Get-Service can get the '' of a service" -TestCases @( - @{property = "Description"; value = "This is a test description"} - @{property = "BinPath"; value = "$PSHOME\powershell.exe"; parameters = @{ BinaryPathName = "$PSHOME\powershell.exe" }}, - @{property = "UserName"; value = $creds.UserName; parameters = @{ Credential = $creds }}, - @{property = "StartupType"; value = "AutomaticDelayedStart";} + @{property = "Description"; value = "This is a test description"} + @{property = "BinaryPathName"; value = "$PSHOME\powershell.exe";}, + @{property = "UserName"; value = $creds.UserName; parameters = @{ Credential = $creds }}, + @{property = "StartupType"; value = "AutomaticDelayedStart";} ) { param($property, $value, $parameters) try { From d3c818cfc5a6f91e912f755faa418058473b6630 Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Tue, 3 Oct 2017 12:50:35 +0200 Subject: [PATCH 14/19] [Feature]Moved FreeCoTaskMem to finally block on New-Service and Set-Service --- .../commands/management/Service.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index ea95e9a6aeb..59f37c58a3e 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -1756,6 +1756,7 @@ protected override void ProcessRecord() NakedWin32Handle hScManager = IntPtr.Zero; NakedWin32Handle hService = IntPtr.Zero; + IntPtr delayedAutoStartInfoBuffer = IntPtr.Zero; try { hScManager = NativeMethods.OpenSCManagerW( @@ -1868,15 +1869,14 @@ protected override void ProcessRecord() NativeMethods.SERVICE_DELAYED_AUTO_START_INFO ds = new NativeMethods.SERVICE_DELAYED_AUTO_START_INFO(); ds.fDelayedAutostart = StartupType == ServiceStartupType.AutomaticDelayedStart; size = Marshal.SizeOf(ds); - buffer = Marshal.AllocCoTaskMem(size); - Marshal.StructureToPtr(ds, buffer, false); + delayedAutoStartInfoBuffer = Marshal.AllocCoTaskMem(size); + Marshal.StructureToPtr(ds, delayedAutoStartInfoBuffer, false); status = NativeMethods.ChangeServiceConfig2W( hService, NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, - buffer); + delayedAutoStartInfoBuffer); - Marshal.FreeCoTaskMem(buffer); if (!status) { int lastError = Marshal.GetLastWin32Error(); @@ -1950,6 +1950,11 @@ protected override void ProcessRecord() } finally { + if (IntPtr.Zero != delayedAutoStartInfoBuffer) + { + Marshal.FreeCoTaskMem(delayedAutoStartInfoBuffer); + } + if (IntPtr.Zero != hService) { bool succeeded = NativeMethods.CloseServiceHandle(hService); @@ -2123,6 +2128,7 @@ protected override void BeginProcessing() NakedWin32Handle hScManager = IntPtr.Zero; NakedWin32Handle hService = IntPtr.Zero; IntPtr password = IntPtr.Zero; + IntPtr delayedAutoStartInfoBuffer = IntPtr.Zero; try { hScManager = NativeMethods.OpenSCManagerW( @@ -2250,13 +2256,13 @@ protected override void BeginProcessing() NativeMethods.SERVICE_DELAYED_AUTO_START_INFO ds = new NativeMethods.SERVICE_DELAYED_AUTO_START_INFO(); ds.fDelayedAutostart = true; size = Marshal.SizeOf(ds); - buffer = Marshal.AllocCoTaskMem(size); - Marshal.StructureToPtr(ds, buffer, false); + delayedAutoStartInfoBuffer = Marshal.AllocCoTaskMem(size); + Marshal.StructureToPtr(ds, delayedAutoStartInfoBuffer, false); succeeded = NativeMethods.ChangeServiceConfig2W( hService, NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, - buffer); + delayedAutoStartInfoBuffer); if (!succeeded) { @@ -2283,6 +2289,11 @@ protected override void BeginProcessing() } finally { + if (IntPtr.Zero != delayedAutoStartInfoBuffer) + { + Marshal.FreeCoTaskMem(delayedAutoStartInfoBuffer); + } + if (IntPtr.Zero != password) { Marshal.ZeroFreeCoTaskMemUnicode(password); From a8120ea0037ae1b60553133931d166bb1af3938f Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Tue, 3 Oct 2017 19:25:17 +0200 Subject: [PATCH 15/19] [Feature]Remove FreeCoTaskMem(buffer) as the variable was renamed and moved to the finally block in the last commit --- .../commands/management/Service.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 59f37c58a3e..537109f8b7f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -2277,7 +2277,6 @@ protected override void BeginProcessing() ServiceResources.CouldNotNewServiceDelayedAutoStart, ErrorCategory.PermissionDenied); } - Marshal.FreeCoTaskMem(buffer); } // write the ServiceController for the new service From 9e4bf0ce7928288dca919a632b4756a4d22cded1 Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Wed, 18 Oct 2017 21:15:53 +0200 Subject: [PATCH 16/19] [Feature]Made generic function to query for service information. --- .../commands/management/Service.cs | 75 +++++++++++-------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 537109f8b7f..c21713585fc 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -700,9 +700,6 @@ private PSObject AddProperties(ServiceController service) { NakedWin32Handle hService = IntPtr.Zero; int lastError = 0; PSObject serviceAsPSObj = PSObject.AsPSObject(service); - IntPtr descriptionStructPtr = IntPtr.Zero; - IntPtr delayedAutoStartStuctPtr = IntPtr.Zero; - IntPtr serviceConfigStructPtr = IntPtr.Zero; try { hScManager = NativeMethods.OpenSCManagerW( @@ -737,14 +734,14 @@ private PSObject AddProperties(ServiceController service) { ErrorCategory.PermissionDenied); } - bool querySuccessful = NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DESCRIPTION, out descriptionStructPtr); - NativeMethods.SERVICE_DESCRIPTIONW description = (NativeMethods.SERVICE_DESCRIPTIONW)Marshal.PtrToStructure(descriptionStructPtr, typeof(NativeMethods.SERVICE_DESCRIPTIONW)); + NativeMethods.SERVICE_DESCRIPTIONW description = new NativeMethods.SERVICE_DESCRIPTIONW(); + bool querySuccessful = NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DESCRIPTION, out description); - querySuccessful = querySuccessful && NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, out delayedAutoStartStuctPtr); - NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo = (NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)Marshal.PtrToStructure(delayedAutoStartStuctPtr, typeof(NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)); + NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo = new NativeMethods.SERVICE_DELAYED_AUTO_START_INFO(); + querySuccessful = querySuccessful && NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, out autostartInfo); - querySuccessful = querySuccessful && NativeMethods.QueryServiceConfig(hService, out serviceConfigStructPtr); - NativeMethods.QUERY_SERVICE_CONFIG serviceInfo = (NativeMethods.QUERY_SERVICE_CONFIG)Marshal.PtrToStructure(serviceConfigStructPtr, typeof(NativeMethods.QUERY_SERVICE_CONFIG)); + NativeMethods.QUERY_SERVICE_CONFIG serviceInfo = new NativeMethods.QUERY_SERVICE_CONFIG(); + querySuccessful = querySuccessful && NativeMethods.QueryServiceConfig(hService, out serviceInfo); if(!querySuccessful) { WriteNonTerminatingError( @@ -778,9 +775,6 @@ private PSObject AddProperties(ServiceController service) { } finally { - Marshal.FreeCoTaskMem(descriptionStructPtr); - Marshal.FreeCoTaskMem(delayedAutoStartStuctPtr); - Marshal.FreeCoTaskMem(serviceConfigStructPtr); if (IntPtr.Zero != hService) { bool succeeded = NativeMethods.CloseServiceHandle(hService); if (!succeeded) { @@ -2821,10 +2815,10 @@ public static extern bool QueryInformationJobObject(SafeHandle hJob, int JobObje ref JOBOBJECT_BASIC_PROCESS_ID_LIST lpJobObjectInfo, int cbJobObjectLength, IntPtr lpReturnLength); - internal static bool QueryServiceConfig(NakedWin32Handle hService, out IntPtr structurePointer) + internal static bool QueryServiceConfig(NakedWin32Handle hService, out NativeMethods.QUERY_SERVICE_CONFIG configStructure) { IntPtr lpBuffer = IntPtr.Zero; - structurePointer = IntPtr.Zero; + configStructure = default(NativeMethods.QUERY_SERVICE_CONFIG); DWORD bufferSize, bufferSizeNeeded = 0; bool status = NativeMethods.QueryServiceConfigW( hSCManager: hService, @@ -2836,23 +2830,31 @@ internal static bool QueryServiceConfig(NakedWin32Handle hService, out IntPtr st return status; } - lpBuffer = Marshal.AllocCoTaskMem((int)bufferSizeNeeded); - bufferSize = bufferSizeNeeded; + try + { + lpBuffer = Marshal.AllocCoTaskMem((int)bufferSizeNeeded); + bufferSize = bufferSizeNeeded; - status = NativeMethods.QueryServiceConfigW( - hService, - lpBuffer, - bufferSize, - out bufferSizeNeeded); - structurePointer = lpBuffer; + status = NativeMethods.QueryServiceConfigW( + hService, + lpBuffer, + bufferSize, + out bufferSizeNeeded); + configStructure = (NativeMethods.QUERY_SERVICE_CONFIG)Marshal.PtrToStructure(lpBuffer, typeof(NativeMethods.QUERY_SERVICE_CONFIG)); + } + finally + { + Marshal.FreeCoTaskMem(lpBuffer); + } return status; } - internal static bool QueryServiceConfig2(NakedWin32Handle hService, DWORD infolevel, out IntPtr structurePointer) + internal static bool QueryServiceConfig2(NakedWin32Handle hService, DWORD infolevel, out T configStructure) { IntPtr lpBuffer = IntPtr.Zero; - structurePointer = IntPtr.Zero; + configStructure = default(T); DWORD bufferSize, bufferSizeNeeded = 0; + bool status = NativeMethods.QueryServiceConfig2W( hService: hService, dwInfoLevel: infolevel, @@ -2864,16 +2866,23 @@ internal static bool QueryServiceConfig2(NakedWin32Handle hService, DWORD infole return status; } - lpBuffer = Marshal.AllocCoTaskMem((int)bufferSizeNeeded); - bufferSize = bufferSizeNeeded; + try + { + lpBuffer = Marshal.AllocCoTaskMem((int)bufferSizeNeeded); + bufferSize = bufferSizeNeeded; - status = NativeMethods.QueryServiceConfig2W( - hService, - infolevel, - lpBuffer, - bufferSize, - out bufferSizeNeeded); - structurePointer = lpBuffer; + status = NativeMethods.QueryServiceConfig2W( + hService, + infolevel, + lpBuffer, + bufferSize, + out bufferSizeNeeded); + configStructure = (T)Marshal.PtrToStructure(lpBuffer, typeof(T)); + } + finally + { + Marshal.FreeCoTaskMem(lpBuffer); + } return status; } From 9e2ddb33c2e8cff2ec97ebc59825ed47f85506dd Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Wed, 18 Oct 2017 22:46:53 +0200 Subject: [PATCH 17/19] [Feature]Corrected indention --- .../commands/management/Service.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index bc006551541..7ba7caa1041 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -1632,7 +1632,7 @@ protected override void ProcessRecord() NakedWin32Handle hScManager = IntPtr.Zero; NakedWin32Handle hService = IntPtr.Zero; - IntPtr delayedAutoStartInfoBuffer = IntPtr.Zero; + IntPtr delayedAutoStartInfoBuffer = IntPtr.Zero; try { hScManager = NativeMethods.OpenSCManagerW( From 755e19239c1b0f2f6b3ec2fed0396a9b73c05d5f Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Thu, 19 Oct 2017 15:59:05 +0200 Subject: [PATCH 18/19] [Feature]Corrected error message to refence the new name --- .../commands/management/Service.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 7ba7caa1041..945774157b5 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -619,7 +619,7 @@ private PSObject AddProperties(ServiceController service) { service, service.MachineName, exception, - "ComputerAccessDenied", + "FailToOpenServiceControlManager", ServiceResources.ComputerAccessDenied, ErrorCategory.PermissionDenied); } From a9a2b325a882f69b041db2252880662ce0f6e14a Mon Sep 17 00:00:00 2001 From: Jonas Andersen Date: Fri, 20 Oct 2017 23:49:41 +0200 Subject: [PATCH 19/19] [Feature]Corrected missed merge errors and made a InvalidValue ServiceStartupType --- .../commands/management/Service.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 945774157b5..24d574b0edd 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -617,10 +617,9 @@ private PSObject AddProperties(ServiceController service) { Win32Exception exception = new Win32Exception(lastError); WriteNonTerminatingError( service, - service.MachineName, exception, "FailToOpenServiceControlManager", - ServiceResources.ComputerAccessDenied, + ServiceResources.FailToOpenServiceControlManager, ErrorCategory.PermissionDenied); } hService = NativeMethods.OpenServiceW( @@ -1515,7 +1514,7 @@ public ServiceStartupType StartupType } // We set the initial value to an invalid value so that we can // distinguish when this is and is not set. - internal ServiceStartupType startupType = (ServiceStartupType)(-1); + internal ServiceStartupType startupType = ServiceStartupType.InvalidValue; /// @@ -1672,7 +1671,7 @@ protected override void ProcessRecord() } // Modify startup type or display name or credential if (!String.IsNullOrEmpty(DisplayName) - || (ServiceStartupType)(-1) != StartupType || null != Credential) + || ServiceStartupType.InvalidValue != StartupType || null != Credential) { DWORD dwStartType = NativeMethods.SERVICE_NO_CHANGE; if (!NativeMethods.TryGetNativeStartupType(StartupType, out dwStartType)) @@ -2768,7 +2767,7 @@ internal static bool TryGetNativeStartupType(ServiceStartupType StartupType, out case ServiceStartupType.Disabled: dwStartType = NativeMethods.SERVICE_DISABLED; break; - case (ServiceStartupType)(-1): + case ServiceStartupType.InvalidValue: dwStartType = NativeMethods.SERVICE_NO_CHANGE; break; default: @@ -2803,6 +2802,8 @@ internal static ServiceStartupType GetServiceStartupType(ServiceStartMode startM ///Enum for usage with StartupType. Automatic, Manual and Disabled index matched from System.ServiceProcess.ServiceStartMode /// public enum ServiceStartupType { + ///Invalid service + InvalidValue = -1, ///Automatic service Automatic = 2, ///Manual service