GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — Add Get-Service, Start-Service, Stop-Service and Restart-Service cmdlets on Unix by soroshsabz · Pull Request #28385 · PowerShell/PowerShell Conversation
|
| Priority | Manager | Covered init systems |
|---|---|---|
| 1 | systemctl |
systemd |
| 2 | launchctl |
launchd (macOS) |
| 3 | service |
sysvinit and compatible |
| 4 | rc-service |
OpenRC |
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.
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.
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:
-DisplayName, -Include/-Exclude, -PassThru, -WhatIf/-Confirm and pipelineGet-Service foo | Stop-Service) work as on Windows..service suffix; other managers use-DependentServices / -RequiredServices are implemented via systemctl list-dependencies;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 orderingSet-Service, New-Service, Remove-Service, Suspend-Service and Resume-Service remainMicrosoft.PowerShell.Management module manifest.gsv, sasv and spsv aliases are now defined on Unix (they do not conflict with nativeServiceResources.resx.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).
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:
.h, .cpp, .cs, .ps1 and .psm1 files have the correct copyright header
Sorry, something went wrong.
There was an error while loading. Please reload this page.
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.
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
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). |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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.
service) and OpenRC (rc-service), the argument order is typically… · Newelevate is true, the process is not elevated, and sudo is not present (`SudoPath is… · NewAddDefaultDisplayProperties unconditionally adds a PSStandardMember member set. If this helper… · NewAdds 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:
gsv, sasv, spsv).| File | Description |
|---|---|
| test/powershell/Modules/Microsoft.PowerShell.Management/Service.Tests.ps1 | Adds Pester tests for Unix service cmdlets using service-manager and sudo shims. |
| src/System.Management.Automation/engine/InitialSessionState.cs | Adds Unix-only aliases for the new service cmdlets. |
| src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 | Exports the service cmdlets from the Unix Management module. |
| src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx | Adds new resource strings for Unix service-manager and sudo/error scenarios. |
| src/Microsoft.PowerShell.Commands.Management/commands/management/ServiceUnix.cs | Implements Unix service cmdlets, parsing, invocation, sudo wrapping, and output shaping. |
Review effort: Lite
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
60c00e1 to
d52bdec
Compare
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
OpenRC enumeration invokes rc-service --all instead of rc-status --all, causing enumeration to fail before named service operations can run.
In code that hasn't changed since last review
src/Microsoft.PowerShell.Commands.Management/commands/management/ServiceUnix.cs:623
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.
src/Microsoft.PowerShell.Commands.Management/commands/management/ServiceUnix.cs:640
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.
src/Microsoft.PowerShell.Commands.Management/commands/management/ServiceUnix.cs:755
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.
src/Microsoft.PowerShell.Commands.Management/commands/management/ServiceUnix.cs:841
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.
src/Microsoft.PowerShell.Commands.Management/commands/management/ServiceUnix.cs:852
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.
src/Microsoft.PowerShell.Commands.Management/commands/management/ServiceUnix.cs:1165
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.
src/Microsoft.PowerShell.Commands.Management/commands/management/ServiceUnix.cs:1449
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.
src/Microsoft.PowerShell.Commands.Management/commands/management/ServiceUnix.cs:1452
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.
src/Microsoft.PowerShell.Commands.Management/commands/management/ServiceUnix.cs:1461
An explicit systemd name is rejected unless it already appears in enumeration. A valid but never-started template instance such as [email protected] appears in neither list-units nor list-unit-files (which lists [email protected], 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.
src/Microsoft.PowerShell.Commands.Management/commands/management/ServiceUnix.cs:1567
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).
src/Microsoft.PowerShell.Commands.Management/commands/management/ServiceUnix.cs:1819
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.
service) and OpenRC (rc-service), the argument order is typically…AddDefaultDisplayProperties unconditionally adds a PSStandardMember member set. If this helper…elevate is true, the process is not elevated, and sudo is not present (`SudoPath is…Review effort: Balanced
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
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.
Review effort: Balanced
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
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.
Review effort: Balanced
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| { | ||
| Dbg.Assert(!WildcardPattern.ContainsWildcardCharacters(nameOfService), "Caller should verify that nameOfService doesn't contain wildcard characters"); | ||
|
|
||
| string name = UnixServiceParser.RemoveServiceSuffix(nameOfService.Trim()); |
There was a problem hiding this comment.
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.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| string name = UnixServiceParser.RemoveServiceSuffix(nameOfService.Trim()); | ||
| foreach (UnixServiceController service in AllServices) | ||
| { | ||
| if (string.Equals(service.Name, name, StringComparison.OrdinalIgnoreCase)) |
There was a problem hiding this comment.
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.)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| } | ||
| } | ||
|
|
||
| return null; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged: template instances that were never loaded (for example [email protected]) appear in neither list-units nor list-unit-files (only the [email protected] 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.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| bool found = false; | ||
| foreach (UnixServiceController service in AllServices) | ||
| { | ||
| if (!wildcard.IsMatch(service.DisplayName)) |
There was a problem hiding this comment.
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.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| if (currentProcess is not null && !currentProcess.HasExited) | ||
| { | ||
| currentProcess.Kill(entireProcessTree: true); | ||
| } |
There was a problem hiding this comment.
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.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| string domain = manager.IsElevated | ||
| ? "system" | ||
| : $"gui/{UnixServiceNativeMethods.GetUserId()}"; | ||
| string unitPath = domain + "/" + service.Name; |
There was a problem hiding this comment.
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.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| return action switch | ||
| { | ||
| ServiceAction.Start => new List<string> { "kickstart", unitPath }, | ||
| ServiceAction.Stop => new List<string> { "kill", "SIGTERM", unitPath }, |
There was a problem hiding this comment.
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.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| private List<UnixServiceController> EnumerateSystemdServices() | ||
| { | ||
| ServiceCommandResult units = RunServiceManagerCommand( | ||
| new[] { "list-units", "--type=service", "--all", "--no-legend", "--no-pager" }, |
There was a problem hiding this comment.
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.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| /// <returns>The service with the expected status.</returns> | ||
| internal static UnixServiceController ApplyExpectedStatus(UnixServiceController service, ServiceControllerStatus status) | ||
| { | ||
| service.Status = status; |
There was a problem hiding this comment.
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.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| return unitFileState switch | ||
| { | ||
| "enabled" or "enabled-runtime" or "generated" or "generated-runtime" or "transient" => ServiceStartMode.Automatic, | ||
| "disabled" or "masked" => ServiceStartMode.Disabled, |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
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.
There was a problem hiding this comment.
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.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
At least 1 approving review is required to merge this pull request.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
Restart-Service should call systemctl restart on Linux OSes with SystemD present.
There was an error while loading. Please reload this page.