Reputation: 31
I'm having issues figuring out an easy way to get the recovery options of a particular service in powershell.
Using command line sc: sc qfailure [servicename] [buffer size]
works.
I also know that HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\\[service]
will contain a FailureActions
but i cant find any documentation on interpreting those values.
Is it just a matter of executing SC.EXE
and parsing that data or is there a better way of doing this?
Upvotes: 3
Views: 7107
Reputation: 1
You can change with batch file that has code like this:
REG ADD "HKLM\SYSTEM\CurrentControlSet\Services\WaasMedicSvc" /v FailureActions /f /t REG_BINARY /d "80467E33000000000000000003000000140000000000000060EA000000000000000000000000000000000000"
Upvotes: 0
Reputation: 4234
Meh, not too hard to parse:
function New-ServiceRecoveryAction {
[CmdletBinding()]
[OutputType('ServiceRecoveryAction')]
Param (
[Parameter(Position = 0)]
[ValidateSet('Run', 'Restart', 'Reboot', 'None')]
[string] $Action = 'None',
[Parameter(Position = 1)]
[TimeSpan] $Delay = [TimeSpan]::Zero
)
End {
[PSCustomObject]@{
PSTypeName = 'ServiceRecoveryAction'
Action = $Action
Delay = $Delay
}
}
}
function New-ServiceRecovery {
[CmdletBinding()]
[OutputType('ServiceRecovery')]
Param (
[Parameter(Position = 0)]
[ValidateCount(0, 3)]
[PSTypeName('ServiceRecoveryAction')]
[PSCustomObject[]] $RecoveryAction = @(),
[Parameter(Position = 1)]
[TimeSpan] $ResetFailureCountAfter = [TimeSpan]::Zero,
[string] $RebootMessage,
[string] $RunCommandOnFailure = '',
[string[]] $CommandArguments = @(),
[switch] $AppendFailureCountToCommandArguments,
[switch] $RecoverOnNonCrashFailure
)
End {
$RecoveryAction = [PSCustomObject]@{
PSTypeName = 'ServiceRecovery'
RecoveryActions = if ($PSBoundParameters.ContainsKey('RecoveryAction')) { $RecoveryAction } else { @() }
ResetFailureCountAfter = $ResetFailureCountAfter
RebootMessage = $RebootMessage
CommandLine = "`"$RunCommandOnFailure`"$(if ($CommandArguments.Count -gt 0) { "$CommandArguments" })$(if ($AppendFailureCountToCommandArguments) { ' /fail=%1%' })"
RecoverOnNonCrashFailure = [switch]$RecoverOnNonCrashFailures
}
$RecoveryAction | Add-Member -MemberType ScriptProperty -Name FailureRecoveryArguments -Value {
@(
# NOTE
#
# 49711 days, converted to seconds, results in a value larger than can be held in a UInt32, which is the
# size of the field which holds the FailureResetCounter in the ServiceController object. When using, e.g.,
# `sc.exe failure <service> actions= restart/120000 reset= INIFINITE`, INFINITE gets translated to
# [UInt32]::MaxValue (approx. 49710 days, as shown in the UI). When attempting to set a value larger than what
# can fit in a unsigned 32-bit integer, the value becomes the "overflow" amount--i.e., (<value>.TotalSeconds % [UInt32]::MaxValue) - 1
# To prevent this (potentially) unexpected behavior, we're going to simply convert the value to INFINITE
# (i.e., [UInt32]::Maxvalue
#
# One other interesting piece of trivia: the UI for setting recovery actions only allows you to enter the number of
# days to reset the failed service counter. But, through the CLI, the granularity used is the number of seconds.
#
# Due to this "limitation", I'm going to assume that the field holding the milliseconds to delay before taking
# a recovery action is also a [Uint32] field.
'actions='
if ($this.RecoveryActions.Count -eq 0) { '///' } else {
$Actions = @()
foreach ($i in 1..3) {
if ($this.RecoveryActions.Count -ge $i) {
$RecoveryAction = $this.RecoveryActions[($i - 1)]
$DelayMilliseconds = if ($RecoveryAction.Delay.TotalMilliseconds -gt [UInt32]::MaxValue) { [UInt32]::MaxValue } else { [Convert]::ToUint32($RecoveryAction.Delay.TotalMilliseconds) }
$Actions += '{0}/{1}' -f $RecoveryAction.Action.ToLowerInvariant(), $DelayMilliseconds
} else {
$Actions += '/'
}
}
$Actions -join '/'
}
'reset='
if ($this.RecoveryActions.Count -eq 0) {
'0'
} elseif ($this.ResetFailureCountAfter.TotalSeconds -gt [UInt32]::MaxValue) {
[UInt32]::MaxValue
} else {
[Convert]::ToUInt32($this.ResetFailureCounterAfter.TotalSeconds)
}
if ($this.RecoveryActions | Where-Object { $_.Action -ieq 'Run' }) {
'command='
$this.CommandLine
}
if ($this.RecoveryActions | where-Object { $_.ACtion -ieq 'Reboot' }) {
'reboot='
$this.RebootMessage
}
)
} -PassThru
}
}
function Get-ServiceRecovery {
[CmdletBinding(DefaultParameterSetName = 'Default')]
[OutputType('ServiceRecovery')]
Param (
[Parameter(Mandatory, Position = 0, ValueFromPipeline, ParameterSetName = 'Default')]
[SupportsWildcards()]
[string[]] $Name,
[Parameter(Mandatory, Position = 0, ValueFromPipeline, ParameterSetName = 'InputObject')]
[System.ServiceProcess.ServiceController[]] $InputObject
)
Process {
if ($PSCmdlet.ParameterSetName -ieq 'Default') {
$Services = $Name | Get-Service
} else {
$Services = $InputObject
}
foreach ($Service in $Services) {
$ServiceName = $Service.Name
$(sc.exe qfailure "$ServiceName") -split '\r?\n' |
Select-Object -Skip 3 |
ForEach-Object -Begin {
$RecoveryParams = [hashtable]@{
RecoveryAction = [PSCustomObject[]]@()
RecoverOnNonCrashFailure = $(sc.exe qfailureflag "$Name") -split '\r?\n' | ForEach-Object -Begin {
$RetVal = $False
} -Process {
$RetVal = $RetVal -or $_ -imatch 'FAILURE_ACTIONS_ON_NONCRASH_FAILURES:\s*TRUE$'
} -End {
$RetVal
}
}
} -Process {
Write-Debug "Processing output string: $_"
if ($_ -imatch "^\s*RESET_PERIOD\s+\(in seconds\)\s+:\s+(?<TimeoutSeconds>\d+|INFINITE)\s*$") {
$Seconds = $matches.TimeoutSeconds
if ($Seconds -ieq 'INFINITE') {
$RecoveryParams.Add('ResetFailureCountAfter', ([TimeSpan]::FromSeconds([UInt32]::MaxValue)))
} else {
$RecoveryParams.Add('ResetFailureCountAfter', ([TimeSpan]::FromSeconds($Seconds)))
}
} elseif ($_ -imatch '^\s*REBOOT_MESSAGE\s+:\s+(?<RebootMessage>[^\s].*)$') {
$RecoveryParams.Add('RebootMessage', $matches.RebootMessage)
} elseif ($_ -imatch '^\s*COMMAND_LINE\s+:\s+(?<CommandLine>.*)$' -and $matches.CommandLine.Trim() -ne '""') {
$RecoveryParams.Add('RunCommandOnFailure', $matches.CommandLine.Trim())
} elseif ($_ -imatch '^\s*(?:FAILURE_ACTIONS\s+:\s+)?(?<RecoveryAction>RESTART|RUN PROCESS|REBOOT) -- Delay = (?<RecoveryDelay>\d+) milliseconds.$') {
switch ($matches.RecoveryAction) {
'RESTART' { $RecoveryParams.RecoveryAction += New-ServiceRecoveryAction 'Restart' ([TimeSpan]::FromMilliseconds($matches.RecoveryDelay)); break }
'RUN PROCESS' { $RecoveryParams.RecoveryAction += New-ServiceRecoveryAction 'Run' ([TimeSpan]::FromMilliseconds($matches.RecoveryDelay)); break }
'REBOOT' { $RecoveryParams.RecoveryAction += New-ServiceRecoveryAction 'Reboot' ([TimeSpan]::FromMilliseconds=($matches.RecoveryDelay)); break }
default { Write-Error "Unknown service failure recovery action: '$_'"; exit 1 }
}
}
} -End {
New-ServiceRecovery @RecoveryParams
}
}
}
}
function Set-ServiceRecovery {
[CmdletBinding(DefaultParameterSetName = 'Default', SupportsShouldProcess)]
[OutputType([System.ServiceProcess.ServiceController])]
Param (
[Parameter(Mandatory, Position = 1, ValueFromPipeline, ParameterSetName = 'Default')]
[SupportsWildcards()]
[string[]] $Name,
[Parameter(Mandatory, Position = 1, ValueFromPipeline, ParameterSetName = 'InputObject')]
[System.ServiceProcess.ServiceController[]] $InputObject,
[Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)]
[PSTypeName('ServiceRecovery')]
[PSCustomObject] $ServiceRecovery,
[switch] $Force
)
Process {
if ($Force -and !($PSBoundParameters.ContainsKey('Confirm') -and $Confirm)) {
$ConfirmPreference = 'None'
}
$Services = if ($PSCmdlet.ParameterSetName -ieq 'Default') {
$Name | Get-Service
} else {
$InputObject
}
foreach ($Service in $Services) {
$ServiceName = $Service.Name
if ($PSCmdlet.ShouldProcess($ServiceName)) {
$Output = sc.exe failure $ServiceName $ServiceRecovery.FailureRecoveryArguments
Write-Verbose $Output
$RetVal = $LASTEXITCODE
if ($RetVal -ne 0) {
$exn = New-Object -TypeName Exception -ArgumentList "Failed to set service recovery actions for $ServiceName."
$exn.Data.Add('Command', "sc.exe failure $ServiceName $($ServiceRecovery.FailureRecoveryArguments)")
$exn.Data.Add('Output', $Output)
throw (New-Object System.Management.Automation.ErrorRecord $exn, '', "$([System.Management.Automation.ErrorCategory]::InvalidOperation)" , $null)
}
$FailureFlag = if ($ServiceRecovery.RecoverOnNonCrashFailure) { 1 } else { 0 }
$Output = sc.exe failureflag $ServiceName $FailureFlag
Write-Verbose $Output
$RetVal = $LASTEXITCODE
if ($RetVal -ne 0) {
$exn = New-Object -TypeName Exception -ArgumentList "Failed to set service recovery to occur on non-crash failures for $($Service.Name)."
$exn.Data.Add('Command', "sc.exe failureflag $ServiceName $FailureFlag")
$exn.Data.Add('Output', $Output)
throw (New-Object System.Management.Automation.ErrorRecord $exn, '', "$([System.Management.Automation.ErrorCategory]::InvalidOperation)", $null)
}
}
$Service
}
}
}
So, to use this:
$ServiceRecoveryConfig = 'MSMQ' | Get-ServiceRecoveryConfig
You'll notice that I added a property to the object called FailureRecoveryArguments
. The idea behind this property is two-fold:
You can easily compare the service recovery configuration for two different services or compare it to a new/proposed service recovery configuration:
$RestartService = New-RecoveryAction 'Restart' ([TimeSpan]::FromMinutes(2))
$NewServiceRecovery = New-ServiceRecovery $RestartService ([TimeSpan]::FromMinutes(5))
$CurrentServiceRecovery = 'MSMQ' | Get-ServiceRecovery
if ("$($CurrentServiceRecovery.FailureRecoveryArguments)" -ine "$($NewServiceRecovery.FailureRecoveryArguments)") {
sc.exe MSMQ $NewServiceRecovery.FailureRecoveryArguments
}
You can "splat" the arguments to an invocation of sc.exe
, as show above when we found the desired service recovery configuration to not match the current service recovery configuration.
Of course, those two use-cases are moot when I have also provided Set-ServiceRecovery
as well: 'MSMQ' | Set-ServiceRecovery $NewServiceRecovery
If you get an error when running Set-ServiceRecovery
, check the Data
property on the exception of the ErrorRecord
object which will show the command that was run to set the service recovery, as well as the output from executing the command:
try {
'MSMQ' | Set-ServiceRecovery $NewServiceRecovery
} catch {
Write-Host "Aww shucks, I couldn't set the service recovery."
$Err = $Error[0]
Write-Host "When running '$($Err.Exception.Data['Command'])', received the output:`n$($Err.Exception.Data['Output'])"
}
Hope that helps!
NOTE that to set the service recovery configuration, you will need to run the function in an elevated context, i.e., run PowerShell as Administrator.
Upvotes: 3
Reputation: 1
jborean93 has created a custom type that exposes the native C# service objects and methods to PowerShell. The included Get-ServiceRecovery and Set-ServiceRecovery functions make it easy to view and change service recovery settings within PowerShell. https://gist.github.com/jborean93/889288b56087a2c5def7fa49b6a8a0ad
.\ServiceRecovery.ps1
(Get-ServiceRecovery -Name 'MyService').Actions
Upvotes: 0
Reputation: 1476
This will provide you Binary Value and you will have interpret it as follow which is tough part.
$actions = get-itemproperty hklm:\system\currentcontrolset\services\<ServiceShortName> | select -Expand FailureActions
typedef struct _SERVICE_FAILURE_ACTIONS {
DWORD dwResetPeriod;
LPTSTR lpRebootMsg;
LPTSTR lpCommand;
DWORD cActions;
SC_ACTION *lpsaActions;
} SERVICE_FAILURE_ACTIONS, *LPSERVICE_FAILURE_ACTIONS;
If you are using .NET
Upvotes: 3
Reputation: 25
The ServiceController object that Get-Service doesn't contain all the properties for what a service can do.
To get access to more things try connecting to WMI. Try this command to see the properties we can see in WMI.
Get-WmiObject Win32_service | select -first 1 -property *
Upvotes: -1