diff --git a/examples/HelloService/HelloService.ps1 b/examples/HelloService/HelloService.ps1 index 904bec2ae..5ed6ccf76 100644 --- a/examples/HelloService/HelloService.ps1 +++ b/examples/HelloService/HelloService.ps1 @@ -153,41 +153,33 @@ catch { if ( $Register.IsPresent) { - Register-PodeService -Name $ServiceName -ParameterString "-Port $Port" -Password $Password -Agent:(!$Daemon.IsPresent) - exit + return Register-PodeService -Name $ServiceName -ParameterString "-Port $Port" -Password $Password -Agent:(!$Daemon.IsPresent) } if ( $Unregister.IsPresent) { - Unregister-PodeService -Name $ServiceName -Force:$Force - exit + return Unregister-PodeService -Name $ServiceName -Force:$Force } if ($Start.IsPresent) { - Start-PodeService -Name $ServiceName - exit + return Start-PodeService -Name $ServiceName } if ($Stop.IsPresent) { - Stop-PodeService -Name $ServiceName - exit + return Stop-PodeService -Name $ServiceName } if ($Suspend.IsPresent) { - Suspend-PodeService -Name $ServiceName - exit + return Suspend-PodeService -Name $ServiceName } if ($Resume.IsPresent) { - Resume-PodeService -Name $ServiceName - exit + return Resume-PodeService -Name $ServiceName } if ($Query.IsPresent) { - Get-PodeService -Name $ServiceName - exit + return Get-PodeService -Name $ServiceName } if ($Restart.IsPresent) { - Restart-PodeService -Name $ServiceName - exit + return Restart-PodeService -Name $ServiceName } # Start the Pode server diff --git a/src/Pode.psm1 b/src/Pode.psm1 index 3e2d95553..53622260f 100644 --- a/src/Pode.psm1 +++ b/src/Pode.psm1 @@ -135,6 +135,15 @@ try { Export-ModuleMember -Function ($funcs.Name) } } + + # Define Properties Display + if (!(Get-TypeData -TypeName 'PodeService')) { + $TypeData = @{ + TypeName = 'PodeService' + DefaultDisplayPropertySet = 'Name', 'Status', 'Pid' + } + Update-TypeData @TypeData + } } catch { throw ("Failed to load the Pode module. $_") diff --git a/src/Private/Service.ps1 b/src/Private/Service.ps1 index 195b14fd6..0f3e76299 100644 --- a/src/Private/Service.ps1 +++ b/src/Private/Service.ps1 @@ -312,14 +312,14 @@ function Register-PodeMacService { } else { $plistPath = "/Library/LaunchDaemons/$($nameService).plist" - sudo cp $tempFile $plistPath + & sudo cp $tempFile $plistPath #set rw r r permissions - sudo chmod 644 $plistPath + & sudo chmod 644 $plistPath - sudo chown root:wheel $plistPath + & sudo chown root:wheel $plistPath # Load the plist with launchctl - sudo launchctl load $plistPath + & sudo launchctl load $plistPath } @@ -455,7 +455,7 @@ WantedBy=multi-user.target Write-Verbose -Message "Service '$nameService' ExecStart : $execStart)." - sudo cp $tempFile "/etc/systemd/system/$nameService" + & sudo cp $tempFile "/etc/systemd/system/$nameService" Remove-Item -path $tempFile -ErrorAction SilentlyContinue @@ -652,7 +652,7 @@ function Confirm-PodeAdminPrivilege { } # Message for non-Windows (Linux/macOS) - Write-PodeHost 'Insufficient privileges. This script must be run as root or with sudo permissions to continue.' -ForegroundColor Red + Write-PodeHost "Insufficient privileges. This script must be run as root or with 'sudo' permissions to continue." -ForegroundColor Red exit } } @@ -745,7 +745,7 @@ function Disable-PodeLinuxService { $Name ) $nameService = Get-PodeRealServiceName -Name $Name - $systemctlDisable = sudo systemctl disable $nameService 2>&1 + $systemctlDisable = & sudo systemctl disable $nameService 2>&1 $success = $LASTEXITCODE -eq 0 Write-Verbose -Message ($systemctlDisable -join '`n') return $success @@ -775,7 +775,7 @@ function Enable-PodeLinuxService { [string] $Name ) - $systemctlEnable = sudo systemctl enable $Name 2>&1 + $systemctlEnable = & sudo systemctl enable $Name 2>&1 $success = $LASTEXITCODE -eq 0 Write-Verbose -Message ($systemctlEnable -join '`n') return $success @@ -807,7 +807,7 @@ function Stop-PodeLinuxService { ) $nameService = Get-PodeRealServiceName -Name $Name #return (Send-PodeServiceSignal -Name $Name -Signal SIGTERM) - $serviceStopInfo = sudo systemctl stop $nameService 2>&1 + $serviceStopInfo = & sudo systemctl stop $nameService 2>&1 $success = $LASTEXITCODE -eq 0 Write-Verbose -Message ($serviceStopInfo -join "`n") return $success @@ -838,7 +838,7 @@ function Start-PodeLinuxService { $Name ) $nameService = Get-PodeRealServiceName -Name $Name - $serviceStartInfo = sudo systemctl start $nameService 2>&1 + $serviceStartInfo = & sudo systemctl start $nameService 2>&1 $success = $LASTEXITCODE -eq 0 Write-Verbose -Message ($serviceStartInfo -join "`n") return $success @@ -879,10 +879,10 @@ function Test-PodeMacOsServiceIsRegistered { $sudo = !(Test-Path -Path "$($HOME)/Library/LaunchAgents/$nameService.plist" -PathType Leaf) } if ($sudo) { - $systemctlStatus = sudo launchctl list $nameService 2>&1 + $systemctlStatus = & sudo launchctl list $nameService 2>&1 } else { - $systemctlStatus = launchctl list $nameService 2>&1 + $systemctlStatus = & launchctl list $nameService 2>&1 } $isRegistered = ($LASTEXITCODE -eq 0) Write-Verbose -Message ($systemctlStatus -join '`n') @@ -998,10 +998,10 @@ function Test-PodeMacOsServiceIsActive { $nameService = Get-PodeRealServiceName -Name $Name $sudo = !(Test-Path -Path "$($HOME)/Library/LaunchAgents/$nameService.plist" -PathType Leaf) if ($sudo) { - $serviceInfo = sudo launchctl list $nameService + $serviceInfo = & sudo launchctl list $nameService } else { - $serviceInfo = launchctl list $nameService + $serviceInfo = & launchctl list $nameService } $isActive = $serviceInfo -match '"PID" = (\d+);' Write-Verbose -Message ($serviceInfo -join "`n") @@ -1035,10 +1035,10 @@ function Get-PodeMacOsServicePid { $nameService = Get-PodeRealServiceName -Name $Name $sudo = !(Test-Path -Path "$($HOME)/Library/LaunchAgents/$nameService.plist" -PathType Leaf) if ($sudo) { - $serviceInfo = sudo launchctl list $nameService + $serviceInfo = & sudo launchctl list $nameService } else { - $serviceInfo = launchctl list $nameService + $serviceInfo = & launchctl list $nameService } $pidString = $serviceInfo -match '"PID" = (\d+);' Write-Verbose -Message ($serviceInfo -join "`n") @@ -1073,10 +1073,10 @@ function Disable-PodeMacOsService { $nameService = Get-PodeRealServiceName -Name $Name $sudo = !(Test-Path -Path "$($HOME)/Library/LaunchAgents/$($nameService).plist" -PathType Leaf) if ($sudo) { - $systemctlDisable = sudo launchctl unload "/Library/LaunchDaemons/$nameService.plist" 2>&1 + $systemctlDisable = & sudo launchctl unload "/Library/LaunchDaemons/$nameService.plist" 2>&1 } else { - $systemctlDisable = launchctl unload "$HOME/Library/LaunchAgents/$nameService.plist" 2>&1 + $systemctlDisable = & launchctl unload "$HOME/Library/LaunchAgents/$nameService.plist" 2>&1 } $success = $LASTEXITCODE -eq 0 Write-Verbose -Message ($systemctlDisable -join '`n') @@ -1140,10 +1140,10 @@ function Start-PodeMacOsService { $nameService = Get-PodeRealServiceName -Name $Name $sudo = !(Test-Path -Path "$($HOME)/Library/LaunchAgents/$($nameService).plist" -PathType Leaf) if ($sudo) { - $serviceStartInfo = sudo launchctl start $nameService 2>&1 + $serviceStartInfo = & sudo launchctl start $nameService 2>&1 } else { - $serviceStartInfo = launchctl start $nameService 2>&1 + $serviceStartInfo = & launchctl start $nameService 2>&1 } $success = $LASTEXITCODE -eq 0 Write-Verbose -Message ($serviceStartInfo -join "`n") @@ -1227,10 +1227,10 @@ function Send-PodeServiceSignal { # Send the signal based on the privilege level if ($svc.Sudo) { - sudo /bin/kill -$($level) $svc.Pid + & sudo /bin/kill -$($level) $svc.Pid } else { - /bin/kill -$($level) $svc.Pid + & /bin/kill -$($level) $svc.Pid } # Check the exit code to determine if the signal was sent successfully @@ -1360,6 +1360,7 @@ function Wait-PodeServiceStatus { - Sudo: A boolean indicating whether elevated privileges are required. .NOTES + - Possible states: Running,Stopped,Suspended,Starting,Stopping,Pausing,Resuming,Unknown - Requires administrative/root privileges to access service information on Linux and macOS. - Platform-specific behaviors: - **Windows**: Retrieves service information via the `Win32_Service` class. @@ -1394,12 +1395,15 @@ function Get-PodeServiceStatus { 'ContinuePending' { $status = 'Resuming' } default { $status = 'Unknown' } } - return @{ - Name = $Name - Status = $status - Pid = $service.ProcessId - Sudo = $true + return [PSCustomObject]@{ + PsTypeName = 'PodeService' + Name = $Name + Status = $status + Pid = $service.ProcessId + Sudo = $true + PathName = $service.PathName } + } else { Write-Verbose -Message "Service '$Name' not found." @@ -1449,11 +1453,13 @@ function Get-PodeServiceStatus { $status = 'Stopped' } } - return @{ - Name = $Name - Status = $status - Pid = $servicePid - Sudo = $true + return [PSCustomObject]@{ + PsTypeName = 'PodeService' + Name = $Name + Status = $status + Pid = $servicePid + Sudo = $true + PathName = "/etc/systemd/system/$nameService" } } else { @@ -1476,32 +1482,29 @@ function Get-PodeServiceStatus { $sudo = !(Test-Path -Path "$($HOME)/Library/LaunchAgents/$($nameService).plist" -PathType Leaf) - # Check if the service has a PID entry - if ($servicePid -ne 0) { - if ($sudo) { - $stateFilePath = "/Library/LaunchDaemons/PodeMonitor/$servicePid.state" - } - else { - $stateFilePath = "$($HOME)/Library/LaunchAgents/PodeMonitor/$servicePid.state" - } - if (Test-Path -Path $stateFilePath) { - $status = Get-Content -Path $stateFilePath -Raw - $status = $status.Substring(0, 1).ToUpper() + $status.Substring(1) - } - return @{ - Name = $Name - Status = $status - Pid = $servicePid - Sudo = $sudo - } + if ($sudo) { + $stateFilePath = "/Library/LaunchDaemons/PodeMonitor/$servicePid.state" + $plistPath="/Library/LaunchDaemons/$($nameService).plist" } else { - return @{ - Name = $Name - Status = 'Stopped' - Pid = 0 - Sudo = $sudo - } + $stateFilePath = "$($HOME)/Library/LaunchAgents/PodeMonitor/$servicePid.state" + $plistPath="$($HOME)/Library/LaunchAgents/$($nameService).plist" + } + if (Test-Path -Path $stateFilePath) { + $status = Get-Content -Path $stateFilePath -Raw + $status = $status.Substring(0, 1).ToUpper() + $status.Substring(1) + } + else { + $status = 'Stopped' + } + + return [PSCustomObject]@{ + PsTypeName = 'PodeService' + Name = $Name + Status = $status + Pid = $servicePid + Sudo = $true + PathName = $plistPath } } else { diff --git a/src/Public/Service.ps1 b/src/Public/Service.ps1 index a5ab332b5..886819399 100644 --- a/src/Public/Service.ps1 +++ b/src/Public/Service.ps1 @@ -307,82 +307,80 @@ function Register-PodeService { - macOS: `sudo launchctl start`. - Errors and logs are captured for debugging purposes. #> - function Start-PodeService { [CmdletBinding(DefaultParameterSetName = 'Default')] [OutputType([bool])] param( [Parameter(Mandatory = $true)] - [string] - $Name, + [string] $Name, [Parameter(Mandatory = $true, ParameterSetName = 'Async')] - [switch] - $Async, + [switch] $Async, [Parameter(Mandatory = $false, ParameterSetName = 'Async')] - [int] - $Timeout = 10 + [ValidateRange(1, 300)] + [int] $Timeout = 10 ) - # Ensure the script is running with the necessary administrative/root privileges. - # Exits the script if the current user lacks the required privileges. - Confirm-PodeAdminPrivilege - try { + # Ensure administrative/root privileges + Confirm-PodeAdminPrivilege + # Get the service status $service = Get-PodeServiceStatus -Name $Name if (!$service) { - # Service is not registered throw ($PodeLocale.serviceIsNotRegisteredException -f $Name) } - if ($service.Status -eq 'Running') { - Write-Verbose -Message "Service '$Name' is already Running." - return $true - } - if ($service.Status -ne 'Stopped') { - Write-Verbose -Message "Service '$Name' is not Stopped." - return $false - } - if (Test-PodeIsWindows) { - if ( Invoke-PodeWinElevatedCommand -Command 'sc.exe' -Arguments "start '$Name'") { - if ($Async) { - return $true - } - else { - return Wait-PodeServiceStatus -Name $Name -Status Running -Timeout $Timeout - } - } + Write-Verbose -Message "Service '$Name' current state: $($service.Status)." - throw ($PodeLocale.serviceCommandFailedException -f 'sc.exe start {0}', $Name) + # Handle the current service state + switch ($service.Status) { + 'Running' { + Write-Verbose -Message "Service '$Name' is already running." + return $true + } + 'Suspended' { + Write-Verbose -Message "Service '$Name' is suspended. Cannot start a suspended service." + return $false + } + 'Stopped' { + Write-Verbose -Message "Service '$Name' is currently stopped. Attempting to start..." + } + { $_ -eq 'Starting' -or $_ -eq 'Stopping' -or $_ -eq 'Pausing' -or $_ -eq 'Resuming' } { + Write-Verbose -Message "Service '$Name' is transitioning state ($($service.Status)). Cannot start at this time." + return $false + } + default { + Write-Verbose -Message "Service '$Name' is in an unknown state ($($service.Status))." + return $false + } + } + # Start the service based on the OS + $serviceStarted = $false + if (Test-PodeIsWindows) { + $serviceStarted = Invoke-PodeWinElevatedCommand -Command 'sc.exe' -Arguments "start '$Name'" } elseif ($IsLinux) { - # Start the service - if ((Start-PodeLinuxService -Name $Name)) { - if ($Async) { - return $true - } - else { - return Wait-PodeServiceStatus -Name $Name -Status Running -Timeout $Timeout - } - } - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f 'sudo systemctl start', $Name) - + $serviceStarted = Start-PodeLinuxService -Name $Name } elseif ($IsMacOS) { - # Start the service - if ((Start-PodeMacOsService -Name $Name)) { - if ($Async) { - return $true - } - else { - return Wait-PodeServiceStatus -Name $Name -Status Running -Timeout $Timeout - } - } - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f 'sudo systemctl start', $Name) + $serviceStarted = Start-PodeMacOsService -Name $Name + } + + # Check if the service start command failed + if (!$serviceStarted) { + throw ($PodeLocale.serviceCommandFailedException -f 'Start', $Name) + } + + # Handle async or wait for start + if ($Async) { + Write-Verbose -Message "Async mode: Service start command issued for '$Name'." + return $true + } + else { + Write-Verbose -Message "Waiting for service '$Name' to start (timeout: $Timeout seconds)..." + return Wait-PodeServiceStatus -Name $Name -Status Running -Timeout $Timeout } } catch { @@ -390,7 +388,6 @@ function Start-PodeService { Write-Error -Exception $_.Exception return $false } - return $true } <# @@ -448,76 +445,75 @@ function Stop-PodeService { $Async, [Parameter(Mandatory = $false, ParameterSetName = 'Async')] + [ValidateRange(1, 300)] [int] $Timeout = 10 ) try { - # Ensure the script is running with the necessary administrative/root privileges. - # Exits the script if the current user lacks the required privileges. + # Ensure administrative/root privileges Confirm-PodeAdminPrivilege - if (Test-PodeIsWindows) { + # Get the service status + $service = Get-PodeServiceStatus -Name $Name + if (!$service) { + throw ($PodeLocale.serviceIsNotRegisteredException -f $Name) + } - $service = Get-Service -Name $Name -ErrorAction SilentlyContinue - if ($service) { - # Check if the service is running - if ($service.Status -eq 'Running' -or $service.Status -eq 'Paused') { - if ( Invoke-PodeWinElevatedCommand -Command 'sc.exe' -Arguments "stop '$Name'") { - if ($Async) { - return $true - } - else { - return Wait-PodeServiceStatus -Name $Name -Status Stopped -Timeout $Timeout - } - } - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f 'sc.exe stop', $Name) - } - else { - Write-Verbose -Message "Service '$Name' is not running." - } + Write-Verbose -Message "Service '$Name' current state: $($service.Status)." + + # Handle service states + switch ($service.Status) { + 'Stopped' { + Write-Verbose -Message "Service '$Name' is already stopped." + return $true } - else { - # Service is not registered - throw ($PodeLocale.serviceIsNotRegisteredException -f $Name) + { $_ -eq 'Running' -or $_ -eq 'Suspended' } { + Write-Verbose -Message "Service '$Name' is currently $($service.Status). Attempting to stop..." } - } - elseif ($IsLinux) { - #Stop the service - if (( Stop-PodeLinuxService -Name $Name)) { - if ($Async) { - return $true - } - else { - return Wait-PodeServiceStatus -Name $Name -Status Stopped -Timeout $Timeout - } + { $_ -eq 'Starting' -or $_ -eq 'Stopping' -or $_ -eq 'Pausing' -or $_ -eq 'Resuming' } { + Write-Verbose -Message "Service '$Name' is transitioning state ($($service.Status)). Cannot stop at this time." + return $false + } + default { + Write-Verbose -Message "Service '$Name' is in an unknown state ($($service.Status))." + return $false } + } - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f 'sudo systemctl stop', $Name) + # Stop the service + $serviceStopped = $false + if (Test-PodeIsWindows) { + $serviceStopped = Invoke-PodeWinElevatedCommand -Command 'sc.exe' -Arguments "stop '$Name'" + } + elseif ($IsLinux) { + $serviceStopped = Stop-PodeLinuxService -Name $Name } elseif ($IsMacOS) { - if ((Stop-PodeMacOsService $Name)) { - if ($Async) { - return $true - } - else { - return Wait-PodeServiceStatus -Name $Name -Status Stopped -Timeout $Timeout - } - } - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f 'launchctl stop', $Name) + $serviceStopped = Stop-PodeMacOsService -Name $Name } + if (!$serviceStopped) { + throw ($PodeLocale.serviceCommandFailedException -f 'Stop', $Name) + } + + # Handle async or wait for stop + if ($Async) { + Write-Verbose -Message "Async mode: Service stop command issued for '$Name'." + return $true + } + else { + Write-Verbose -Message "Waiting for service '$Name' to stop (timeout: $Timeout seconds)..." + return Wait-PodeServiceStatus -Name $Name -Status Stopped -Timeout $Timeout + } } catch { $_ | Write-PodeErrorLog Write-Error -Exception $_.Exception return $false } - return $true } + <# .SYNOPSIS Suspend a specified service on Windows systems. @@ -555,74 +551,72 @@ function Suspend-PodeService { [OutputType([bool])] param( [Parameter(Mandatory = $true)] - [string] - $Name, + [string] $Name, [Parameter(Mandatory = $true, ParameterSetName = 'Async')] - [switch] - $Async, + [switch] $Async, [Parameter(Mandatory = $false, ParameterSetName = 'Async')] - [int] - $Timeout = 10 + [ValidateRange(1, 300)] + [int] $Timeout = 10 ) - # Ensure the script is running with the necessary administrative/root privileges. - # Exits the script if the current user lacks the required privileges. - Confirm-PodeAdminPrivilege - try { + # Ensure administrative/root privileges + Confirm-PodeAdminPrivilege + # Get the service status $service = Get-PodeServiceStatus -Name $Name if (!$service) { - # Service is not registered throw ($PodeLocale.serviceIsNotRegisteredException -f $Name) } - if ($service.Status -eq 'Suspended') { - Write-Verbose -Message "Service '$Name' is already suspended." - return $true - } - if ($service.Status -ne 'Running') { - Write-Verbose -Message "Service '$Name' is not running." - return $false - } - if (Test-PodeIsWindows) { - if (( Invoke-PodeWinElevatedCommand -Command 'sc.exe' -Arguments "pause '$Name'")) { - if ($Async) { - return $true - } - else { - return Wait-PodeServiceStatus -Name $Name -Status Suspended -Timeout $Timeout - } + + Write-Verbose -Message "Service '$Name' current state: $($service.Status)." + + # Handle the current service state + switch ($service.Status) { + 'Suspended' { + Write-Verbose -Message "Service '$Name' is already suspended." + return $true } + 'Running' { + Write-Verbose -Message "Service '$Name' is currently running. Attempting to suspend..." + } + 'Stopped' { + Write-Verbose -Message "Service '$Name' is stopped. Cannot suspend a stopped service." + return $false + } + { $_ -eq 'Starting' -or $_ -eq 'Stopping' -or $_ -eq 'Pausing' -or $_ -eq 'Resuming' } { + Write-Verbose -Message "Service '$Name' is transitioning state ($($service.Status)). Cannot suspend at this time." + return $false + } + default { + Write-Verbose -Message "Service '$Name' is in an unknown state ($($service.Status))." + return $false + } + } - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f 'sc.exe pause', $Name) + # Suspend the service based on the OS + $serviceSuspended = $false + if (Test-PodeIsWindows) { + $serviceSuspended = Invoke-PodeWinElevatedCommand -Command 'sc.exe' -Arguments "pause '$Name'" + } + elseif ($IsLinux -or $IsMacOS) { + $serviceSuspended = ( Send-PodeServiceSignal -Name $Name -Signal 'SIGTSTP') } - elseif ($IsLinux) { - if (( Send-PodeServiceSignal -Name $Name -Signal 'SIGTSTP')) { - if ($Async) { - return $true - } - else { - return Wait-PodeServiceStatus -Name $Name -Status Suspended -Timeout $Timeout - } - } - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f ' sudo /bin/kill -SIGTSTP', $Name) + # Check if the service suspend command failed + if (!$serviceSuspended) { + throw ($PodeLocale.serviceCommandFailedException -f 'Suspend', $Name) } - elseif ($IsMacOS) { - if (( Send-PodeServiceSignal -Name $Name -Signal 'SIGTSTP')) { - if ($Async) { - return $true - } - else { - return Wait-PodeServiceStatus -Name $Name -Status Suspended -Timeout $Timeout - } - } - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f '/bin/kill -SIGTSTP ', $Name) + # Handle async or wait for suspend + if ($Async) { + Write-Verbose -Message "Async mode: Service suspend command issued for '$Name'." + return $true + } + else { + Write-Verbose -Message "Waiting for service '$Name' to suspend (timeout: $Timeout seconds)..." + return Wait-PodeServiceStatus -Name $Name -Status Suspended -Timeout $Timeout } } catch { @@ -630,9 +624,9 @@ function Suspend-PodeService { Write-Error -Exception $_.Exception return $false } - return $true } + <# .SYNOPSIS Resume a specified service on Windows systems. @@ -665,85 +659,84 @@ function Suspend-PodeService { - If the service is not registered, an error is thrown. #> - function Resume-PodeService { [CmdletBinding(DefaultParameterSetName = 'Default')] [OutputType([bool])] param( [Parameter(Mandatory = $true)] - [string] - $Name, + [string] $Name, [Parameter(Mandatory = $true, ParameterSetName = 'Async')] - [switch] - $Async, + [switch] $Async, [Parameter(Mandatory = $false, ParameterSetName = 'Async')] - [int] - $Timeout = 10 + [ValidateRange(1, 300)] + [int] $Timeout = 10 ) - # Ensure the script is running with the necessary administrative/root privileges. - # Exits the script if the current user lacks the required privileges. - Confirm-PodeAdminPrivilege - try { + # Ensure administrative/root privileges + Confirm-PodeAdminPrivilege + # Get the service status $service = Get-PodeServiceStatus -Name $Name if (!$service) { - # Service is not registered throw ($PodeLocale.serviceIsNotRegisteredException -f $Name) } - if ($service.Status -ne 'Suspended') { - Write-Verbose -Message "Service '$Name' is not Suspended." - return $false - } - if (Test-PodeIsWindows) { - if (( Invoke-PodeWinElevatedCommand -Command 'sc.exe' -Arguments "continue '$Name'")) { - if ($Async) { - return $true - } - else { - return Wait-PodeServiceStatus -Name $Name -Status Running -Timeout $Timeout - } + Write-Verbose -Message "Service '$Name' current state: $($service.Status)." + + # Handle the current service state + switch ($service.Status) { + 'Running' { + Write-Verbose -Message "Service '$Name' is already running. No need to resume." + return $true } - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f 'sc.exe continue', $Name) - } - elseif ($IsLinux) { - if (( Send-PodeServiceSignal -Name $Name -Signal 'SIGCONT')) { - if ($Async) { - return $true - } - else { - return Wait-PodeServiceStatus -Name $Name -Status Running -Timeout $Timeout - } + 'Suspended' { + Write-Verbose -Message "Service '$Name' is currently suspended. Attempting to resume..." + } + 'Stopped' { + Write-Verbose -Message "Service '$Name' is stopped. Cannot resume a stopped service." + return $false } + { $_ -eq 'Starting' -or $_ -eq 'Stopping' -or $_ -eq 'Pausing' -or $_ -eq 'Resuming' } { + Write-Verbose -Message "Service '$Name' is transitioning state ($($service.Status)). Cannot resume at this time." + return $false + } + default { + Write-Verbose -Message "Service '$Name' is in an unknown state ($($service.Status))." + return $false + } + } - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f ' sudo /bin/kill -SIGCONT', $Name) + # Resume the service based on the OS + $serviceResumed = $false + if (Test-PodeIsWindows) { + $serviceResumed = Invoke-PodeWinElevatedCommand -Command 'sc.exe' -Arguments "continue '$Name'" + } + elseif ($IsLinux -or $IsMacOS) { + $serviceResumed = Send-PodeServiceSignal -Name $Name -Signal 'SIGCONT' } - elseif ($IsMacOS) { - if (( Send-PodeServiceSignal -Name $Name -Signal 'SIGCONT')) { - if ($Async) { - return $true - } - else { - return Wait-PodeServiceStatus -Name $Name -Status Running -Timeout $Timeout - } - } - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f '/bin/kill -SIGCONT ', $Name) + # Check if the service resume command failed + if (!$serviceResumed) { + throw ($PodeLocale.serviceCommandFailedException -f 'Resume', $Name) } + # Handle async or wait for resume + if ($Async) { + Write-Verbose -Message "Async mode: Service resume command issued for '$Name'." + return $true + } + else { + Write-Verbose -Message "Waiting for service '$Name' to resume (timeout: $Timeout seconds)..." + return Wait-PodeServiceStatus -Name $Name -Status Running -Timeout $Timeout + } } catch { $_ | Write-PodeErrorLog Write-Error -Exception $_.Exception return $false } - return $true } <# @@ -780,189 +773,125 @@ function Resume-PodeService { #> function Unregister-PodeService { param( - [Parameter()] - [switch]$Force, - [Parameter(Mandatory = $true)] [string] - $Name + $Name, + + [Parameter()] + [switch] + $Force ) - # Ensure the script is running with the necessary administrative/root privileges. - # Exits the script if the current user lacks the required privileges. + + # Ensure administrative/root privileges Confirm-PodeAdminPrivilege - if (Test-PodeIsWindows) { - # Check if the service exists - $service = Get-Service -Name $Name -ErrorAction SilentlyContinue - if (-not $service) { - # Service is not registered - throw ($PodeLocale.serviceIsNotRegisteredException -f "$Name") - } + # Get the service status + $service = Get-PodeServiceStatus -Name $Name + if (!$service) { + throw ($PodeLocale.serviceIsNotRegisteredException -f $Name) + } - try { - $pathName = $service.BinaryPathName - # Check if the service is running before attempting to stop it - if ($service.Status -eq 'Running') { - if ($Force.IsPresent) { - $null = Invoke-PodeWinElevatedCommand -Command 'sc' -Arguments "stop '$Name'" - if (!( Stop-PodeService -Name $Name)) { - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f 'Stop-Service', $Name) - } - } - else { - # Service is running. Use the -Force parameter to forcefully stop." - throw ($Podelocale.serviceIsRunningException -f $Name ) - } - } + Write-Verbose -Message "Service '$Name' current state: $($service.Status)." - # Remove the service - $null = Invoke-PodeWinElevatedCommand -Command 'sc.exe' -Arguments "delete '$Name'" - $service = Get-Service -Name $Name -ErrorAction SilentlyContinue - if ($null -ne $service) { - Write-Verbose -Message "Service '$Name' unregistered failed." - throw ($PodeLocale.serviceUnRegistrationException -f $Name) - } - Write-Verbose -Message "Service '$Name' unregistered successfully." + # Handle service state + if ($service.Status -ne 'Stopped') { + if ($Force) { + Write-Verbose -Message "Service '$Name' is not stopped. Stopping the service due to -Force parameter." + Stop-PodeService -Name $Name + Write-Verbose -Message "Service '$Name' has been stopped." + } + else { + Write-Verbose -Message "Service '$Name' is not stopped. Use -Force to stop and unregister it." + return $false + } + } - # Remove the service configuration - if ($pathName) { - $binaryPath = $pathName.trim('"').split('" "') - if ((Test-Path -Path ($binaryPath[1]) -PathType Leaf)) { - Remove-Item -Path ($binaryPath[1]) -ErrorAction Break - } - } - return $true + if (Test-PodeIsWindows) { + # Remove the service + $null = Invoke-PodeWinElevatedCommand -Command 'sc.exe' -Arguments "delete '$Name'" + if (Get-PodeService -Name $Name -ErrorAction SilentlyContinue) { + Write-Verbose -Message "Service '$Name' unregistered failed." + throw ($PodeLocale.serviceUnRegistrationException -f $Name) } - catch { - $_ | Write-PodeErrorLog - Write-Error -Exception $_.Exception - return $false + + Write-Verbose -Message "Service '$Name' unregistered successfully." + + # Remove the service configuration + if ($service.PathName) { + $binaryPath = $service.PathName.trim('"').split('" "') + if ($binaryPath.Count -gt 1 -and (Test-Path -Path ($binaryPath[1]) -PathType Leaf)) { + Remove-Item -Path ($binaryPath[1]) -ErrorAction Break + } } + return $true + } elseif ($IsLinux) { - try { - # Check if the service is already registered - if ((Test-PodeLinuxServiceIsRegistered $Name)) { - # Check if the service is active - if ((Test-PodeLinuxServiceIsActive -Name $Name)) { - if ($Force.IsPresent) { - #Stop the service - if (!( Stop-PodeService -Name $Name)) { - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f 'sudo systemctl stop', $Name) - } - } - else { - # Service is running. Use the -Force parameter to forcefully stop." - throw ($Podelocale.serviceIsRunningException -f $Name) - } - } - if ((Disable-PodeLinuxService -Name $Name)) { - # Read the content of the service file - $serviceFilePath = "/etc/systemd/system/$(Get-PodeRealServiceName -Name $Name)" - if ((Test-path -path $serviceFilePath -PathType Leaf)) { - $serviceFileContent = sudo cat $serviceFilePath - - # Extract the SettingsFile from the ExecStart line using regex - $execStart = ($serviceFileContent | Select-String -Pattern 'ExecStart=.*\s+(.*)').ToString() - # Find the index of '/PodeMonitor ' in the string - $index = $execStart.IndexOf('/PodeMonitor ') + ('/PodeMonitor '.Length) - # Extract everything after '/PodeMonitor ' - $settingsFile = $execStart.Substring($index) - if ((Test-Path -Path $settingsFile -PathType Leaf)) { - Remove-Item -Path $settingsFile - } - sudo rm $serviceFilePath - - Write-Verbose -Message "Service '$Name' unregistered successfully." - } - sudo systemctl daemon-reload - } - else { - Write-Verbose -Message "Service '$Name' unregistered failed." - throw ($PodeLocale.serviceUnRegistrationException -f $Name) - } - } - else { - # Service is not registered - throw ($PodeLocale.serviceIsNotRegisteredException -f $Name ) - } - return $true + $null = Disable-PodeLinuxService -Name $Name + if (Get-PodeService -Name $Name -ErrorAction SilentlyContinue) { + Write-Verbose -Message "Service '$Name' unregistered failed." + throw ($PodeLocale.serviceUnRegistrationException -f $Name) } - catch { - $_ | Write-PodeErrorLog - Write-Error -Exception $_.Exception - return $false + + Write-Verbose -Message "Service '$Name' unregistered successfully." + + # Read the content of the service file + if ((Test-path -path $service.PathName -PathType Leaf)) { + $serviceFileContent = & sudo cat $service.PathName + # Extract the SettingsFile from the ExecStart line using regex + $execStart = ($serviceFileContent | Select-String -Pattern 'ExecStart=.*\s+(.*)').ToString() + # Find the index of '/PodeMonitor ' in the string + $index = $execStart.IndexOf('/PodeMonitor ') + ('/PodeMonitor '.Length) + # Extract everything after '/PodeMonitor ' + $settingsFile = $execStart.Substring($index) + + & sudo rm $settingsFile + Write-Verbose -Message "Settings file '$settingsFile' removed." + + & sudo rm $service.PathName + Write-Verbose -Message "Service file '$($service.PathName)' removed." } + + # Reload systemd to apply changes + & sudo systemctl daemon-reload + Write-Verbose -Message 'Systemd daemon reloaded.' + return $true } elseif ($IsMacOS) { - try { - # Check if the service is already registered - if (Test-PodeMacOsServiceIsRegistered $Name) { - # Check if the service is active - if ((Test-PodeMacOsServiceIsActive -Name $Name)) { - if ($Force.IsPresent) { - #Stop the service - if (!( Stop-PodeService -Name $Name)) { - # Service command '{0}' failed on service '{1}'. - throw ($PodeLocale.serviceCommandFailedException -f 'launchctl stop', $Name) - } - } - else { - # Service is running. Use the -Force parameter to forcefully stop." - throw ($Podelocale.serviceIsRunningException -f $Name) - } - } + # Disable and unregister the service + Disable-PodeMacOsService -Name $Name + if (Get-PodeService -Name $Name -ErrorAction SilentlyContinue) { + Write-Verbose -Message "Service '$Name' unregistered failed." + throw ($PodeLocale.serviceUnRegistrationException -f $Name) + } - if ((Disable-PodeMacOsService -Name $Name)) { - $sudo = !(Test-Path -Path "$($HOME)/Library/LaunchAgents/$(Get-PodeRealServiceName -Name $Name).plist" -PathType Leaf) - if ($sudo) { - $plistFilePath = "/Library/LaunchDaemons/$(Get-PodeRealServiceName -Name $Name).plist" - } - else { - $plistFilePath = "$($HOME)/Library/LaunchAgents/$(Get-PodeRealServiceName -Name $Name).plist" - } - #Check if the plist file exists - if (Test-Path -Path $plistFilePath) { - # Read the content of the plist file - $plistXml = [xml](Get-Content -Path $plistFilePath -Raw) - - # Extract the second string in the ProgramArguments array (the settings file path) - $settingsFile = $plistXml.plist.dict.array.string[1] - if ($sudo) { - sudo rm $settingsFile - sudo rm $plistFilePath - } - else { - if ((Test-Path -Path $settingsFile -PathType Leaf)) { - Remove-Item -Path $settingsFile - } - - Remove-Item -Path $plistFilePath -ErrorAction Break - } - - Write-Verbose -Message "Service '$Name' unregistered successfully." - } + Write-Verbose -Message "Service '$Name' unregistered successfully." + + # Check if the plist file exists + if (Test-Path -Path $service.PathName) { + # Read the content of the plist file + $plistXml = [xml](Get-Content -Path $service.PathName -Raw) + if ($plistXml.plist.dict.array.string.Count -ge 2) { + # Extract the second string in the ProgramArguments array (the settings file path) + $settingsFile = $plistXml.plist.dict.array.string[1] + if ($service.Sudo) { + & sudo rm $settingsFile + Write-Verbose -Message "Settings file '$settingsFile' removed." + + & sudo rm $service.PathName + Write-Verbose -Message "Service file '$($service.PathName)' removed." } else { - Write-Verbose -Message "Service '$Name' unregistered failed." - throw ($PodeLocale.serviceUnRegistrationException -f $Name) + Remove-Item -Path $settingsFile -ErrorAction SilentlyContinue + Write-Verbose -Message "Settings file '$settingsFile' removed." + + Remove-Item -Path $service.PathName -ErrorAction SilentlyContinue + Write-Verbose -Message "Service file '$($service.PathName)' removed." } } - else { - # Service is not registered - throw ($PodeLocale.serviceIsNotRegisteredException -f $Name ) - } - return $true - } - catch { - $_ | Write-PodeErrorLog - Write-Error -Exception $_.Exception - return $false } } } @@ -1147,3 +1076,5 @@ function Restart-PodeService { Write-Verbose -Message "Service '$Name' restart operation completed successfully." return $true } + + diff --git a/tests/integration/Service.Tests.ps1 b/tests/integration/Service.Tests.ps1 index 63693366e..7c928d413 100644 --- a/tests/integration/Service.Tests.ps1 +++ b/tests/integration/Service.Tests.ps1 @@ -7,9 +7,10 @@ param() Describe 'Service Lifecycle' { it 'register' { - . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Register + $success = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Register + $success | Should -BeTrue Start-Sleep 10 - $status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query + $status = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query if ($IsMacOS) { $status.Status | Should -Be 'Running' $status.Pid | Should -BeGreaterThan 0 @@ -25,10 +26,11 @@ Describe 'Service Lifecycle' { it 'start' -Skip:( $IsMacOS) { - . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Start + $success = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Start + $success | Should -BeTrue Start-Sleep 2 $webRequest = Invoke-WebRequest -uri http://localhost:8080 -ErrorAction SilentlyContinue - $status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query + $status = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query $status.Status | Should -Be 'Running' $status.Name | Should -Be 'Hello Service' $status.Pid | Should -BeGreaterThan 0 @@ -36,10 +38,11 @@ Describe 'Service Lifecycle' { } it 'pause' { - . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Suspend + $success = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Suspend + $success | Should -BeTrue Start-Sleep 2 # $webRequest = Invoke-WebRequest -uri http://localhost:8080 -ErrorAction SilentlyContinue - $status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query + $status = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query $status.Status | Should -Be 'Suspended' $status.Name | Should -Be 'Hello Service' $status.Pid | Should -BeGreaterThan 0 @@ -47,19 +50,21 @@ Describe 'Service Lifecycle' { } it 'resume' { - . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -resume + $success = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -resume + $success | Should -BeTrue Start-Sleep 2 $webRequest = Invoke-WebRequest -uri http://localhost:8080 -ErrorAction SilentlyContinue - $status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query + $status = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query $status.Status | Should -Be 'Running' $status.Name | Should -Be 'Hello Service' $status.Pid | Should -BeGreaterThan 0 $webRequest.Content | Should -Be 'Hello, Service!' } it 'stop' { - . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Stop + $success = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Stop + $success | Should -BeTrue Start-Sleep 2 - $status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query + $status = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query $status.Status | Should -Be 'Stopped' $status.Name | Should -Be 'Hello Service' $status.Pid | Should -Be 0 @@ -67,11 +72,12 @@ Describe 'Service Lifecycle' { { Invoke-WebRequest -uri http://localhost:8080 } | Should -Throw } - it 're-start' { - . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Start + it 're-start' { + $success = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Start + $success | Should -BeTrue Start-Sleep 2 $webRequest = Invoke-WebRequest -uri http://localhost:8080 -ErrorAction SilentlyContinue - $status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query + $status = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query $status.Status | Should -Be 'Running' $status.Name | Should -Be 'Hello Service' $status.Pid | Should -BeGreaterThan 0 @@ -80,11 +86,11 @@ Describe 'Service Lifecycle' { it 're-stop' { - . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Stop + $success = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Stop + $success | Should -BeTrue Start-Sleep 2 - - $status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query + $status = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query $status.Status | Should -Be 'Stopped' $status.Name | Should -Be 'Hello Service' $status.Pid | Should -Be 0 @@ -93,9 +99,10 @@ Describe 'Service Lifecycle' { } it 'unregister' { - . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Unregister + $success = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Unregister + $success | Should -BeTrue Start-Sleep 2 - $status = . "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query + $status = & "$($PSScriptRoot)\..\..\examples\HelloService\HelloService.ps1" -Query $status | Should -BeNullOrEmpty }