win_nssm: refactor to fix issues, support check mode and add more features (#45693)

* win_nssm: rename cmdlets to use approved verbs, rename service name parameters

* win_nssm: improve code style and cmdlets ordering

* win_nssm: always escape all command line parameters with Argv-ToString

fix error when the service name contains quotes

* win_nssm: use Fail-Json instead of exceptions and remove global try/catch

* win_nssm: small refactoring, inline some functions

* win_nssm: refactoring - add a generic cmdlet to idempotently set any nssm service parameter

* win_nssm: refactoring - inline some functions

To make the code more malleable for future changes

* win_nssm: change application, stdout_file and stderr_file options type to path

* win_nssm: deprecates app_parameters, rename app_parameters_free_form to arguments, and add support for list of parameters

* win_nssm: add support of check mode

* win_nssm: add working_directory option

* win_nssm: add display_name and description options

* win_nssm: minor changes

* win_nssm: remove some sanity exclusions

* win_nssm: avoid using aliases and minor style fixes

* win_nssm: doc and ui improvements

* win_nssm: remove sanity exclusions

* win_nssm: minor revision

* win_nssm: deprecates dependencies, start_mode, user and password parameters and some choices of state in favor of win_service

* win_nssm: fix style

* win_nssm: add executable option to specify the location of the NSSM utility

* win_nssm: add missing parameter types

* win_nssm: add diff mode support

* win_nssm: avoid displaying depreciation warning if default value is assigned

* win_nssm: fix variable scope

* win_nssm: use the explicit -LiteralPath parameter name instead of -Path

* win_nssm: fix documentation

* win_nssm: add porting guide entries

* win_nssm: add changelog fragment
pull/54695/head
Kevin Subileau 6 years ago committed by Jordan Borean
parent eff1f8851c
commit 09979e899f

@ -0,0 +1,13 @@
minor_changes:
- win_nssm - Add the ``executable`` option to specify the location of the NSSM utility.
- win_nssm - Add the ``working_directory``, ``display_name`` and ``description`` options.
- win_nssm - Add support for check and diff modes.
- win_nssm - Change default value for ``state`` from ``start`` to ``present``.
bugfixes:
- win_nssm - Fix several escaping and quoting issues of paths and parameters.
deprecated_features:
- win_nssm - Deprecate ``dependencies``, ``start_mode``, ``user``, and ``password`` options, in favor of using the ``win_service`` module.
- win_nssm - Deprecate ``start``, ``stop``, and ``restart`` values for ``state`` option, in favor of using the ``win_service`` module.
- win_nssm - Deprecate ``app_parameters`` option in favor of ``arguments``.

@ -312,6 +312,24 @@ Noteworthy module changes
* The ``win_psexec`` has deprecated the undocumented ``extra_opts`` module option. This will be removed in Ansible 2.10. * The ``win_psexec`` has deprecated the undocumented ``extra_opts`` module option. This will be removed in Ansible 2.10.
* The ``win_nssm`` module has deprecated the following options in favor of using the ``win_service`` module to configure the service after installing it with ``win_nssm``:
* ``dependencies``, use ``dependencies`` of ``win_service`` instead
* ``start_mode``, use ``start_mode`` of ``win_service`` instead
* ``user``, use ``username`` of ``win_service`` instead
* ``password``, use ``password`` of ``win_service`` instead
These options will be removed in Ansible 2.12.
* The ``win_nssm`` module has also deprecated the ``start``, ``stop``, and ``restart`` values of the ``status`` option.
You should use the ``win_service`` module to control the running state of the service. This will be removed in Ansible 2.12.
* The ``status`` module option for ``win_nssm`` has changed its default value to ``present``. Before, the default was ``start``.
Consequently, the service is no longer started by default after creation with ``win_nssm``, and you should use
the ``win_service`` module to start it if needed.
* The ``app_parameters`` module option for ``win_nssm`` has been deprecated; use ``argument`` instead. This will be removed in Ansible 2.12.
* The ``app_parameters_free_form`` module option for ``win_nssm`` has been aliased to the new ``arguments`` option.
* The ``win_dsc`` module will now validate the input options for a DSC resource. In previous versions invalid options * The ``win_dsc`` module will now validate the input options for a DSC resource. In previous versions invalid options
would be ignored but are now not. would be ignored but are now not.

@ -3,6 +3,7 @@
# Copyright: (c) 2015, George Frank <george@georgefrank.net> # Copyright: (c) 2015, George Frank <george@georgefrank.net>
# Copyright: (c) 2015, Adam Keech <akeech@chathamfinancial.com> # Copyright: (c) 2015, Adam Keech <akeech@chathamfinancial.com>
# Copyright: (c) 2015, Hans-Joachim Kliemeck <git@kliemeck.de> # Copyright: (c) 2015, Hans-Joachim Kliemeck <git@kliemeck.de>
# Copyright: (c) 2019, Kevin Subileau (@ksubileau)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy #Requires -Module Ansible.ModuleUtils.Legacy
@ -11,671 +12,472 @@
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$params = Parse-Args $args $start_modes_map = @{
"auto" = "SERVICE_AUTO_START"
$result = @{ "delayed" = "SERVICE_DELAYED_AUTO_START"
changed = $false "manual" = "SERVICE_DEMAND_START"
"disabled" = "SERVICE_DISABLED"
} }
$params = Parse-Args -arguments $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$diff_mode = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true $name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent","started","stopped","restarted" -resultobj $result $state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent","started","stopped","restarted" -resultobj $result
$display_name = Get-AnsibleParam -obj $params -name 'display_name' -type 'str'
$description = Get-AnsibleParam -obj $params -name 'description' -type 'str'
$application = Get-AnsibleParam -obj $params -name "application" -type "str" $application = Get-AnsibleParam -obj $params -name "application" -type "path"
$appDirectory = Get-AnsibleParam -obj $params -name "working_directory" -aliases "app_directory","chdir" -type "path"
$appParameters = Get-AnsibleParam -obj $params -name "app_parameters" $appParameters = Get-AnsibleParam -obj $params -name "app_parameters"
$appParametersFree = Get-AnsibleParam -obj $params -name "app_parameters_free_form" -type "str" $appArguments = Get-AnsibleParam -obj $params -name "arguments" -aliases "app_parameters_free_form"
$startMode = Get-AnsibleParam -obj $params -name "start_mode" -type "str" -default "auto" -validateset "auto","delayed","manual","disabled" -resultobj $result
$stdoutFile = Get-AnsibleParam -obj $params -name "stdout_file" -type "str" $stdoutFile = Get-AnsibleParam -obj $params -name "stdout_file" -type "path"
$stderrFile = Get-AnsibleParam -obj $params -name "stderr_file" -type "str" $stderrFile = Get-AnsibleParam -obj $params -name "stderr_file" -type "path"
$dependencies = Get-AnsibleParam -obj $params -name "dependencies" -type "list"
$executable = Get-AnsibleParam -obj $params -name "executable" -type "path" -default "nssm.exe"
# Deprecated options since 2.8. Remove in 2.12
$startMode = Get-AnsibleParam -obj $params -name "start_mode" -type "str" -default "auto" -validateset $start_modes_map.Keys -resultobj $result
$dependencies = Get-AnsibleParam -obj $params -name "dependencies" -type "list"
$user = Get-AnsibleParam -obj $params -name "user" -type "str" $user = Get-AnsibleParam -obj $params -name "user" -type "str"
$password = Get-AnsibleParam -obj $params -name "password" -type "str" $password = Get-AnsibleParam -obj $params -name "password" -type "str"
if (($appParameters -ne $null) -and ($appParametersFree -ne $null)) $result = @{
{ changed = $false
Fail-Json $result "Use either app_parameters or app_parameteres_free_form, but not both"
}
if (($appParameters -ne $null) -and ($appParameters -isnot [string])) {
Fail-Json -obj $result -message "The app_parameters parameter must be a string representing a dictionary."
} }
$diff_text = $null
Function Nssm-Invoke function Invoke-NssmCommand {
{
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true,ValueFromRemainingArguments=$true)]
[string]$cmd [string[]]$arguments
) )
$nssm_result = Run-Command -command "nssm $cmd" $command = Argv-ToString -arguments (@($executable) + $arguments)
$result = Run-Command -command $command
return $nssm_result $result.arguments = $command
}
Function Service-Exists return $result
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$name
)
return [bool](Get-Service "$name" -ErrorAction SilentlyContinue)
} }
Function Nssm-Remove function Get-NssmServiceStatus {
{
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$name [string]$service
) )
if (Service-Exists -name $name) return Invoke-NssmCommand -arguments @("status", $service)
{
if ((Get-Service -Name $name).Status -ne "Stopped") {
$cmd = "stop ""$name"""
$nssm_result = Nssm-Invoke $cmd
}
$cmd = "remove ""$name"" confirm"
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0)
{
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr
Throw "Error removing service ""$name"""
}
$result.changed_by = "remove_service"
$result.changed = $true
}
} }
Function Nssm-Install function Get-NssmServiceParameter {
{
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$name, [string]$service,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[AllowEmptyString()] [Alias("param")]
[string]$application [string]$parameter,
[Parameter(Mandatory=$false)]
[string]$subparameter
) )
if (!$application) $arguments = @("get", $service, $parameter)
{ if($subparameter -ne "") {
Throw "Error installing service ""$name"". No application was supplied." $arguments += $subparameter
} }
If (-Not (Test-Path -Path $application -PathType Leaf)) { return Invoke-NssmCommand -arguments $arguments
Throw "$application does not exist on the host"
}
if (!(Service-Exists -name $name))
{
$nssm_result = Nssm-Invoke "install ""$name"" ""$application"""
if ($nssm_result.rc -ne 0)
{
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr
Throw "Error installing service ""$name"""
}
$result.changed_by = "install_service"
$result.changed = $true
} else {
$nssm_result = Nssm-Invoke "get ""$name"" Application"
if ($nssm_result.rc -ne 0)
{
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr
Throw "Error installing service ""$name"""
}
if ($nssm_result.stdout.split("`n`r")[0] -ne $application)
{
$cmd = "set ""$name"" Application ""$application"""
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0)
{
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr
Throw "Error installing service ""$name"""
}
$result.application = "$application"
$result.changed_by = "reinstall_service"
$result.changed = $true
}
}
if ($result.changed)
{
$applicationPath = (Get-Item $application).DirectoryName
$cmd = "set ""$name"" AppDirectory ""$applicationPath"""
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0)
{
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr
Throw "Error installing service ""$name"""
}
}
} }
Function ParseAppParameters() function Set-NssmServiceParameter {
{ [CmdletBinding()]
[CmdletBinding()]
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[AllowEmptyString()] [string]$service,
[string]$appParameters [Parameter(Mandatory=$true)]
[string]$parameter,
[Parameter(Mandatory=$true,ValueFromRemainingArguments=$true)]
[Alias("value")]
[string[]]$arguments
) )
$escapedAppParameters = $appParameters.TrimStart("@").TrimStart("{").TrimEnd("}").Replace("; ","`n").Replace("\","\\") return Invoke-NssmCommand -arguments (@("set", $service, $parameter) + $arguments)
return ConvertFrom-StringData -StringData $escapedAppParameters
} }
Function Nssm-Update-AppParameters function Reset-NssmServiceParameter {
{
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$name, [string]$service,
$appParameters, [Parameter(Mandatory=$true)]
[string]$appParametersFree [Alias("param")]
[string]$parameter
) )
$cmd = "get ""$name"" AppParameters" return Invoke-NssmCommand -arguments @("reset", $service, $parameter)
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0)
{
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr
Throw "Error updating AppParameters for service ""$name"""
}
$appParamKeys = @()
$appParamVals = @()
$singleLineParams = ""
if ($null -ne $appParameters)
{
$appParametersHash = ParseAppParameters -appParameters $appParameters
$appParamsArray = @()
$appParametersHash.GetEnumerator() | foreach {
$key = $($_.Name)
$val = $($_.Value)
$appParamKeys += $key
$appParamVals += $val
if ($key -ne "_") {
$appParamsArray += $key
}
$appParamsArray += $val
}
$result.nssm_app_parameters_keys = $appParamKeys
$result.nssm_app_parameters_vals = $appParamVals
$singleLineParams = Argv-ToString -arguments $appParamsArray
}
elseif ($null -ne $appParametersFree) {
$result.nssm_app_parameters_free_form = $appParametersFree
$singleLineParams = $appParametersFree
}
$result.nssm_app_parameters = $appParameters
$result.nssm_single_line_app_parameters = $singleLineParams
if ($nssm_result.stdout.split("`n`r")[0] -ne $singleLineParams)
{
# Escape argument line to preserve possible quotes and spaces
$singleLineParams = Escape-Argument -argument $singleLineParams
$cmd = "set ""$name"" AppParameters $singleLineParams"
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0)
{
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr
Throw "Error updating AppParameters for service ""$name"""
}
$result.changed_by = "update_app_parameters"
$result.changed = $true
}
} }
Function Nssm-Set-Output-Files function Update-NssmServiceParameter {
{ <#
[CmdletBinding()] .SYNOPSIS
A generic cmdlet to idempotently set a nssm service parameter.
.PARAMETER service
[String] The service name
.PARAMETER parameter
[String] The name of the nssm parameter to set.
.PARAMETER arguments
[String[]] Target value (or list of value) or array of arguments to pass to the 'nssm set' command.
.PARAMETER compare
[scriptblock] An optionnal idempotency check scriptblock that must return true when
the current value is equal to the desired value. Usefull when 'nssm get' doesn't return
the same value as 'nssm set' takes in argument, like for the ObjectName parameter.
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$name, [string]$service,
[string]$stdout,
[string]$stderr
)
$cmd = "get ""$name"" AppStdout"
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0)
{
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr
Throw "Error retrieving existing stdout file for service ""$name"""
}
if ($nssm_result.stdout.split("`n`r")[0] -ne $stdout) [Parameter(Mandatory=$true)]
{ [string]$parameter,
if (!$stdout)
{
$cmd = "reset ""$name"" AppStdout"
} else {
$cmd = "set ""$name"" AppStdout $stdout"
}
$nssm_result = Nssm-Invoke $cmd [Parameter(Mandatory=$true,ValueFromRemainingArguments=$true)]
[AllowEmptyString()]
[AllowNull()]
[Alias("value")]
[string[]]$arguments,
if ($nssm_result.rc -ne 0) [Parameter()]
{ [scriptblock]$compare = {param($actual,$expected) @(Compare-Object -ReferenceObject $actual -DifferenceObject $expected).Length -eq 0}
$result.nssm_error_cmd = $cmd )
$result.nssm_error_log = $nssm_result.stderr
Throw "Error setting stdout file for service ""$name"""
}
$result.changed_by = "set_stdout" if($null -eq $arguments) { return }
$result.changed = $true $arguments = @($arguments | Where-Object { $_ -ne '' })
}
$cmd = "get ""$name"" AppStderr" $nssm_result = Get-NssmServiceParameter -service $service -parameter $parameter
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0) if ($nssm_result.rc -ne 0) {
{ $result.nssm_error_cmd = $nssm_result.arguments
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr $result.nssm_error_log = $nssm_result.stderr
Throw "Error retrieving existing stderr file for service ""$name""" Fail-Json -obj $result -message "Error retrieving $parameter for service ""$service"""
} }
if ($nssm_result.stdout.split("`n`r")[0] -ne $stderr) $current_values = @($nssm_result.stdout.split("`n`r") | Where-Object { $_ -ne '' })
{
if (!$stderr)
{
$cmd = "reset ""$name"" AppStderr"
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0) if (-not $compare.Invoke($current_values,$arguments)) {
{ if ($PSCmdlet.ShouldProcess($service, "Update '$parameter' parameter")) {
$result.nssm_error_cmd = $cmd if($arguments.Count -gt 0) {
$result.nssm_error_log = $nssm_result.stderr $nssm_result = Set-NssmServiceParameter -service $service -parameter $parameter -arguments $arguments
Throw "Error clearing stderr file setting for service ""$name""" }
else {
$nssm_result = Reset-NssmServiceParameter -service $service -parameter $parameter
} }
} else {
$cmd = "set ""$name"" AppStderr $stderr"
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0) if ($nssm_result.rc -ne 0) {
{ $result.nssm_error_cmd = $nssm_result.arguments
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr $result.nssm_error_log = $nssm_result.stderr
Throw "Error setting stderr file for service ""$name""" Fail-Json -obj $result -message "Error setting $parameter for service ""$service"""
} }
} }
$result.changed_by = "set_stderr" $script:diff_text += "-$parameter = $($current_values -join ', ')`n+$parameter = $($arguments -join ', ')`n"
$result.changed_by = $parameter
$result.changed = $true $result.changed = $true
} }
}
### function Test-NssmServiceExists {
# Setup file rotation so we don't accidentally consume too much disk [CmdletBinding()]
### param(
[Parameter(Mandatory=$true)]
#set files to overwrite [string]$service
$cmd = "set ""$name"" AppStdoutCreationDisposition 2" )
$nssm_result = Nssm-Invoke $cmd
$cmd = "set ""$name"" AppStderrCreationDisposition 2"
$nssm_result = Nssm-Invoke $cmd
#enable file rotation
$cmd = "set ""$name"" AppRotateFiles 1"
$nssm_result = Nssm-Invoke $cmd
#don't rotate until the service restarts
$cmd = "set ""$name"" AppRotateOnline 0"
$nssm_result = Nssm-Invoke $cmd
#both of the below conditions must be met before rotation will happen
#minimum age before rotating
$cmd = "set ""$name"" AppRotateSeconds 86400"
$nssm_result = Nssm-Invoke $cmd
#minimum size before rotating return [bool](Get-Service -Name $service -ErrorAction SilentlyContinue)
$cmd = "set ""$name"" AppRotateBytes 104858"
$nssm_result = Nssm-Invoke $cmd
} }
Function Nssm-Update-Credentials function Invoke-NssmStart {
{
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$name, [string]$service
[Parameter(Mandatory=$false)]
[string]$user,
[Parameter(Mandatory=$false)]
[string]$password
) )
$cmd = "get ""$name"" ObjectName" $nssm_result = Invoke-NssmCommand -arguments @("start", $service)
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0) if ($nssm_result.rc -ne 0) {
{ $result.nssm_error_cmd = $nssm_result.arguments
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr $result.nssm_error_log = $nssm_result.stderr
Throw "Error updating credentials for service ""$name""" Fail-Json -obj $result -message "Error starting service ""$service"""
} }
}
if ($user) { function Invoke-NssmStop {
if (!$password) { [CmdletBinding()]
Throw "User without password is informed for service ""$name""" param(
} [Parameter(Mandatory=$true)]
else { [string]$service
$fullUser = $user )
If (-Not($user.contains("@")) -And ($user.Split("\").count -eq 1)) {
$fullUser = ".\" + $user
}
If ($nssm_result.stdout.split("`n`r")[0] -ne $fullUser) {
$cmd = Argv-ToString @("set", $name, "ObjectName", $fullUser, $password)
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0) $nssm_result = Invoke-NssmCommand -arguments @("stop", $service)
{
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr
Throw "Error updating credentials for service ""$name"""
}
$result.changed_by = "update_credentials" if ($nssm_result.rc -ne 0) {
$result.changed = $true $result.nssm_error_cmd = $nssm_result.arguments
} $result.nssm_error_log = $nssm_result.stderr
} Fail-Json -obj $result -message "Error stopping service ""$service"""
} }
} }
Function Nssm-Update-Dependencies function Start-NssmService {
{ [CmdletBinding(SupportsShouldProcess=$true)]
[CmdletBinding()]
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$name, [string]$service
[Parameter(Mandatory=$false)]
$dependencies
) )
if($null -eq $dependencies) { $currentStatus = Get-NssmServiceStatus -service $service
# Don't make any change to dependencies if the parameter is omitted
return
}
$cmd = "get ""$name"" DependOnService"
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0) if ($currentStatus.rc -ne 0) {
{ $result.nssm_error_cmd = $currentStatus.arguments
$result.nssm_error_cmd = $cmd $result.nssm_error_log = $currentStatus.stderr
$result.nssm_error_log = $nssm_result.stderr Fail-Json -obj $result -message "Error starting service ""$service"""
Throw "Error updating dependencies for service ""$name"""
} }
$current_dependencies = @($nssm_result.stdout.split("`n`r") | where { $_ -ne '' }) if ($currentStatus.stdout -notlike "*SERVICE_RUNNING*") {
if ($PSCmdlet.ShouldProcess($service, "Start service")) {
If (@(Compare-Object -ReferenceObject $current_dependencies -DifferenceObject $dependencies).Length -ne 0) { switch -wildcard ($currentStatus.stdout) {
$dependencies_str = Argv-ToString -arguments $dependencies "*SERVICE_STOPPED*" { Invoke-NssmStart -service $service }
$cmd = "set ""$name"" DependOnService $dependencies_str"
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0) "*SERVICE_CONTINUE_PENDING*" { Invoke-NssmStop -service $service; Invoke-NssmStart -service $service }
{ "*SERVICE_PAUSE_PENDING*" { Invoke-NssmStop -service $service; Invoke-NssmStart -service $service }
$result.nssm_error_cmd = $cmd "*SERVICE_PAUSED*" { Invoke-NssmStop -service $service; Invoke-NssmStart -service $service }
$result.nssm_error_log = $nssm_result.stderr "*SERVICE_START_PENDING*" { Invoke-NssmStop -service $service; Invoke-NssmStart -service $service }
Throw "Error updating dependencies for service ""$name""" "*SERVICE_STOP_PENDING*" { Invoke-NssmStop -service $service; Invoke-NssmStart -service $service }
}
} }
$result.changed_by = "update-dependencies" $result.changed_by = "start_service"
$result.changed = $true $result.changed = $true
} }
} }
Function Nssm-Update-StartMode function Stop-NssmService {
{ [CmdletBinding(SupportsShouldProcess=$true)]
[CmdletBinding()]
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$name, [string]$service
[Parameter(Mandatory=$true)]
[string]$mode
) )
$cmd = "get ""$name"" Start" $currentStatus = Get-NssmServiceStatus -service $service
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0) if ($currentStatus.rc -ne 0) {
{ $result.nssm_error_cmd = $currentStatus.arguments
$result.nssm_error_cmd = $cmd $result.nssm_error_log = $currentStatus.stderr
$result.nssm_error_log = $nssm_result.stderr Fail-Json -obj $result -message "Error stopping service ""$service"""
Throw "Error updating start mode for service ""$name"""
} }
$modes=@{"auto" = "SERVICE_AUTO_START"; "delayed" = "SERVICE_DELAYED_AUTO_START"; "manual" = "SERVICE_DEMAND_START"; "disabled" = "SERVICE_DISABLED"} if ($currentStatus.stdout -notlike "*SERVICE_STOPPED*") {
$mappedMode = $modes.$mode if ($PSCmdlet.ShouldProcess($service, "Stop service")) {
if ($nssm_result.stdout -notlike "*$mappedMode*") { Invoke-NssmStop -service $service
$cmd = "set ""$name"" Start $mappedMode"
$nssm_result = Nssm-Invoke $cmd
if ($nssm_result.rc -ne 0)
{
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr
Throw "Error updating start mode for service ""$name"""
} }
$result.changed_by = "start_mode" $result.changed_by = "stop_service"
$result.changed = $true $result.changed = $true
} }
} }
Function Nssm-Get-Status if (($null -ne $appParameters) -and ($null -ne $appArguments)) {
{ Fail-Json $result "'app_parameters' and 'arguments' are mutually exclusive but have both been set."
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$name
)
$cmd = "status ""$name"""
$nssm_result = Nssm-Invoke $cmd
return $nssm_result
} }
Function Nssm-Start # Backward compatibility for old parameters style. Remove the block bellow in 2.12
{ if ($null -ne $appParameters) {
[CmdletBinding()] Add-DeprecationWarning -obj $result -message "The parameter 'app_parameters' will be removed soon, use 'arguments' instead" -version 2.12
param(
[Parameter(Mandatory=$true)]
[string]$name
)
$currentStatus = Nssm-Get-Status -name $name
if ($currentStatus.rc -ne 0) if ($appParameters -isnot [string]) {
{ Fail-Json -obj $result -message "The app_parameters parameter must be a string representing a dictionary."
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $currentStatus.stderr
Throw "Error starting service ""$name"""
} }
switch -wildcard ($currentStatus.stdout) # Convert dict-as-string form to list
{ $escapedAppParameters = $appParameters.TrimStart("@").TrimStart("{").TrimEnd("}").Replace("; ","`n").Replace("\","\\")
"*SERVICE_RUNNING*" { <# Nothing to do #> } $appParametersHash = ConvertFrom-StringData -StringData $escapedAppParameters
"*SERVICE_STOPPED*" { Nssm-Start-Service-Command -name $name }
"*SERVICE_CONTINUE_PENDING*" { Nssm-Stop-Service-Command -name $name; Nssm-Start-Service-Command -name $name } $appParamsArray = @()
"*SERVICE_PAUSE_PENDING*" { Nssm-Stop-Service-Command -name $name; Nssm-Start-Service-Command -name $name } $appParametersHash.GetEnumerator() | Foreach-Object {
"*SERVICE_PAUSED*" { Nssm-Stop-Service-Command -name $name; Nssm-Start-Service-Command -name $name } if ($_.Name -ne "_") {
"*SERVICE_START_PENDING*" { Nssm-Stop-Service-Command -name $name; Nssm-Start-Service-Command -name $name } $appParamsArray += $_.Name
"*SERVICE_STOP_PENDING*" { Nssm-Stop-Service-Command -name $name; Nssm-Start-Service-Command -name $name } }
$appParamsArray += $_.Value
} }
$appArguments = @($appParamsArray)
# The rest of the code should use only the new $appArguments variable
} }
Function Nssm-Start-Service-Command if ($state -in @("started","stopped","restarted")) {
{ Add-DeprecationWarning -obj $result -message "The values 'started', 'stopped', and 'restarted' for 'state' will be removed soon, use the win_service module to start or stop the service instead" -version 2.12
[CmdletBinding()] }
param( if ($params.ContainsKey('start_mode')) {
[Parameter(Mandatory=$true)] Add-DeprecationWarning -obj $result -message "The parameter 'start_mode' will be removed soon, use the win_service module instead" -version 2.12
[string]$name }
) if ($null -ne $dependencies) {
Add-DeprecationWarning -obj $result -message "The parameter 'dependencies' will be removed soon, use the win_service module instead" -version 2.12
}
if ($null -ne $user) {
Add-DeprecationWarning -obj $result -message "The parameter 'user' will be removed soon, use the win_service module instead" -version 2.12
}
if ($null -ne $password) {
Add-DeprecationWarning -obj $result -message "The parameter 'password' will be removed soon, use the win_service module instead" -version 2.12
}
$cmd = "start ""$name""" if ($state -ne 'absent') {
if ($null -eq $application) {
Fail-Json -obj $result -message "The application parameter must be defined when the state is not absent."
}
$nssm_result = Nssm-Invoke $cmd if (-not (Test-Path -LiteralPath $application -PathType Leaf)) {
Fail-Json -obj $result -message "The application specified ""$application"" does not exist on the host."
}
if ($nssm_result.rc -ne 0) if($null -eq $appDirectory) {
{ $appDirectory = (Get-Item -LiteralPath $application).DirectoryName
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr
Throw "Error starting service ""$name"""
} }
$result.changed_by = "start_service" if ($user -and -not $password) {
$result.changed = $true Fail-Json -obj $result -message "User without password is informed for service ""$name"""
}
} }
Function Nssm-Stop-Service-Command
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$name
)
$cmd = "stop ""$name""" $service_exists = Test-NssmServiceExists -service $name
$nssm_result = Nssm-Invoke $cmd if ($state -eq 'absent') {
if ($service_exists) {
if(-not $check_mode) {
if ((Get-Service -Name $name).Status -ne "Stopped") {
$nssm_result = Invoke-NssmStop -service $name
}
if ($nssm_result.rc -ne 0) $nssm_result = Invoke-NssmCommand -arguments @("remove", $name, "confirm")
{
$result.nssm_error_cmd = $cmd if ($nssm_result.rc -ne 0) {
$result.nssm_error_log = $nssm_result.stderr $result.nssm_error_cmd = $nssm_result.arguments
Throw "Error stopping service ""$name""" $result.nssm_error_log = $nssm_result.stderr
Fail-Json -obj $result -message "Error removing service ""$name"""
}
}
$diff_text += "-[$name]"
$result.changed_by = "remove_service"
$result.changed = $true
} }
} else {
$diff_text_added_prefix = ''
if (-not $service_exists) {
if(-not $check_mode) {
$nssm_result = Invoke-NssmCommand -arguments @("install", $name, $application)
if ($nssm_result.rc -ne 0) {
$result.nssm_error_cmd = $nssm_result.arguments
$result.nssm_error_log = $nssm_result.stderr
Fail-Json -obj $result -message "Error installing service ""$name"""
}
$service_exists = $true
}
$result.changed_by = "stop_service_command" $diff_text_added_prefix = '+'
$result.changed = $true $result.changed_by = "install_service"
} $result.changed = $true
}
Function Nssm-Stop $diff_text += "$diff_text_added_prefix[$name]`n"
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$name
)
$currentStatus = Nssm-Get-Status -name $name # We cannot configure a service that was created above in check mode as it won't actually exist
if ($service_exists) {
$common_params = @{
service = $name
WhatIf = $check_mode
}
if ($currentStatus.rc -ne 0) Update-NssmServiceParameter -parameter "Application" -value $application @common_params
{ Update-NssmServiceParameter -parameter "DisplayName" -value $display_name @common_params
$result.nssm_error_cmd = $cmd Update-NssmServiceParameter -parameter "Description" -value $description @common_params
$result.nssm_error_log = $currentStatus.stderr
Throw "Error stopping service ""$name""" Update-NssmServiceParameter -parameter "AppDirectory" -value $appDirectory @common_params
}
if ($currentStatus.stdout -notlike "*SERVICE_STOPPED*")
{
$cmd = "stop ""$name"""
$nssm_result = Nssm-Invoke $cmd if ($null -ne $appArguments) {
$singleLineParams = ""
if ($appArguments -is [array]) {
$singleLineParams = Argv-ToString -arguments $appArguments
} else {
$singleLineParams = $appArguments.ToString()
}
$result.nssm_app_parameters = $appArguments
$result.nssm_single_line_app_parameters = $singleLineParams
if ($nssm_result.rc -ne 0) Update-NssmServiceParameter -parameter "AppParameters" -value $singleLineParams @common_params
{
$result.nssm_error_cmd = $cmd
$result.nssm_error_log = $nssm_result.stderr
Throw "Error stopping service ""$name"""
} }
$result.changed_by = "stop_service"
$result.changed = $true
}
}
Function Nssm-Restart Update-NssmServiceParameter -parameter "AppStdout" -value $stdoutFile @common_params
{ Update-NssmServiceParameter -parameter "AppStderr" -value $stderrFile @common_params
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$name
)
Nssm-Stop-Service-Command -name $name ###
Nssm-Start-Service-Command -name $name # Setup file rotation so we don't accidentally consume too much disk
} ###
Function NssmProcedure #set files to overwrite
{ Update-NssmServiceParameter -parameter "AppStdoutCreationDisposition" -value 2 @common_params
Nssm-Install -name $name -application $application Update-NssmServiceParameter -parameter "AppStderrCreationDisposition" -value 2 @common_params
Nssm-Update-AppParameters -name $name -appParameters $appParameters -appParametersFree $appParametersFree
Nssm-Set-Output-Files -name $name -stdout $stdoutFile -stderr $stderrFile
Nssm-Update-Dependencies -name $name -dependencies $dependencies
Nssm-Update-Credentials -name $name -user $user -password $password
Nssm-Update-StartMode -name $name -mode $startMode
}
Try #enable file rotation
{ Update-NssmServiceParameter -parameter "AppRotateFiles" -value 1 @common_params
switch ($state)
{ #don't rotate until the service restarts
"absent" { Update-NssmServiceParameter -parameter "AppRotateOnline" -value 0 @common_params
Nssm-Remove -name $name
} #both of the below conditions must be met before rotation will happen
"present" { #minimum age before rotating
NssmProcedure Update-NssmServiceParameter -parameter "AppRotateSeconds" -value 86400 @common_params
}
"started" { #minimum size before rotating
NssmProcedure Update-NssmServiceParameter -parameter "AppRotateBytes" -value 104858 @common_params
Nssm-Start -name $name
############## DEPRECATED block since 2.8. Remove in 2.12 ##############
Update-NssmServiceParameter -parameter "DependOnService" -arguments $dependencies @common_params
if ($user) {
$fullUser = $user
if (-Not($user.contains("@")) -And ($user.Split("\").count -eq 1)) {
$fullUser = ".\" + $user
}
# Use custom compare callback to test only the username (and not the password)
Update-NssmServiceParameter -parameter "ObjectName" -arguments @($fullUser, $password) -compare {param($actual,$expected) $actual[0] -eq $expected[0]} @common_params
} }
"stopped" { $mappedMode = $start_modes_map.$startMode
NssmProcedure Update-NssmServiceParameter -parameter "Start" -value $mappedMode @common_params
Nssm-Stop -name $name if ($state -in "stopped","restarted") {
Stop-NssmService @common_params
} }
"restarted" {
NssmProcedure if($state -in "started","restarted") {
Nssm-Restart -name $name Start-NssmService @common_params
} }
} ########################################################################
Exit-Json $result }
} }
Catch
{ if ($diff_mode -and $result.changed -eq $true) {
Fail-Json $result $_.Exception.Message $result.diff = @{
prepared = $diff_text
}
} }
Exit-Json $result

@ -15,9 +15,10 @@ DOCUMENTATION = r'''
--- ---
module: win_nssm module: win_nssm
version_added: "2.0" version_added: "2.0"
short_description: NSSM - the Non-Sucking Service Manager short_description: Install a service using NSSM
description: description:
- nssm is a service helper which doesn't suck. See U(https://nssm.cc/) for more information. - Install a Windows service using the NSSM wrapper.
- NSSM is a service helper which doesn't suck. See U(https://nssm.cc/) for more information.
requirements: requirements:
- "nssm >= 2.24.0 # (install via M(win_chocolatey)) C(win_chocolatey: name=nssm)" - "nssm >= 2.24.0 # (install via M(win_chocolatey)) C(win_chocolatey: name=nssm)"
options: options:
@ -29,50 +30,75 @@ options:
state: state:
description: description:
- State of the service on the system. - State of the service on the system.
- Note that NSSM actions like "pause", "continue", "rotate" do not fit the declarative style of ansible, so these should be implemented via the - Values C(started), C(stopped), and C(restarted) are deprecated since v2.8,
ansible command module. please use the M(win_service) module instead to start, stop or restart the service.
type: str type: str
choices: [ absent, present, started, stopped, restarted ] choices: [ absent, present, started, stopped, restarted ]
default: started default: present
application: application:
description: description:
- The application binary to run as a service - The application binary to run as a service
- "Specify this whenever the service may need to be installed (state: present, started, stopped, restarted)" - Required when I(state) is C(present), C(started), C(stopped), or C(restarted).
- "Note that the application name must look like the following, if the directory includes spaces:" type: path
- 'nssm install service "C:\\Program Files\\app.exe\\" "C:\\Path with spaces\\"' executable:
- > description:
See commit 0b386fc1984ab74ee59b7bed14b7e8f57212c22b in the nssm.git project for more info: - The location of the NSSM utility (in case it is not located in your PATH).
U(https://git.nssm.cc/?p=nssm.git;a=commit;h=0b386fc1984ab74ee59b7bed14b7e8f57212c22b) type: path
default: nssm.exe
version_added: "2.8.0"
description:
description:
- The description to set for the service.
type: str
version_added: "2.8.0"
display_name:
description:
- The display name to set for the service.
type: str
version_added: "2.8.0"
working_directory:
version_added: "2.8.0"
description:
- The working directory to run the service executable from (defaults to the directory containing the application binary)
type: path
aliases: [ app_directory, chdir ]
stdout_file: stdout_file:
description: description:
- Path to receive output. - Path to receive output.
type: str type: path
stderr_file: stderr_file:
description: description:
- Path to receive error output. - Path to receive error output.
type: str type: path
app_parameters: app_parameters:
description: description:
- A string representing a dictionary of parameters to be passed to the application when it starts. - A string representing a dictionary of parameters to be passed to the application when it starts.
- Use either this or C(app_parameters_free_form), not both. - DEPRECATED since v2.8, please use I(arguments) instead.
- This is mutually exclusive with I(arguments).
type: str type: str
app_parameters_free_form: arguments:
description: description:
- Single string of parameters to be passed to the service. - Parameters to be passed to the application when it starts.
- Use either this or C(app_parameters), not both. - This can be either a simple string or a list.
- This parameter was renamed from I(app_parameters_free_form) in 2.8.
- This is mutually exclusive with I(app_parameters).
aliases: [ app_parameters_free_form ]
type: str type: str
version_added: "2.3" version_added: "2.3"
dependencies: dependencies:
description: description:
- Service dependencies that has to be started to trigger startup, separated by comma. - Service dependencies that has to be started to trigger startup, separated by comma.
- DEPRECATED since v2.8, please use the M(win_service) module instead.
type: list type: list
user: user:
description: description:
- User to be used for service startup. - User to be used for service startup.
- DEPRECATED since v2.8, please use the M(win_service) module instead.
type: str type: str
password: password:
description: description:
- Password to be used for service startup. - Password to be used for service startup.
- DEPRECATED since v2.8, please use the M(win_service) module instead.
type: str type: str
start_mode: start_mode:
description: description:
@ -80,84 +106,66 @@ options:
- C(delayed) causes a delayed but automatic start after boot (added in version 2.5). - C(delayed) causes a delayed but automatic start after boot (added in version 2.5).
- C(manual) means that the service will start only when another service needs it. - C(manual) means that the service will start only when another service needs it.
- C(disabled) means that the service will stay off, regardless if it is needed or not. - C(disabled) means that the service will stay off, regardless if it is needed or not.
- DEPRECATED since v2.8, please use the M(win_service) module instead.
type: str type: str
choices: [ auto, delayed, disabled, manual ] choices: [ auto, delayed, disabled, manual ]
default: auto default: auto
seealso: seealso:
- module: win_service - module: win_service
notes:
- The service will NOT be started after its creation when C(state=present).
- Once the service is created, you can use the M(win_service) module to start it or configure
some additionals properties, such as its startup type, dependencies, service account, and so on.
author: author:
- Adam Keech (@smadam813) - Adam Keech (@smadam813)
- George Frank (@georgefrank) - George Frank (@georgefrank)
- Hans-Joachim Kliemeck (@h0nIg) - Hans-Joachim Kliemeck (@h0nIg)
- Michael Wild (@themiwi) - Michael Wild (@themiwi)
- Kevin Subileau (@ksubileau)
''' '''
EXAMPLES = r''' EXAMPLES = r'''
# Install and start the foo service - name: Install the foo service
- win_nssm: 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 single parameter
# This will yield the following command: C:\windows\\foo.exe bar
- win_nssm:
name: foo name: foo
application: C:\windows\foo.exe 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 "true"
# This will yield the following command: C:\windows\\foo.exe bar -file output.bat -foo false - name: Install the Consul service with a list of parameters
- win_nssm: win_nssm:
name: foo name: Consul
application: C:\windows\foo.exe application: C:\consul\consul.exe
app_parameters: _=bar; -file=output.bat; -foo=false arguments:
- agent
- -config-dir=C:\consul\config
# Use the single line parameters option to specify an arbitrary string of parameters # This is strictly equivalent to the previous example
# for the service executable - name: Install the Consul service with an arbitrary string of parameters
- name: Make sure the Consul service runs
win_nssm: win_nssm:
name: consul name: Consul
application: C:\consul\consul.exe application: C:\consul\consul.exe
app_parameters_free_form: agent -config-dir=C:\consul\config arguments: agent -config-dir=C:\consul\config
stdout_file: C:\consul\log.txt
stderr_file: C:\consul\error.txt
# Install and start the foo service, redirecting stdout and stderr to the same file
- win_nssm: # Install the foo service, an then configure and start it with win_service
- name: Install the foo service, redirecting stdout and stderr to the same file
win_nssm:
name: foo name: foo
application: C:\windows\foo.exe application: C:\windows\foo.exe
stdout_file: C:\windows\foo.log stdout_file: C:\windows\foo.log
stderr_file: C:\windows\foo.log stderr_file: C:\windows\foo.log
# Install and start the foo service, but wait for dependencies tcpip and adf - name: Configure and start the foo service using win_service
- win_nssm: win_service:
name: foo
application: C:\windows\foo.exe
dependencies: 'adf,tcpip'
# Install and start the foo service with dedicated user
- win_nssm:
name: foo name: foo
application: C:\windows\foo.exe dependencies: [ adf, tcpip ]
user: foouser user: foouser
password: secret password: secret
# Install the foo service but do not start it automatically
- win_nssm:
name: foo
application: C:\windows\foo.exe
state: present
start_mode: manual start_mode: manual
state: started
# Remove the foo service - name: Remove the foo service
- win_nssm: win_nssm:
name: foo name: foo
state: absent state: absent
''' '''

@ -3,11 +3,11 @@
set_fact: set_fact:
test_service_cmd: | test_service_cmd: |
$res = @{} $res = @{}
$srvobj = Get-WmiObject Win32_Service -Filter "Name=""$service""" | Select Name,PathName,StartMode,StartName,State $srvobj = Get-WmiObject Win32_Service -Filter "Name=""$service""" | Select Name,DisplayName,Description,PathName,StartMode,StartName,State
if ($srvobj) { if ($srvobj) {
$srvobj | Get-Member -MemberType *Property | % { $res.($_.name) = $srvobj.($_.name) } $srvobj | Get-Member -MemberType *Property | % { $res.($_.name) = $srvobj.($_.name) }
$res.Exists = $true $res.Exists = $true
$res.Dependencies = Get-WmiObject -Query "Associators of {Win32_Service.Name=""$service""} Where AssocClass=Win32_DependentService" | select -ExpandProperty Name $res.Dependencies = @(Get-WmiObject -Query "Associators of {Win32_Service.Name=""$service""} Where AssocClass=Win32_DependentService" | select -ExpandProperty Name)
$res.Parameters = @{} $res.Parameters = @{}
$srvkey = "HKLM:\SYSTEM\CurrentControlSet\Services\$service\Parameters" $srvkey = "HKLM:\SYSTEM\CurrentControlSet\Services\$service\Parameters"
Get-Item "$srvkey" | Select-Object -ExpandProperty property | % { $res.Parameters.$_ = (Get-ItemProperty -Path "$srvkey" -Name $_).$_} Get-Item "$srvkey" | Select-Object -ExpandProperty property | % { $res.Parameters.$_ = (Get-ItemProperty -Path "$srvkey" -Name $_).$_}
@ -16,6 +16,24 @@
} }
ConvertTo-Json -InputObject $res -Compress ConvertTo-Json -InputObject $res -Compress
- name: install service (check mode)
win_nssm:
name: '{{ test_service_name }}'
application: C:\Windows\System32\cmd.exe
state: present
register: install_service_check
check_mode: yes
- name: get result of install service (check mode)
win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}'
register: install_service_check_actual
- name: assert results of install service (check mode)
assert:
that:
- install_service_check.changed == true
- (install_service_check_actual.stdout|from_json).Exists == false
- name: install service - name: install service
win_nssm: win_nssm:
name: '{{ test_service_name }}' name: '{{ test_service_name }}'
@ -35,6 +53,7 @@
- (install_service_actual.stdout|from_json).State == 'Stopped' - (install_service_actual.stdout|from_json).State == 'Stopped'
- (install_service_actual.stdout|from_json).StartMode == 'Auto' - (install_service_actual.stdout|from_json).StartMode == 'Auto'
- (install_service_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe" - (install_service_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe"
- (install_service_actual.stdout|from_json).Parameters.AppDirectory == "C:\Windows\System32"
- name: test install service (idempotent) - name: test install service (idempotent)
win_nssm: win_nssm:
@ -55,6 +74,7 @@
- (install_service_again_actual.stdout|from_json).State == 'Stopped' - (install_service_again_actual.stdout|from_json).State == 'Stopped'
- (install_service_again_actual.stdout|from_json).StartMode == 'Auto' - (install_service_again_actual.stdout|from_json).StartMode == 'Auto'
- (install_service_again_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe" - (install_service_again_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe"
- (install_service_again_actual.stdout|from_json).Parameters.AppDirectory == "C:\Windows\System32"
- name: install and start service - name: install and start service
win_nssm: win_nssm:
@ -75,12 +95,52 @@
- (install_start_service_actual.stdout|from_json).State == 'Running' - (install_start_service_actual.stdout|from_json).State == 'Running'
- (install_start_service_actual.stdout|from_json).StartMode == 'Auto' - (install_start_service_actual.stdout|from_json).StartMode == 'Auto'
- (install_start_service_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe" - (install_start_service_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe"
- (install_start_service_actual.stdout|from_json).Parameters.AppDirectory == "C:\Windows\System32"
- name: install and start service with more parameters (check mode)
win_nssm:
name: '{{ test_service_name }}'
display_name: Ansible testing
description: win_nssm test service
application: C:\Windows\System32\cmd.exe
start_mode: manual
working_directory: '{{ test_win_nssm_path }}'
dependencies: 'tcpip,dnscache'
user: '{{ test_win_nssm_username }}'
password: '{{ test_win_nssm_password }}'
stdout_file: '{{ test_win_nssm_path }}\log.txt'
stderr_file: '{{ test_win_nssm_path }}\error.txt'
state: started
register: install_service_complex_check
check_mode: yes
- name: get result of install and start service with more parameters (check mode)
win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}'
register: install_service_complex_check_actual
- name: assert results of install and start service with more parameters (check mode)
assert:
that:
- install_service_complex_check.changed == true
- (install_service_complex_check_actual.stdout|from_json).Exists == true
- (install_service_complex_check_actual.stdout|from_json).DisplayName == '{{ test_service_name }}'
- (install_service_complex_check_actual.stdout|from_json).Description is none
- (install_service_complex_check_actual.stdout|from_json).StartMode != 'Manual'
- (install_service_complex_check_actual.stdout|from_json).StartName != '.\\' + test_win_nssm_username
- (install_service_complex_check_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe"
- (install_service_complex_check_actual.stdout|from_json).Parameters.AppDirectory == "C:\Windows\System32"
- '"AppStdout" not in (install_service_complex_check_actual.stdout|from_json).Parameters'
- '"AppStderr" not in (install_service_complex_check_actual.stdout|from_json).Parameters'
- (install_service_complex_check_actual.stdout|from_json).Dependencies|length == 0
- name: install and start service with more parameters - name: install and start service with more parameters
win_nssm: win_nssm:
name: '{{ test_service_name }}' name: '{{ test_service_name }}'
display_name: Ansible testing
description: win_nssm test service
application: C:\Windows\System32\cmd.exe application: C:\Windows\System32\cmd.exe
start_mode: manual start_mode: manual
working_directory: '{{ test_win_nssm_path }}'
dependencies: 'tcpip,dnscache' dependencies: 'tcpip,dnscache'
user: '{{ test_win_nssm_username }}' user: '{{ test_win_nssm_username }}'
password: '{{ test_win_nssm_password }}' password: '{{ test_win_nssm_password }}'
@ -98,10 +158,13 @@
that: that:
- install_service_complex.changed == true - install_service_complex.changed == true
- (install_service_complex_actual.stdout|from_json).Exists == true - (install_service_complex_actual.stdout|from_json).Exists == true
- (install_service_complex_actual.stdout|from_json).DisplayName == 'Ansible testing'
- (install_service_complex_actual.stdout|from_json).Description == 'win_nssm test service'
- (install_service_complex_actual.stdout|from_json).State == 'Running' - (install_service_complex_actual.stdout|from_json).State == 'Running'
- (install_service_complex_actual.stdout|from_json).StartMode == 'Manual' - (install_service_complex_actual.stdout|from_json).StartMode == 'Manual'
- (install_service_complex_actual.stdout|from_json).StartName == '.\\' + test_win_nssm_username - (install_service_complex_actual.stdout|from_json).StartName == '.\\' + test_win_nssm_username
- (install_service_complex_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe" - (install_service_complex_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe"
- (install_service_complex_actual.stdout|from_json).Parameters.AppDirectory == test_win_nssm_path
- (install_service_complex_actual.stdout|from_json).Parameters.AppStdout == test_win_nssm_path + '\\log.txt' - (install_service_complex_actual.stdout|from_json).Parameters.AppStdout == test_win_nssm_path + '\\log.txt'
- (install_service_complex_actual.stdout|from_json).Parameters.AppStderr == test_win_nssm_path + '\\error.txt' - (install_service_complex_actual.stdout|from_json).Parameters.AppStderr == test_win_nssm_path + '\\error.txt'
- (install_service_complex_actual.stdout|from_json).Dependencies|length == 2 - (install_service_complex_actual.stdout|from_json).Dependencies|length == 2
@ -111,8 +174,11 @@
- name: install and start service with more parameters (idempotent) - name: install and start service with more parameters (idempotent)
win_nssm: win_nssm:
name: '{{ test_service_name }}' name: '{{ test_service_name }}'
display_name: Ansible testing
description: win_nssm test service
application: C:\Windows\System32\cmd.exe application: C:\Windows\System32\cmd.exe
start_mode: manual start_mode: manual
working_directory: '{{ test_win_nssm_path }}'
# Dependencies order should not trigger a change # Dependencies order should not trigger a change
dependencies: 'dnscache,tcpip' dependencies: 'dnscache,tcpip'
user: '{{ test_win_nssm_username }}' user: '{{ test_win_nssm_username }}'
@ -131,70 +197,76 @@
that: that:
- install_service_complex_again.changed == false - install_service_complex_again.changed == false
- (install_service_complex_again_actual.stdout|from_json).Exists == true - (install_service_complex_again_actual.stdout|from_json).Exists == true
- (install_service_complex_again_actual.stdout|from_json).DisplayName == 'Ansible testing'
- (install_service_complex_again_actual.stdout|from_json).Description == 'win_nssm test service'
- (install_service_complex_again_actual.stdout|from_json).State == 'Running' - (install_service_complex_again_actual.stdout|from_json).State == 'Running'
- (install_service_complex_again_actual.stdout|from_json).StartMode == 'Manual' - (install_service_complex_again_actual.stdout|from_json).StartMode == 'Manual'
- (install_service_complex_again_actual.stdout|from_json).StartName == '.\\' + test_win_nssm_username - (install_service_complex_again_actual.stdout|from_json).StartName == '.\\' + test_win_nssm_username
- (install_service_complex_again_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe" - (install_service_complex_again_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe"
- (install_service_complex_again_actual.stdout|from_json).Parameters.AppDirectory == test_win_nssm_path
- (install_service_complex_again_actual.stdout|from_json).Parameters.AppStdout == test_win_nssm_path + '\\log.txt' - (install_service_complex_again_actual.stdout|from_json).Parameters.AppStdout == test_win_nssm_path + '\\log.txt'
- (install_service_complex_again_actual.stdout|from_json).Parameters.AppStderr == test_win_nssm_path + '\\error.txt' - (install_service_complex_again_actual.stdout|from_json).Parameters.AppStderr == test_win_nssm_path + '\\error.txt'
- (install_service_complex_again_actual.stdout|from_json).Dependencies|length == 2 - (install_service_complex_again_actual.stdout|from_json).Dependencies|length == 2
- '"Tcpip" in (install_service_complex_again_actual.stdout|from_json).Dependencies' - '"Tcpip" in (install_service_complex_again_actual.stdout|from_json).Dependencies'
- '"Dnscache" in (install_service_complex_again_actual.stdout|from_json).Dependencies' - '"Dnscache" in (install_service_complex_again_actual.stdout|from_json).Dependencies'
- name: install service with free form parameters - name: install service with string form parameters
win_nssm: win_nssm:
name: '{{ test_service_name }}' name: '{{ test_service_name }}'
application: C:\Windows\System32\cmd.exe application: C:\Windows\System32\cmd.exe
app_parameters_free_form: '-v -Dcom.test.string=value "C:\with space\\"' arguments: '-v -Dtest.str=value "C:\with space\\"'
state: present state: present
register: free_params register: str_params
- name: get result of install service with free form parameters - name: get result of install service with string form parameters
win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}' win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}'
register: free_params_actual register: str_params_actual
- name: assert results of install service with free form parameters - name: assert results of install service with string form parameters
assert: assert:
that: that:
- free_params.changed == true - str_params.changed == true
- (free_params_actual.stdout|from_json).Exists == true - (str_params_actual.stdout|from_json).Exists == true
- (free_params_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe" - (str_params_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe"
# Expected value: -v -Dcom.test.string=value "C:\with space\\" (backslashes doubled for jinja) # Expected value: -v -Dtest.str=value "C:\with space\\" (backslashes doubled for jinja)
- (free_params_actual.stdout|from_json).Parameters.AppParameters == '-v -Dcom.test.string=value "C:\\with space\\\\"' - (str_params_actual.stdout|from_json).Parameters.AppParameters == '-v -Dtest.str=value "C:\\with space\\\\"'
- name: install service with free form parameters (idempotent) - name: install service with string form parameters (idempotent)
win_nssm: win_nssm:
name: '{{ test_service_name }}' name: '{{ test_service_name }}'
application: C:\Windows\System32\cmd.exe application: C:\Windows\System32\cmd.exe
app_parameters_free_form: '-v -Dcom.test.string=value "C:\with space\\"' arguments: '-v -Dtest.str=value "C:\with space\\"'
state: present state: present
register: free_params_again register: str_params_again
- name: get result of install service with free form parameters (idempotent) - name: get result of install service with string form parameters (idempotent)
win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}' win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}'
register: free_params_again_actual register: str_params_again_actual
- name: assert results of install service with free form parameters (idempotent) - name: assert results of install service with string form parameters (idempotent)
assert: assert:
that: that:
- free_params_again.changed == false - str_params_again.changed == false
- (free_params_again_actual.stdout|from_json).Exists == true - (str_params_again_actual.stdout|from_json).Exists == true
- (free_params_again_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe" - (str_params_again_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe"
# Expected value: -v -Dcom.test.string=value "C:\with space\\" (backslashes doubled for jinja) # Expected value: -v -Dtest.str=value "C:\with space\\" (backslashes doubled for jinja)
- (free_params_again_actual.stdout|from_json).Parameters.AppParameters == '-v -Dcom.test.string=value "C:\\with space\\\\"' - (str_params_again_actual.stdout|from_json).Parameters.AppParameters == '-v -Dtest.str=value "C:\\with space\\\\"'
- name: install service with dict parameters # deprecated in 2.12
- name: install service with dict-as-string parameters
win_nssm: win_nssm:
name: '{{ test_service_name }}' name: '{{ test_service_name }}'
application: C:\Windows\System32\cmd.exe application: C:\Windows\System32\cmd.exe
app_parameters: foo=true; -file.out=output.bat; -path=C:\with space\; -str=test"quotes; _=bar app_parameters: foo=true; -file.out=output.bat; -path=C:\with space\; -str=test"quotes; _=bar
register: mixed_params register: mixed_params
- name: get result of install service with dict parameters # deprecated in 2.12
- name: get result of install service with dict-as-string parameters
win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}' win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}'
register: mixed_params_actual register: mixed_params_actual
- name: assert results of install service with dict parameters # deprecated in 2.12
- name: assert results of install service with dict-as-string parameters
assert: assert:
that: that:
- mixed_params.changed == true - mixed_params.changed == true
@ -203,18 +275,21 @@
# Expected value: bar -file.out output.bat -str "test\"quotes" foo true -path "C:\with space\\" (backslashes doubled for jinja) # Expected value: bar -file.out output.bat -str "test\"quotes" foo true -path "C:\with space\\" (backslashes doubled for jinja)
- (mixed_params_actual.stdout|from_json).Parameters.AppParameters == 'bar -file.out output.bat -str "test\\"quotes" foo true -path "C:\\with space\\\\"' - (mixed_params_actual.stdout|from_json).Parameters.AppParameters == 'bar -file.out output.bat -str "test\\"quotes" foo true -path "C:\\with space\\\\"'
- name: install service with dict parameters (idempotent) # deprecated in 2.12
- name: install service with dict-as-string parameters (idempotent)
win_nssm: win_nssm:
name: '{{ test_service_name }}' name: '{{ test_service_name }}'
application: C:\Windows\System32\cmd.exe application: C:\Windows\System32\cmd.exe
app_parameters: foo=true; -file.out=output.bat; -path=C:\with space\; -str=test"quotes; _=bar app_parameters: foo=true; -file.out=output.bat; -path=C:\with space\; -str=test"quotes; _=bar
register: mixed_params_again register: mixed_params_again
- name: get result of install service with dict parameters (idempotent) # deprecated in 2.12
- name: get result of install service with dict-as-string parameters (idempotent)
win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}' win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}'
register: mixed_params_again_actual register: mixed_params_again_actual
- name: assert results of install service with dict parameters (idempotent) # deprecated in 2.12
- name: assert results of install service with dict-as-string parameters (idempotent)
assert: assert:
that: that:
- mixed_params_again.changed == false - mixed_params_again.changed == false
@ -223,6 +298,81 @@
# Expected value: bar -file.out output.bat -str "test\"quotes" foo true -path "C:\with space\\" (backslashes doubled for jinja) # Expected value: bar -file.out output.bat -str "test\"quotes" foo true -path "C:\with space\\" (backslashes doubled for jinja)
- (mixed_params_again_actual.stdout|from_json).Parameters.AppParameters == 'bar -file.out output.bat -str "test\\"quotes" foo true -path "C:\\with space\\\\"' - (mixed_params_again_actual.stdout|from_json).Parameters.AppParameters == 'bar -file.out output.bat -str "test\\"quotes" foo true -path "C:\\with space\\\\"'
- name: install service with list of parameters
win_nssm:
name: '{{ test_service_name }}'
application: C:\Windows\System32\cmd.exe
arguments:
- -foo=bar
- -day
# Test non-string value
- 14
# Test if dot is not interpreted as separator (see #44079)
- -file.out
# Test if spaces are escaped
- C:\with space\output.bat
- -str
# Test if quotes and backslashes are escaped
- test"quotes\
register: list_params
- name: get result of install service with list of parameters
win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}'
register: list_params_actual
- name: assert results of install service with list of parameters
assert:
that:
- list_params.changed == true
- (list_params_actual.stdout|from_json).Exists == true
- (list_params_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe"
# Expected value: -foo=bar -day 14 -file.out "C:\with space\output.bat" -str "test\"quotes\\" (backslashes doubled for jinja)
- (list_params_actual.stdout|from_json).Parameters.AppParameters == '-foo=bar -day 14 -file.out "C:\\with space\\output.bat" -str "test\\"quotes\\\\"'
- name: install service with list of parameters (idempotent)
win_nssm:
name: '{{ test_service_name }}'
application: C:\Windows\System32\cmd.exe
arguments:
- -foo=bar
- -day
- 14
- -file.out
- C:\with space\output.bat
- -str
- test"quotes\
register: list_params_again
- name: get result of install service with list of parameters (idempotent)
win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}'
register: list_params_again_actual
- name: assert results of install service with list of parameters (idempotent)
assert:
that:
- list_params_again.changed == false
- (list_params_again_actual.stdout|from_json).Exists == true
- (list_params_again_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe"
# Expected value: -foo=bar -day 14 -file.out "C:\with space\output.bat" -str "test\"quotes\\" (backslashes doubled for jinja)
- (list_params_again_actual.stdout|from_json).Parameters.AppParameters == '-foo=bar -day 14 -file.out "C:\\with space\\output.bat" -str "test\\"quotes\\\\"'
- name: remove service (check mode)
win_nssm:
name: '{{ test_service_name }}'
state: absent
register: remove_service_check
check_mode: yes
- name: get result of remove service (check mode)
win_shell: '$service = ''{{ test_service_name }}''; {{ test_service_cmd }}'
register: remove_service_check_actual
- name: assert results of remove service (check mode)
assert:
that:
- remove_service_check.changed == true
- (remove_service_check_actual.stdout|from_json).Exists == true
- name: remove service - name: remove service
win_nssm: win_nssm:
name: '{{ test_service_name }}' name: '{{ test_service_name }}'

@ -91,9 +91,6 @@ lib/ansible/modules/windows/win_iis_website.ps1 PSUseDeclaredVarsMoreThanAssignm
lib/ansible/modules/windows/win_lineinfile.ps1 PSCustomUseLiteralPath lib/ansible/modules/windows/win_lineinfile.ps1 PSCustomUseLiteralPath
lib/ansible/modules/windows/win_mapped_drive.ps1 PSCustomUseLiteralPath lib/ansible/modules/windows/win_mapped_drive.ps1 PSCustomUseLiteralPath
lib/ansible/modules/windows/win_msg.ps1 PSAvoidTrailingWhitespace lib/ansible/modules/windows/win_msg.ps1 PSAvoidTrailingWhitespace
lib/ansible/modules/windows/win_nssm.ps1 PSAvoidUsingCmdletAliases
lib/ansible/modules/windows/win_nssm.ps1 PSCustomUseLiteralPath
lib/ansible/modules/windows/win_nssm.ps1 PSUseApprovedVerbs
lib/ansible/modules/windows/win_package.ps1 PSAvoidTrailingWhitespace lib/ansible/modules/windows/win_package.ps1 PSAvoidTrailingWhitespace
lib/ansible/modules/windows/win_package.ps1 PSCustomUseLiteralPath lib/ansible/modules/windows/win_package.ps1 PSCustomUseLiteralPath
lib/ansible/modules/windows/win_package.ps1 PSUseApprovedVerbs lib/ansible/modules/windows/win_package.ps1 PSUseApprovedVerbs

Loading…
Cancel
Save