function Start-AlkamiService { <# .SYNOPSIS Starts a service, and throws an error if the service does not start in -Timeout seconds. .PARAMETER ServiceName The name of the service to start .PARAMETER Timeout The maximum number of seconds to wait. Defaults to 60 seconds .PARAMETER StatusUpdateIntervalSeconds Number of seconds to wait between log statements during the "Timeout Loop" when waiting for the service to start. This is to avoid having 60 log statements for each service start attempt that times out .PARAMETER Force Ignore whether or not Get-Service returns a service for ServiceName, try to start it anyway. Primarily useful for testing. #> Param( [Parameter(Mandatory = $true)] [string]$ServiceName, [Parameter(Mandatory = $false)] [int]$Timeout = 60, [Parameter(Mandatory = $false)] [int]$StatusUpdateIntervalSeconds = 10, [Parameter(Mandatory = $false)] [switch]$Force ) #region entry-guards $logLead = Get-LogLeadName $runningConstant = "Running" $disabledConstant = "Disabled" $startPendingConstant = "StartPending" if ($Force) { Write-Warning "$logLead : Force flag present! Unexepected behavior may occur AND log statements may not always be accurate, especially regarding existence or state of the service named [$ServiceName]" } Write-Host "$logLead : Get-Service -Name [$ServiceName]" try { $service = Get-Service -Name $ServiceName } catch { $caughtEx = $_ Write-Warning "$loglead : Service with name $ServiceName not found" if ($Force) { Write-Host "$loglead : Force flag enabled, continuing" } else { throw $caughtEx } } if ($null -eq $service -and -not $Force) { Write-Warning "$logLead : Service [$ServiceName] was not found" return } if ($service.StartType -eq $disabledConstant) { Write-Warning "$logLead : Service [$ServiceName] was disabled" return } if ($service.Status -eq $runningConstant) { Write-Host "$logLead : Service [$ServiceName] is already running." return } if ([int]$Timeout -le 0) { Write-Warning "$logLead : Invalid Timeout value [$Timeout], defaulting to 60" $Timeout = 60 } #endregion entry-guards $startTime = Get-Date Write-Host "$logLead : Starting [$ServiceName] at [$startTime]" # Invoke Command With Retry timing. $icwrTiming = @{ MaxRetries = 3 Seconds = 3 JitterMin = -500 JitterMax = 1500 } $icwrArgs = @{ Timeout = $Timeout StatusUpdateIntervalSeconds = $StatusUpdateIntervalSeconds LogLead = $logLead RunningConstant = $runningConstant StartPendingConstant = $startPendingConstant } $scriptblockStartService = { param($sbServiceName, $sbInputArgs) $sbTimeout = $sbInputArgs.Timeout $sbStatusUpdateIntervalSeconds = $sbInputArgs.StatusUpdateIntervalSeconds $sbLogLead = "sb_$($sbInputArgs.LogLead)" $sbRunningConstant = $sbInputArgs.RunningConstant $sbStartPendingConstant = $sbInputArgs.StartPendingConstant $sbPriorSvcStatus = (Get-Service -Name $sbServiceName -ErrorAction Ignore).Status if ($sbPriorSvcStatus -eq $sbRunningConstant) { Write-Warning "$sbLogLead : Service [$sbServiceName] already running; maybe a previously timed out attempt succeeded during backoff period. Exiting..." return } if ($sbPriorSvcStatus -eq $sbStartPendingConstant) { Write-Warning "$sbLoglead : service [$sbServiceName] is already in state [$sbStartPendingConstant]" Write-Warning "$sbLogLead : Waiting for [$sbServiceName] to start instead of calling start command" } else { try { Write-Host "$sbLogLead : start [$sbServiceName]" Invoke-SCExe -Arguments @("start", $sbServiceName) } catch { # Is an error on start $sbCaughtEx = $_ Write-Warning "$sbLogLead : start [$sbServiceName] failed due to immediate start failure" Write-Warning "$sbLogLead : $($sbCaughEx.Exception.Message)" throw $sbCaughtEx } } # Slowly poll to make sure that the service is running. Write-Host "$sbLogLead : waiting for [$sbServiceName] to enter state $sbRunningConstant (max $sbTimeout seconds)" $sbLastStatus = "" for ($i = 0; $i -lt $sbTimeout; $i++) { $sbLastStatus = (Get-Service -Name $sbServiceName).Status if ($sbLastStatus -eq $sbRunningConstant) { Write-Host "$sbLogLead : Service [$sbServiceName] was started successfully" return } # The following condition ALWAYS means the service entered the RUNNING state AND THEN LEFT IT # For explanations: # See https://docs.microsoft.com/en-us/windows/win32/services/service-status-transitions # See https://jira.alkami.com/browse/SRE-18231?focusedCommentId=5158719&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-5158719 if ($sbLastStatus -ne $sbStartPendingConstant) { Write-Host "$sbLogLead : Current status: [$sbLastStatus]" Write-Host "$sbLogLead : Expected status: [$sbStartPendingConstant]" Write-Warning "$sbLogLead : [$sbServiceName] started but is not in status [$sbRunningConstant] - CHECK LOGS" throw "$sbLogLead : Service [$sbServiceName] STOPPED RUNNING during the timeout period for some OTHER reason. Last status was [$sbLastStatus] - CHECK LOGS" } Start-Sleep -Seconds 1 $sbShouldWriteStatus = ($i + 1) % $sbStatusUpdateIntervalSeconds -eq 0 if ($sbShouldWriteStatus) { Write-Host "$sbLogLead : still waiting to make sure [$sbServiceName] has started. Last status was [$sbLastStatus] - seconds elapsed - [$($i + 1)] of [$sbTimeout]" } } Write-Host "$sbLogLead : start service [$sbServiceName] failed due to a TIMEOUT FAILURE" throw "$sbLogLead : Service [$sbServiceName] could not be started within the timeout period. Please investigate. Last status was [$sbLastStatus]" } $icwrSplat = @{ ScriptBlock = $scriptblockStartService Arguments = @($ServiceName, $icwrArgs) Exponential = $true } Invoke-CommandWithRetry @icwrSplat @icwrTiming }