Skip to content

Commit 352fd38

Browse files
authored
Update Get-Service to ignore common errors when retrieving non-critical properties for a service (#24245)
1 parent 12a6500 commit 352fd38

File tree

3 files changed

+171
-100
lines changed

3 files changed

+171
-100
lines changed

src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs

Lines changed: 113 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -610,146 +610,163 @@ public string[] Name
610610
/// </summary>
611611
protected override void ProcessRecord()
612612
{
613-
foreach (ServiceController service in MatchingServices())
613+
nint scManagerHandle = nint.Zero;
614+
if (!DependentServices && !RequiredServices)
614615
{
615-
if (!DependentServices.IsPresent && !RequiredServices.IsPresent)
616-
{
617-
WriteObject(AddProperties(service));
616+
// As Get-Service only works on local services we get this once
617+
// to retrieve extra properties added by PowerShell.
618+
scManagerHandle = NativeMethods.OpenSCManagerW(
619+
lpMachineName: null,
620+
lpDatabaseName: null,
621+
dwDesiredAccess: NativeMethods.SC_MANAGER_CONNECT);
622+
if (scManagerHandle == nint.Zero)
623+
{
624+
Win32Exception exception = new();
625+
string message = StringUtil.Format(ServiceResources.FailToOpenServiceControlManager, exception.Message);
626+
ServiceCommandException serviceException = new ServiceCommandException(message, exception);
627+
ErrorRecord err = new ErrorRecord(
628+
serviceException,
629+
"FailToOpenServiceControlManager",
630+
ErrorCategory.PermissionDenied,
631+
null);
632+
ThrowTerminatingError(err);
618633
}
619-
else
634+
}
635+
636+
try
637+
{
638+
foreach (ServiceController service in MatchingServices())
620639
{
621-
if (DependentServices.IsPresent)
640+
if (!DependentServices.IsPresent && !RequiredServices.IsPresent)
622641
{
623-
foreach (ServiceController dependantserv in service.DependentServices)
642+
WriteObject(AddProperties(scManagerHandle, service));
643+
}
644+
else
645+
{
646+
if (DependentServices.IsPresent)
624647
{
625-
WriteObject(dependantserv);
648+
foreach (ServiceController dependantserv in service.DependentServices)
649+
{
650+
WriteObject(dependantserv);
651+
}
626652
}
627-
}
628653

629-
if (RequiredServices.IsPresent)
630-
{
631-
foreach (ServiceController servicedependedon in service.ServicesDependedOn)
654+
if (RequiredServices.IsPresent)
632655
{
633-
WriteObject(servicedependedon);
656+
foreach (ServiceController servicedependedon in service.ServicesDependedOn)
657+
{
658+
WriteObject(servicedependedon);
659+
}
634660
}
635661
}
636662
}
637663
}
664+
finally
665+
{
666+
if (scManagerHandle != nint.Zero)
667+
{
668+
bool succeeded = NativeMethods.CloseServiceHandle(scManagerHandle);
669+
Diagnostics.Assert(succeeded, "SCManager handle close failed");
670+
}
671+
}
638672
}
639673

640674
#endregion Overrides
641675

676+
#nullable enable
642677
/// <summary>
643678
/// Adds UserName, Description, BinaryPathName, DelayedAutoStart and StartupType to a ServiceController object.
644679
/// </summary>
680+
/// <param name="scManagerHandle">Handle to the local SCManager instance.</param>
645681
/// <param name="service"></param>
646682
/// <returns>ServiceController as PSObject with UserName, Description and StartupType added.</returns>
647-
private PSObject AddProperties(ServiceController service)
683+
private static PSObject AddProperties(nint scManagerHandle, ServiceController service)
648684
{
649-
NakedWin32Handle hScManager = IntPtr.Zero;
650-
NakedWin32Handle hService = IntPtr.Zero;
651-
int lastError = 0;
652-
PSObject serviceAsPSObj = PSObject.AsPSObject(service);
685+
NakedWin32Handle hService = nint.Zero;
686+
687+
// As these are optional values, a failure due to permissions or
688+
// other problem is ignored and the properties are set to null.
689+
bool? isDelayedAutoStart = null;
690+
string? binPath = null;
691+
string? description = null;
692+
string? startName = null;
693+
ServiceStartupType startupType = ServiceStartupType.InvalidValue;
653694
try
654695
{
655-
hScManager = NativeMethods.OpenSCManagerW(
656-
lpMachineName: service.MachineName,
657-
lpDatabaseName: null,
658-
dwDesiredAccess: NativeMethods.SC_MANAGER_CONNECT
659-
);
660-
if (hScManager == IntPtr.Zero)
661-
{
662-
lastError = Marshal.GetLastWin32Error();
663-
Win32Exception exception = new(lastError);
664-
WriteNonTerminatingError(
665-
service,
666-
exception,
667-
"FailToOpenServiceControlManager",
668-
ServiceResources.FailToOpenServiceControlManager,
669-
ErrorCategory.PermissionDenied);
670-
}
671-
696+
// We don't use service.ServiceHandle as that requests
697+
// SERVICE_ALL_ACCESS when we only need SERVICE_QUERY_CONFIG.
672698
hService = NativeMethods.OpenServiceW(
673-
hScManager,
699+
scManagerHandle,
674700
service.ServiceName,
675701
NativeMethods.SERVICE_QUERY_CONFIG
676702
);
677-
if (hService == IntPtr.Zero)
703+
if (hService != nint.Zero)
678704
{
679-
lastError = Marshal.GetLastWin32Error();
680-
Win32Exception exception = new(lastError);
681-
WriteNonTerminatingError(
682-
service,
683-
exception,
684-
"CouldNotGetServiceInfo",
685-
ServiceResources.CouldNotGetServiceInfo,
686-
ErrorCategory.PermissionDenied);
687-
}
688-
689-
NativeMethods.SERVICE_DESCRIPTIONW description = new();
690-
bool querySuccessful = NativeMethods.QueryServiceConfig2<NativeMethods.SERVICE_DESCRIPTIONW>(hService, NativeMethods.SERVICE_CONFIG_DESCRIPTION, out description);
691-
692-
NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo = new();
693-
querySuccessful = querySuccessful && NativeMethods.QueryServiceConfig2<NativeMethods.SERVICE_DELAYED_AUTO_START_INFO>(hService, NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, out autostartInfo);
705+
if (NativeMethods.QueryServiceConfig2(
706+
hService,
707+
NativeMethods.SERVICE_CONFIG_DESCRIPTION,
708+
out NativeMethods.SERVICE_DESCRIPTIONW descriptionInfo))
709+
{
710+
description = descriptionInfo.lpDescription;
711+
}
694712

695-
NativeMethods.QUERY_SERVICE_CONFIG serviceInfo = new();
696-
querySuccessful = querySuccessful && NativeMethods.QueryServiceConfig(hService, out serviceInfo);
713+
if (NativeMethods.QueryServiceConfig2(
714+
hService,
715+
NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
716+
out NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo))
717+
{
718+
isDelayedAutoStart = autostartInfo.fDelayedAutostart;
719+
}
697720

698-
if (!querySuccessful)
699-
{
700-
WriteNonTerminatingError(
701-
service: service,
702-
innerException: null,
703-
errorId: "CouldNotGetServiceInfo",
704-
errorMessage: ServiceResources.CouldNotGetServiceInfo,
705-
category: ErrorCategory.PermissionDenied
706-
);
721+
if (NativeMethods.QueryServiceConfig(
722+
hService,
723+
out NativeMethods.QUERY_SERVICE_CONFIG serviceInfo))
724+
{
725+
binPath = serviceInfo.lpBinaryPathName;
726+
startName = serviceInfo.lpServiceStartName;
727+
if (isDelayedAutoStart.HasValue)
728+
{
729+
startupType = NativeMethods.GetServiceStartupType(
730+
(ServiceStartMode)serviceInfo.dwStartType,
731+
isDelayedAutoStart.Value);
732+
}
733+
}
707734
}
708-
709-
PSProperty noteProperty = new("UserName", serviceInfo.lpServiceStartName);
710-
serviceAsPSObj.Properties.Add(noteProperty, true);
711-
serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#UserName");
712-
713-
noteProperty = new PSProperty("Description", description.lpDescription);
714-
serviceAsPSObj.Properties.Add(noteProperty, true);
715-
serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#Description");
716-
717-
noteProperty = new PSProperty("DelayedAutoStart", autostartInfo.fDelayedAutostart);
718-
serviceAsPSObj.Properties.Add(noteProperty, true);
719-
serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#DelayedAutoStart");
720-
721-
noteProperty = new PSProperty("BinaryPathName", serviceInfo.lpBinaryPathName);
722-
serviceAsPSObj.Properties.Add(noteProperty, true);
723-
serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#BinaryPathName");
724-
725-
noteProperty = new PSProperty("StartupType", NativeMethods.GetServiceStartupType(service.StartType, autostartInfo.fDelayedAutostart));
726-
serviceAsPSObj.Properties.Add(noteProperty, true);
727-
serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#StartupType");
728735
}
729736
finally
730737
{
731738
if (hService != IntPtr.Zero)
732739
{
733740
bool succeeded = NativeMethods.CloseServiceHandle(hService);
734-
if (!succeeded)
735-
{
736-
Diagnostics.Assert(lastError != 0, "ErrorCode not success");
737-
}
738-
}
739-
740-
if (hScManager != IntPtr.Zero)
741-
{
742-
bool succeeded = NativeMethods.CloseServiceHandle(hScManager);
743-
if (!succeeded)
744-
{
745-
Diagnostics.Assert(lastError != 0, "ErrorCode not success");
746-
}
741+
Diagnostics.Assert(succeeded, "Failed to close service handle");
747742
}
748743
}
749744

745+
PSObject serviceAsPSObj = PSObject.AsPSObject(service);
746+
PSNoteProperty noteProperty = new("UserName", startName);
747+
serviceAsPSObj.Properties.Add(noteProperty, true);
748+
serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#UserName");
749+
750+
noteProperty = new PSNoteProperty("Description", description);
751+
serviceAsPSObj.Properties.Add(noteProperty, true);
752+
serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#Description");
753+
754+
noteProperty = new PSNoteProperty("DelayedAutoStart", isDelayedAutoStart);
755+
serviceAsPSObj.Properties.Add(noteProperty, true);
756+
serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#DelayedAutoStart");
757+
758+
noteProperty = new PSNoteProperty("BinaryPathName", binPath);
759+
serviceAsPSObj.Properties.Add(noteProperty, true);
760+
serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#BinaryPathName");
761+
762+
noteProperty = new PSNoteProperty("StartupType", startupType);
763+
serviceAsPSObj.Properties.Add(noteProperty, true);
764+
serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#StartupType");
765+
750766
return serviceAsPSObj;
751767
}
752768
}
769+
#nullable disable
753770
#endregion GetServiceCommand
754771

755772
#region ServiceOperationBaseCommand

src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,6 @@
159159
<data name="CouldNotSetService" xml:space="preserve">
160160
<value>Service '{1} ({0})' cannot be configured due to the following error: {2}</value>
161161
</data>
162-
<data name="CouldNotGetServiceInfo" xml:space="preserve">
163-
<value>Service '{1} ({0})' cannot be queried due to the following error: {2}</value>
164-
</data>
165162
<data name="CouldNotSetServiceDescription" xml:space="preserve">
166163
<value>Service '{1} ({0})' description cannot be configured due to the following error: {2}</value>
167164
</data>
@@ -211,7 +208,7 @@
211208
<value>Service '{1} ({0})' resume failed.</value>
212209
</data>
213210
<data name="FailToOpenServiceControlManager" xml:space="preserve">
214-
<value>Failed to configure the service '{1} ({0})' due to the following error: {2}. Run PowerShell as admin and run your command again.</value>
211+
<value>Failed to open SCManager due to the following error: {0}. Run PowerShell as admin and run your command again.</value>
215212
</data>
216213
<data name="UnsupportedStartupType" xml:space="preserve">
217214
<value>The startup type '{0}' is not supported by {1}.</value>

test/powershell/Modules/Microsoft.PowerShell.Management/Get-Service.Tests.ps1

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,60 @@ Describe "Get-Service cmdlet tests" -Tags "CI" {
8282
{ & $script } | Should -Throw -ErrorId $errorid
8383
}
8484
}
85+
86+
Describe 'Get-Service Admin tests' -Tag CI,RequireAdminOnWindows {
87+
BeforeAll {
88+
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
89+
if ( -not $IsWindows ) {
90+
$PSDefaultParameterValues["it:skip"] = $true
91+
}
92+
}
93+
AfterAll {
94+
$global:PSDefaultParameterValues = $originalDefaultParameterValues
95+
}
96+
97+
BeforeEach {
98+
$serviceParams = @{
99+
Name = "PowerShellTest-$([Guid]::NewGuid().Guid)"
100+
BinaryPathName = "$env:SystemRoot\System32\cmd.exe"
101+
StartupType = 'Manual'
102+
}
103+
$service = New-Service @serviceParams
104+
}
105+
AfterEach {
106+
$service | Remove-Service
107+
}
108+
109+
It "Ignores invalid description MUI entry" {
110+
$serviceRegPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$($service.Name)"
111+
Set-ItemProperty -LiteralPath $serviceRegPath -Name Description -Value '@Fake.dll,-0'
112+
$actual = Get-Service -Name $service.Name -ErrorAction Stop
113+
114+
$actual.Name | Should -Be $service.Name
115+
$actual.Status | Should -Be Stopped
116+
$actual.Description | Should -BeNullOrEmpty
117+
$actual.UserName | Should -Be LocalSystem
118+
$actual.StartupType | Should -Be Manual
119+
}
120+
121+
It "Ignores no SERVICE_QUERY_CONFIG access" {
122+
$sddl = ((sc.exe sdshow $service.Name) -join "").Trim()
123+
$sd = ConvertFrom-SddlString -Sddl $sddl
124+
$sd.RawDescriptor.DiscretionaryAcl.AddAccess(
125+
[System.Security.AccessControl.AccessControlType]::Deny,
126+
[System.Security.Principal.WindowsIdentity]::GetCurrent().User,
127+
0x1, # SERVICE_QUERY_CONFIG
128+
[System.Security.AccessControl.InheritanceFlags]::None,
129+
[System.Security.AccessControl.PropagationFlags]::None)
130+
$newSddl = $sd.RawDescriptor.GetSddlForm([System.Security.AccessControl.AccessControlSections]::All)
131+
$null = sc.exe sdset $service.Name $newSddl
132+
133+
$actual = Get-Service -Name $service.Name -ErrorAction Stop
134+
135+
$actual.Name | Should -Be $service.Name
136+
$actual.Status | Should -Be Stopped
137+
$actual.Description | Should -BeNullOrEmpty
138+
$actual.UserName | Should -BeNullOrEmpty
139+
$actual.StartupType | Should -Be InvalidValue
140+
}
141+
}

0 commit comments

Comments
 (0)