You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add Get-Service, Start-Service, Stop-Service and Restart-Service cmdlets on Unix - #28385
These cmdlets previously did not exist on Unix at all, which made cross-platform service
management impossible.
The implementation follows the cmdlets WG recommendation recorded in #18863 (comment):
a fallback system to search for common commands used by various distros (including macOS) to restart a service on those platforms and if no common command is found then fail the entire operation
looking if sudo exists and using that to avoid the inevitable permission problem as sudo cannot elevate cmdlets
check the exit code and if non-zero, emit STDERR as an ErrorRecord
We would expect Get-Service, Stop-Service and Start-Service to also use the same/similar code
Rather than duplicating the cmdlets, master's Service.cs is now also compiled on Unix. A using ServiceController = Microsoft.PowerShell.Commands.UnixServiceController alias maps the
unqualified ServiceController name to a new Unix facade (UnixServiceController.cs) so the
shared cmdlet implementation works unchanged. Only a few places are platform-specific: the five
Windows-SCM-only cmdlets (Set-Service, New-Service, Remove-Service, Suspend-Service, Resume-Service) and the SCM property/native sections stay guarded with #if !UNIX, Get-Service's ProcessRecord has a small Unix branch, and DoStartService/DoStopService
surface sudo-unavailable failures as SudoRequired errors.
The cmdlets resolve the service manager through PATH in this order and fail the operation with a
terminating error when none is found:
Priority
Manager
Covered init systems
1
systemctl
systemd
2
launchctl
launchd (macOS)
3
service
sysvinit and compatible
4
rc-service
OpenRC
Elevation
Mutating operations (Start-Service, Stop-Service, Restart-Service) are wrapped with sudo -n when the process is not running as root, so they never hang on a password prompt. If
sudo would require a password, the cmdlet writes a non-terminating error with guidance to run pwsh as root or configure passwordless sudo. Get-Service works unprivileged.
Error semantics
Non-zero exit codes surface the command's stderr as a non-terminating ErrorRecord per service
(exception type ServiceCommandException, mirroring Windows), with error IDs matching the Windows
cmdlets (CouldNotStartService, CouldNotStopService), plus SudoRequired when elevation is
unavailable. A missing service manager is reported as a terminating NoServiceManagerFound error,
and service-manager enumeration failures terminate the cmdlet.
Output type
Get-Service returns the new Microsoft.PowerShell.Commands.UnixServiceController type with Name, DisplayName, Status, StartType, Description, SubState, and UnitFileState. Status and StartType reuse the System.ServiceProcess.ServiceControllerStatus / ServiceStartMode enums (the managed assembly ships cross-platform; only the Windows
implementation is RID-specific), so Where-Object Status -eq Running and other pipelines behave
identically on both platforms. Objects carry a DefaultDisplayPropertySet so the default table
view matches Windows.
Parameter behavior:
Wildcards, -DisplayName, -Include/-Exclude, -PassThru, -WhatIf/-Confirm and pipeline
input (Get-Service foo | Stop-Service) work as on Windows.
On systemd, unit names are accepted with or without the .service suffix; other managers use
the provided name verbatim.
-DependentServices / -RequiredServices are implemented via systemctl list-dependencies;
other service managers return no results.
Stop-Service -NoWait skips the wait for the stopped state; the stop request itself is unchanged.
-Force is accepted for script compatibility; service managers handle dependency ordering
themselves.
Set-Service, New-Service, Remove-Service, Suspend-Service and Resume-Service remain
Windows-only, per the WG recommendation that capabilities beyond these four cmdlets belong in a
separate module.
Supporting changes
The four cmdlets are exported by the Unix Microsoft.PowerShell.Management module manifest.
The gsv, sasv and spsv aliases are now defined on Unix (they do not conflict with native
commands there).
New neutral resource strings in ServiceResources.resx.
Notes
Tests
New test/powershell/Modules/Microsoft.PowerShell.Management/Service.Tests.ps1 covers systemd
enumeration, name/display-name/wildcard matching, dependencies, the exact service-manager
invocation arguments for systemd, launchctl, sysvinit and OpenRC (including sudo wrapping), the
sudo error paths, and the no-service-manager and enumeration-failure errors. The manager
behaviors are exercised through command shims, so the suite does not require a real systemd
installation.
All 22 tests pass against a local Linux build of this PR (run in WSL Ubuntu as root; the 2
sudo-specific tests skip as root and run on non-root agents):
Total: 22 Passed: 20 Failed: 0 Skipped: 2
The Windows build is unchanged by this PR (#if !UNIX sections compile as before, verified by a
Windows cross-compile of the affected project).
Breaking Changes & Documentation
This is not a breaking change: the Windows behavior of all service cmdlets is unchanged. The Resolve-Path-style documentation for the four cross-platform cmdlets has been updated to
describe the Unix support:
Implements PowerShell#18863 as reviewed by the cmdlets WG:
- New Unix-only Service.cs implementation (#if UNIX) that discovers the
service manager in this order: systemctl (systemd), launchctl (macOS),
service (sysvinit), rc-service (OpenRC), and fails the entire operation
with a terminating error when none is found.
- Start/Stop/Restart wrap the service manager with 'sudo -n' when not
running as root (sudo cannot elevate cmdlets), and report a clear
error when a password would be required.
- Non-zero exit codes surface the command stderr as a non-terminating
ErrorRecord per service, with error IDs mirroring the Windows
implementation.
- Get-Service returns UnixServiceController objects with Name,
DisplayName, Status, StartType, Description, SubState and
UnitFileState; Status/StartType reuse the System.ServiceProcess
enums for pipeline compatibility with Windows. Wildcards,
-DisplayName, -Include/-Exclude and -PassThru behave like Windows.
-DependentServices/-RequiredServices are supported on systemd.
- Stop-Service -NoWait maps to 'systemctl --no-block'; -Force is
accepted for script compatibility (service managers order services
themselves).
- Exports the four cmdlets in the Unix Microsoft.PowerShell.Management
module manifest and enables the gsv/sasv/spsv aliases on Unix.
- Adds Service.Tests.ps1 exercising the real command resolution and
invocation paths against PATH shims for each service manager.
Companion documentation update: MicrosoftDocs/PowerShell-Docs#13383 (documents the Unix behavior, the new UnixServiceController output type, and parameter differences in the 7.7 version folder).
The reason will be displayed to describe this comment to others. Learn more.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds cross-platform service management support by implementing Get-Service, Start-Service, Stop-Service, and Restart-Service on Unix platforms, aligning behavior and error semantics with Windows where practical.
Changes:
Introduces a Unix implementation of service cmdlets with service-manager discovery (systemctl/launchctl/service/rc-service) and sudo wrapping for mutating operations.
Exports the new cmdlets and adds Unix-only aliases (gsv, sasv, spsv).
Adds Pester coverage for Unix behavior via PATH shims and new resource strings for consistent error messaging.
reloading is a systemd ActiveState for a service that is still running, but it currently falls through to Stopped. A Get-Service query during systemctl reload therefore incorrectly reports the live service as stopped and breaks status-based filtering. Map reloading to Running as well.
A systemd disabled unit is still startable manually or through dependencies/socket activation; it is not equivalent to Windows ServiceStartMode.Disabled. Mapping it to Disabled misclassifies ordinary manually-started services in cross-platform StartType filters. Map disabled to Manual, and reserve Disabled for masked/masked-runtime; update the test that currently expects Disabled for foo.
Suppress systemd unit markers when parsing list-units
systemctl list-units can prefix failed/not-found units with a ● marker even with --no-legend. The parser assumes the first token is the unit name, so it discards those rows. Failed transient services disappear entirely, and file-backed ones lose their failure substate/description when recreated from list-unit-files. Add --plain to suppress the marker and cover a failed-unit row in the tests.
Preserve launchd bootstrap domains for service mutations
A non-root macOS process does not necessarily have a gui/<uid> bootstrap domain. In a headless/SSH session, launchctl list can enumerate jobs in user/<uid>, but every mutation here targets the nonexistent GUI domain and fails even with working sudo. Preserve or resolve the service's actual bootstrap domain instead of choosing it solely from root status and UID.
Stop KeepAlive launchd jobs without automatic restart
Sending SIGTERM to a launchd job does not keep it stopped. For a job configured with KeepAlive, launchd starts another process immediately, yet Stop-Service reports success (and -PassThru reports Stopped). Implement launchd lifecycle handling that actually leaves the job stopped and allows a subsequent Start-Service to restore it; test this with a KeepAlive job rather than only asserting the signal arguments.
Capture the process reference before checking or killing it
StopProcessing runs concurrently with the invocation thread, whose finally block sets _currentProcess to null. The repeated field reads can therefore pass the null check and then dereference null in HasExited or Kill; the existing catches do not handle that exception. Capture the process reference once before checking and killing it.
Applying systemd's .service convention to every backend makes exact lookups miss labels such as com.example.service, while wildcard operations can select unintended services through synthesized names. Restrict both suffix removal here and the alternative suffixed match in MatchesServiceName at lines 1688–1690 to systemd.
Use case-sensitive comparisons for Unix service identity
Unix service names can differ only by case, so this lookup can make Stop-Service foo stop Foo instead; case-insensitive identity maps also merge distinct services or omit dependencies. Use ordinal, case-sensitive comparisons for service identity here and in the dictionaries, deduplication, and self-dependency filter at lines 365, 527, 1642, 1993, and 2001.
Resolve explicit systemd names outside the enumeration snapshot
An explicit systemd name is rejected unless it already appears in enumeration. A valid but never-started template instance such as worker@blue.service appears in neither list-units nor list-unit-files (which lists worker@.service, deliberately filtered out here), so Start-Service worker@blue reports not found although systemctl can start it. Resolve exact names directly through systemd, validating LoadState, rather than treating the enumeration snapshot as exhaustive.
Load systemd descriptions before filtering by display name
Services found only in list-unit-files have DisplayName set to their unit name. Only GetOneService refreshes their description, so Get-Service -DisplayName 'Example Worker' cannot find an unloaded service even though Get-Service worker retrieves that display name successfully. Refresh missing metadata before applying the display-name filter (preferably with a batched systemd query).
A successful manager exit does not establish this status. In particular, Stop-Service -NoWait -PassThru always returns Stopped even though --no-block only queues the stop, and a successful start of a oneshot unit can leave it inactive. This also leaves SubState inconsistent with Status. Query the actual service state for PassThru instead of overwriting the snapshot with the requested end state.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Unresolved service-identity bugs can target unintended Unix services, while non-root tests contain guaranteed assertion failures and can invoke the host’s real sudo.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Case-insensitive identity handling and backend-agnostic suffix stripping can stop the wrong service, while unresolved enumeration, state-reporting, and lifecycle defects further compromise service management.
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 0637796: the facade strips the .service suffix only when the manager is systemd; launchd, SysV and OpenRC names are used verbatim, so Stop-Service -Name com.example.service on launchd targets the exact label. There is also no synthetic suffix alternative in wildcard matching anymore - the shared master code matches wildcards against the manager-reported name, and systemd enumeration names are suffix-stripped by the parser.
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 0483d24 and 0637796: the facade now prefers an exact ordinal name match and only falls back to a case-insensitive comparison when no exact match exists, and the parser's unit-file merge and OpenRC deduplication dictionaries compare names ordinally, so distinct units differing only in case are no longer conflated. (The duplicate detection in master's MultipleServiceCommandBase already compared names case-sensitively.)
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged: template instances that were never loaded (for example openvpn@client.service) appear in neither list-units nor list-unit-files (only the openvpn@.service template is listed, and template instances are deliberately not synthesized), so an exact-name lookup reports NoServiceFoundForGivenName. Adding a targeted systemctl show <unit> existence check for enumeration misses is a possible follow-up; it is out of scope for this working-group PR, which focuses on basic Get/Start/Stop/Restart parity.
The reason will be displayed to describe this comment to others. Learn more.
True: systemctl list-unit-files does not carry descriptions, so for units that were never loaded the facade exposes the unit name as DisplayName, and -DisplayName matching by description only works for loaded units (which come from list-units with a description). Populating descriptions for never-loaded units would require a metadata query (for example systemctl show) per unit; that cost is not justified for the working-group scope of this PR, and it is noted as a possible follow-up.
The reason will be displayed to describe this comment to others. Learn more.
This code path no longer exists in the PR: the parallel-implementation files (including ServiceBaseCommand.cs with the currentProcess field and the custom StopProcessing) were removed in the rework, and master's ServiceBaseCommand is reused unchanged. Resolving as obsolete.
The reason will be displayed to describe this comment to others. Learn more.
The facade enumerates with launchctl list in the caller's context and derives the mutation target from the same identity (the system domain for root, gui/<uid> otherwise), so enumeration and operations always address the same domain. Per-job domain discovery would require parsing launchctl print output for every job; that is beyond the working-group scope of this PR and is noted as a follow-up.
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged. Switching stop to bootout would prevent KeepAlive relaunches, but the facade only knows the job's label from launchctl list; re-starting a booted-out job requires bootstrap with the job's plist path, which is not tracked. The current label-based lifecycle (kickstart / kill SIGTERM / kickstart -k) operates on exactly the jobs that enumeration reported and is straightforwardly sudo-wrappable. Discovering each job's plist path (for example from launchctl print) so that stop can use bootout and start can use bootstrap is a reasonable follow-up beyond the working-group scope of this PR.
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 0637796: --plain was added to the list-units invocation so failed units are no longer prefixed with a marker, and the test now enumerates a failed unit and asserts its name, status and sub-state.
The reason will be displayed to describe this comment to others. Learn more.
The Unix facade deliberately keeps Status as the state reported by the last enumeration or operation: mutating operations update it from the manager's exit status, and when waiting is requested the shared cmdlet code (DoWaitForStatus) confirms the transition. With -NoWait the caller explicitly opts out of waiting, so a point-in-time snapshot is the documented behavior; SubState likewise reflects the last enumeration. Note also that the referenced file (ServiceOperationBaseCommand.cs) no longer exists in this PR - master's implementation is reused unchanged and the Unix behavior lives in UnixServiceController.cs. Querying the live state for -NoWait -PassThru would add manager invocations and special-case oneshot units, so I would like to keep that as a possible follow-up.
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 0483d24 and 0637796: disabled now maps to ServiceStartMode.Manual (a disabled unit can still be started manually), masked/masked-runtime map to ServiceStartMode.Disabled, and the test expectation in Service.Tests.ps1 was updated accordingly.
Compile the master Service.cs cmdlet implementation on Unix as well and
map the unqualified ServiceController name to a Unix service-manager
facade (UnixServiceController) via a using alias, so Get/Start/Stop/
Restart-Service work on Unix with only a few platform-specific branches:
- Service.cs: top '#if !UNIX' replaced by Unix aliases; the five
Windows-SCM-only cmdlets (Set/New/Remove/Suspend/Resume-Service) and
the SCM property/native sections stay guarded with '#if !UNIX'.
- Get-Service ProcessRecord gains a Unix branch that honors
-DependentServices/-RequiredServices and reports NoServiceManagerFound.
- DoStartService/DoStopService surface sudo-unavailable failures as
SudoRequired errors on Unix.
- UnixServiceNativeMethods gains the ERROR_SERVICE_* constants that the
shared code compares against, aliased as NativeMethods on Unix.
…ching
- 'systemctl list-units' gains --plain so failed units (prefixed with a
marker without it) are still enumerated.
- systemd 'disabled' maps to ServiceStartMode.Manual (it can still be
started manually); ServiceStartMode.Disabled is reserved for masked
unit files, matching the Windows semantics of these values.
- The Unix service controller prefers an exact native name match and
only falls back to the case-insensitive lookup when there is no exact
match, so distinct units differing in case are not conflated.
- The '.service' suffix is only stripped for the systemd manager; other
managers use the provided name verbatim.
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Note
This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Summary
This PR implements
Get-Service,Start-Service,Stop-Service, andRestart-Serviceon Unix(Linux and macOS), based on issue
Restart-Serviceshould callsystemctl restarton Linux OSes with SystemD present. #18863These cmdlets previously did not exist on Unix at all, which made cross-platform service
management impossible.
The implementation follows the cmdlets WG recommendation recorded in
#18863 (comment):
Rather than duplicating the cmdlets, master's
Service.csis now also compiled on Unix. Ausing ServiceController = Microsoft.PowerShell.Commands.UnixServiceControlleralias maps theunqualified
ServiceControllername to a new Unix facade (UnixServiceController.cs) so theshared cmdlet implementation works unchanged. Only a few places are platform-specific: the five
Windows-SCM-only cmdlets (
Set-Service,New-Service,Remove-Service,Suspend-Service,Resume-Service) and the SCM property/native sections stay guarded with#if !UNIX,Get-Service'sProcessRecordhas a small Unix branch, andDoStartService/DoStopServicesurface sudo-unavailable failures as
SudoRequirederrors.PR Context
Fixes #18863
Service manager fallback chain
The cmdlets resolve the service manager through
PATHin this order and fail the operation with aterminating error when none is found:
systemctllaunchctlservicerc-serviceElevation
Mutating operations (
Start-Service,Stop-Service,Restart-Service) are wrapped withsudo -nwhen the process is not running as root, so they never hang on a password prompt. Ifsudo would require a password, the cmdlet writes a non-terminating error with guidance to run
pwshas root or configure passwordless sudo.Get-Serviceworks unprivileged.Error semantics
Non-zero exit codes surface the command's stderr as a non-terminating
ErrorRecordper service(exception type
ServiceCommandException, mirroring Windows), with error IDs matching the Windowscmdlets (
CouldNotStartService,CouldNotStopService), plusSudoRequiredwhen elevation isunavailable. A missing service manager is reported as a terminating
NoServiceManagerFounderror,and service-manager enumeration failures terminate the cmdlet.
Output type
Get-Servicereturns the newMicrosoft.PowerShell.Commands.UnixServiceControllertype withName,DisplayName,Status,StartType,Description,SubState, andUnitFileState.StatusandStartTypereuse theSystem.ServiceProcess.ServiceControllerStatus/ServiceStartModeenums (the managed assembly ships cross-platform; only the Windowsimplementation is RID-specific), so
Where-Object Status -eq Runningand other pipelines behaveidentically on both platforms. Objects carry a
DefaultDisplayPropertySetso the default tableview matches Windows.
Parameter behavior:
-DisplayName,-Include/-Exclude,-PassThru,-WhatIf/-Confirmand pipelineinput (
Get-Service foo | Stop-Service) work as on Windows..servicesuffix; other managers usethe provided name verbatim.
-DependentServices/-RequiredServicesare implemented viasystemctl list-dependencies;other service managers return no results.
Stop-Service -NoWaitskips the wait for the stopped state; the stop request itself is unchanged.-Forceis accepted for script compatibility; service managers handle dependency orderingthemselves.
Set-Service,New-Service,Remove-Service,Suspend-ServiceandResume-ServiceremainWindows-only, per the WG recommendation that capabilities beyond these four cmdlets belong in a
separate module.
Supporting changes
Microsoft.PowerShell.Managementmodule manifest.gsv,sasvandspsvaliases are now defined on Unix (they do not conflict with nativecommands there).
ServiceResources.resx.Notes
Tests
New
test/powershell/Modules/Microsoft.PowerShell.Management/Service.Tests.ps1covers systemdenumeration, name/display-name/wildcard matching, dependencies, the exact service-manager
invocation arguments for systemd, launchctl, sysvinit and OpenRC (including sudo wrapping), the
sudo error paths, and the no-service-manager and enumeration-failure errors. The manager
behaviors are exercised through command shims, so the suite does not require a real systemd
installation.
All 22 tests pass against a local Linux build of this PR (run in WSL Ubuntu as root; the 2
sudo-specific tests skip as root and run on non-root agents):
The Windows build is unchanged by this PR (
#if !UNIXsections compile as before, verified by aWindows cross-compile of the affected project).
Breaking Changes & Documentation
This is not a breaking change: the Windows behavior of all service cmdlets is unchanged. The
Resolve-Path-style documentation for the four cross-platform cmdlets has been updated todescribe the Unix support:
PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright header