diff --git a/lib/ansible/modules/extras/windows/win_nssm.ps1 b/lib/ansible/modules/extras/windows/win_nssm.ps1 new file mode 100644 index 00000000000..088914f4060 --- /dev/null +++ b/lib/ansible/modules/extras/windows/win_nssm.ps1 @@ -0,0 +1,547 @@ +#!powershell +# This file is part of Ansible +# +# Copyright 2015, George Frank +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +$ErrorActionPreference = "Stop" + +# WANT_JSON +# POWERSHELL_COMMON + +$params = Parse-Args $args; +$result = New-Object PSObject; +Set-Attr $result "changed" $false; + + +If ($params.name) +{ + $name = $params.name +} +Else +{ + Fail-Json $result "missing required argument: name" +} + +If ($params.state) +{ + $state = $params.state.ToString().ToLower() + $validStates = "present", "absent", "started", "stopped", "restarted" + + # These don't really fit the declarative style of ansible + # If you need to do these things, you can just write a command for it + # "paused", "continued", "rotated" + + If ($validStates -notcontains $state) + { + Fail-Json $result "state is $state; must be one of: $validStates" + } +} +else +{ + $state = "present" +} + +If ($params.application) +{ + $application = $params.application +} +Else +{ + $application = $null +} + +If ($params.app_parameters) +{ + $appParameters = $params.app_parameters +} +Else +{ + $appParameters = $null +} + +If ($params.stdout_file) +{ + $stdoutFile = $params.stdout_file +} +Else +{ + $stdoutFile = $null +} + +If ($params.stderr_file) +{ + $stderrFile = $params.stderr_file +} +Else +{ + $stderrFile = $null +} + +Function Service-Exists +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$name + ) + + return ,[bool](Get-Service "$name" -ErrorAction SilentlyContinue) +} + +Function Nssm-Remove +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$name + ) + + if (Service-Exists -name $name) + { + $cmd = "nssm stop ""$name""" + $results = invoke-expression $cmd + + $cmd = "nssm remove ""$name"" confirm" + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error removing service ""$name""" + } + + $result.changed = $true + } +} + +Function Nssm-Install +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$name, + [Parameter(Mandatory=$true)] + [string]$application + ) + + #note: the application name must look like the following, if the directory includes spaces: + # nssm install service "c:\Program Files\app.exe" """C:\Path with spaces""" + #see https://git.nssm.cc/?p=nssm/nssm.git;a=commit;h=0b386fc1984ab74ee59b7bed14b7e8f57212c22b for more info + + + if (!$application) + { + Throw "Error installing service ""$name"". No application was supplied." + } + + if (!(Service-Exists -name $name)) + { + $cmd = "nssm install ""$name"" $application" + + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error installing service ""$name""" + } + + $result.changed = $true + + } else { + $cmd = "nssm get ""$name"" Application" + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error installing service ""$name""" + } + + if ($results -ne $application) + { + $cmd = "nssm set ""$name"" Application $application" + + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error installing service ""$name""" + } + + $result.changed = $true + } + } +} + +Function ParseAppParameters() +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$appParameters + ) + + return ConvertFrom-StringData -StringData $appParameters.TrimStart("@").TrimStart("{").TrimEnd("}").Replace("; ","`n") +} + + +Function Nssm-Update-AppParameters +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$name, + [Parameter(Mandatory=$true)] + [string]$appParameters + ) + + $cmd = "nssm get ""$name"" AppParameters" + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error updating AppParameters for service ""$name""" + } + + $appParametersHash = ParseAppParameters -appParameters $appParameters + + $appParamKeys = @() + $appParamVals = @() + $singleLineParams = "" + $appParametersHash.GetEnumerator() | + % { + $key = $($_.Name) + $val = $($_.Value) + + $appParamKeys += $key + $appParamVals += $val + + if ($key -eq "_") { + $singleLineParams = "$val " + $singleLineParams + } else { + $singleLineParams = $singleLineParams + "$key ""$val""" + } + } + + Set-Attr $result "nssm_app_parameters" $appParameters + Set-Attr $result "nssm_app_parameters_parsed" $appParametersHash + Set-Attr $result "nssm_app_parameters_keys" $appParamKeys + Set-Attr $result "nssm_app_parameters_vals" $appParamVals + Set-Attr $result "nssm_single_line_app_parameters" $singleLineParams + + if ($results -ne $singleLineParams) + { + $cmd = "nssm set ""$name"" AppParameters $singleLineParams" + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error updating AppParameters for service ""$name""" + } + + $result.changed = $true + } +} + +Function Nssm-Set-Ouput-Files +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$name, + [string]$stdout, + [string]$stderr + ) + + $cmd = "nssm get ""$name"" AppStdout" + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error retrieving existing stdout file for service ""$name""" + } + + if ($results -ne $stdout) + { + if (!$stdout) + { + $cmd = "nssm reset ""$name"" AppStdout" + } else { + $cmd = "nssm set ""$name"" AppStdout $stdout" + } + + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error setting stdout file for service ""$name""" + } + + $result.changed = $true + } + + $cmd = "nssm get ""$name"" AppStderr" + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error retrieving existing stderr file for service ""$name""" + } + + if ($results -ne $stderr) + { + if (!$stderr) + { + $cmd = "nssm reset ""$name"" AppStderr" + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error clearing stderr file setting for service ""$name""" + } + } else { + $cmd = "nssm set ""$name"" AppStderr $stderr" + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error setting stderr file for service ""$name""" + } + } + + $result.changed = $true + } + + ### + # Setup file rotation so we don't accidentally consume too much disk + ### + + #set files to overwrite + $cmd = "nssm set ""$name"" AppStdoutCreationDisposition 2" + $results = invoke-expression $cmd + + $cmd = "nssm set ""$name"" AppStderrCreationDisposition 2" + $results = invoke-expression $cmd + + #enable file rotation + $cmd = "nssm set ""$name"" AppRotateFiles 1" + $results = invoke-expression $cmd + + #don't rotate until the service restarts + $cmd = "nssm set ""$name"" AppRotateOnline 0" + $results = invoke-expression $cmd + + #both of the below conditions must be met before rotation will happen + #minimum age before rotating + $cmd = "nssm set ""$name"" AppRotateSeconds 86400" + $results = invoke-expression $cmd + + #minimum size before rotating + $cmd = "nssm set ""$name"" AppRotateBytes 104858" + $results = invoke-expression $cmd +} + +Function Nssm-Get-Status +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$name + ) + + $cmd = "nssm status ""$name""" + $results = invoke-expression $cmd + + return ,$results +} + +Function Nssm-Start +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$name + ) + + $currentStatus = Nssm-Get-Status -name $name + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error starting service ""$name""" + } + + switch ($currentStatus) + { + "SERVICE_RUNNING" { <# Nothing to do #> } + "SERVICE_STOPPED" { Nssm-Start-Service-Command -name $name } + + "SERVICE_CONTINUE_PENDING" { Nssm-Stop-Service-Command -name $name; Nssm-Start-Service-Command -name $name } + "SERVICE_PAUSE_PENDING" { Nssm-Stop-Service-Command -name $name; Nssm-Start-Service-Command -name $name } + "SERVICE_PAUSED" { Nssm-Stop-Service-Command -name $name; Nssm-Start-Service-Command -name $name } + "SERVICE_START_PENDING" { Nssm-Stop-Service-Command -name $name; Nssm-Start-Service-Command -name $name } + "SERVICE_STOP_PENDING" { Nssm-Stop-Service-Command -name $name; Nssm-Start-Service-Command -name $name } + } +} + +Function Nssm-Start-Service-Command +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$name + ) + + $cmd = "nssm start ""$name""" + + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error starting service ""$name""" + } + + $result.changed = $true +} + +Function Nssm-Stop-Service-Command +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$name + ) + + $cmd = "nssm stop ""$name""" + + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error stopping service ""$name""" + } + + $result.changed = $true +} + +Function Nssm-Stop +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$name + ) + + $currentStatus = Nssm-Get-Status -name $name + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error stopping service ""$name""" + } + + if (currentStatus -ne "SERVICE_STOPPED") + { + $cmd = "nssm stop ""$name""" + + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "nssm_error_cmd" $cmd + Set-Attr $result "nssm_error_log" "$results" + Throw "Error stopping service ""$name""" + } + + $result.changed = $true + } +} + +Function Nssm-Restart +{ + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$name + ) + + Nssm-Stop-Service-Command -name $name + Nssm-Start-Service-Command -name $name +} + +Try +{ + switch ($state) + { + "absent" { Nssm-Remove -name $name } + "present" { + Nssm-Install -name $name -application $application + Nssm-Update-AppParameters -name $name -appParameters $appParameters + Nssm-Set-Ouput-Files -name $name -stdout $stdoutFile -stderr $stderrFile + } + "started" { + Nssm-Install -name $name -application $application + Nssm-Update-AppParameters -name $name -appParameters $appParameters + Nssm-Set-Ouput-Files -name $name -stdout $stdoutFile -stderr $stderrFile + Nssm-Start -name $name + } + "stopped" { + Nssm-Install -name $name -application $application + Nssm-Update-AppParameters -name $name -appParameters $appParameters + Nssm-Set-Ouput-Files -name $name -stdout $stdoutFile -stderr $stderrFile + Nssm-Stop -name $name + } + "restarted" { + Nssm-Install -name $name -application $application + Nssm-Update-AppParameters -name $name -appParameters $appParameters + Nssm-Set-Ouput-Files -name $name -stdout $stdoutFile -stderr $stderrFile + Nssm-Restart -name $name + } + } + + Exit-Json $result; +} +Catch +{ + Fail-Json $result $_.Exception.Message +} + diff --git a/lib/ansible/modules/extras/windows/win_nssm.py b/lib/ansible/modules/extras/windows/win_nssm.py new file mode 100644 index 00000000000..46c940ce151 --- /dev/null +++ b/lib/ansible/modules/extras/windows/win_nssm.py @@ -0,0 +1,136 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Heyo +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# this is a windows documentation stub. actual code lives in the .ps1 +# file of the same name + +DOCUMENTATION = ''' +--- +module: win_nssm +version_added: "2.0" +short_description: NSSM - the Non-Sucking Service Manager +description: + - nssm is a service helper which doesn't suck. See https://nssm.cc/ for more information. +options: + name: + description: + - Name of the service to operate on + required: true + default: null + aliases: [] + + state: + description: + - State of the service on the system + required: false + choices: + - started + - stopped + - restarted + - absent + default: started + aliases: [] + + application: + description: + - The application binary to run as a service + required: false + default: null + aliases: [] + + stdout_file: + description: + - Path to receive output + required: false + default: null + aliases: [] + + stderr_file: + description: + - Path to receive error output + required: false + default: null + aliases: [] + + app_parameters: + description: + - Parameters to be passed to the application when it starts + required: false + default: null + aliases: [] +author: "Adam Keech (@smadam813), George Frank (@georgefrank)" +''' + +# TODO: +# * Better parsing when a package has dependencies - currently fails +# * Time each item that is run +# * Support 'changed' with gems - would require shelling out to `gem list` first and parsing, kinda defeating the point of using chocolatey. + +EXAMPLES = ''' + # Install and start the foo service + win_nssm: + name: foo + application: C:\windows\foo.exe + + # Install and start the foo service with a key-value pair argument + # This will yield the following command: C:\windows\foo.exe bar "true" + win_nssm: + name: foo + application: C:\windows\foo.exe + app_parameters: + bar: true + + # Install and start the foo service with a key-value pair argument, where the argument needs to start with a dash + # This will yield the following command: C:\windows\foo.exe -bar "true" + win_nssm: + name: foo + application: C:\windows\foo.exe + app_parameters: + "-bar": true + + # Install and start the foo service with a single parameter + # This will yield the following command: C:\windows\foo.exe bar + win_nssm: + name: foo + application: C:\windows\foo.exe + app_parameters: + _: bar + + # Install and start the foo service with a mix of single params, and key value pairs + # This will yield the following command: C:\windows\foo.exe bar -file output.bat + win_nssm: + name: foo + application: C:\windows\foo.exe + app_parameters: + _: bar + "-file": "output.bat" + + # Install and start the foo service, redirecting stdout and stderr to the same file + win_nssm: + name: foo + application: C:\windows\foo.exe + stdout_file: C:\windows\foo.log + stderr_file: C:\windows\foo.log + + # Remove the foo service + win_nssm: + name: foo + state: absent +'''