From 09979e899f1f49e51f1db02180a7ff6add0a8579 Mon Sep 17 00:00:00 2001 From: Kevin Subileau Date: Sun, 31 Mar 2019 21:56:48 +0200 Subject: [PATCH] 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 --- changelogs/fragments/win_nssm.yaml | 13 + .../rst/porting_guides/porting_guide_2.8.rst | 18 + lib/ansible/modules/windows/win_nssm.ps1 | 830 +++++++----------- lib/ansible/modules/windows/win_nssm.py | 140 +-- .../targets/win_nssm/tasks/tests.yml | 212 ++++- test/sanity/pslint/ignore.txt | 3 - 6 files changed, 602 insertions(+), 614 deletions(-) create mode 100644 changelogs/fragments/win_nssm.yaml diff --git a/changelogs/fragments/win_nssm.yaml b/changelogs/fragments/win_nssm.yaml new file mode 100644 index 00000000000..9de9a159e3e --- /dev/null +++ b/changelogs/fragments/win_nssm.yaml @@ -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``. diff --git a/docs/docsite/rst/porting_guides/porting_guide_2.8.rst b/docs/docsite/rst/porting_guides/porting_guide_2.8.rst index 3512f19fa1a..ccda1c48c2d 100644 --- a/docs/docsite/rst/porting_guides/porting_guide_2.8.rst +++ b/docs/docsite/rst/porting_guides/porting_guide_2.8.rst @@ -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_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 would be ignored but are now not. diff --git a/lib/ansible/modules/windows/win_nssm.ps1 b/lib/ansible/modules/windows/win_nssm.ps1 index 323eeb0d712..1bb6542a595 100644 --- a/lib/ansible/modules/windows/win_nssm.ps1 +++ b/lib/ansible/modules/windows/win_nssm.ps1 @@ -3,6 +3,7 @@ # Copyright: (c) 2015, George Frank # Copyright: (c) 2015, Adam Keech # Copyright: (c) 2015, Hans-Joachim Kliemeck +# Copyright: (c) 2019, Kevin Subileau (@ksubileau) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) #Requires -Module Ansible.ModuleUtils.Legacy @@ -11,671 +12,472 @@ $ErrorActionPreference = "Stop" -$params = Parse-Args $args - -$result = @{ - changed = $false +$start_modes_map = @{ + "auto" = "SERVICE_AUTO_START" + "delayed" = "SERVICE_DELAYED_AUTO_START" + "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 $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" -$appParametersFree = Get-AnsibleParam -obj $params -name "app_parameters_free_form" -type "str" -$startMode = Get-AnsibleParam -obj $params -name "start_mode" -type "str" -default "auto" -validateset "auto","delayed","manual","disabled" -resultobj $result +$appArguments = Get-AnsibleParam -obj $params -name "arguments" -aliases "app_parameters_free_form" -$stdoutFile = Get-AnsibleParam -obj $params -name "stdout_file" -type "str" -$stderrFile = Get-AnsibleParam -obj $params -name "stderr_file" -type "str" -$dependencies = Get-AnsibleParam -obj $params -name "dependencies" -type "list" +$stdoutFile = Get-AnsibleParam -obj $params -name "stdout_file" -type "path" +$stderrFile = Get-AnsibleParam -obj $params -name "stderr_file" -type "path" + +$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" $password = Get-AnsibleParam -obj $params -name "password" -type "str" -if (($appParameters -ne $null) -and ($appParametersFree -ne $null)) -{ - 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." +$result = @{ + changed = $false } +$diff_text = $null -Function Nssm-Invoke -{ +function Invoke-NssmCommand { [CmdletBinding()] param( - [Parameter(Mandatory=$true)] - [string]$cmd + [Parameter(Mandatory=$true,ValueFromRemainingArguments=$true)] + [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 -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string]$name - ) - - return [bool](Get-Service "$name" -ErrorAction SilentlyContinue) + return $result } -Function Nssm-Remove -{ +function Get-NssmServiceStatus { [CmdletBinding()] param( [Parameter(Mandatory=$true)] - [string]$name + [string]$service ) - if (Service-Exists -name $name) - { - 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 - } + return Invoke-NssmCommand -arguments @("status", $service) } -Function Nssm-Install -{ +function Get-NssmServiceParameter { [CmdletBinding()] param( [Parameter(Mandatory=$true)] - [string]$name, + [string]$service, [Parameter(Mandatory=$true)] - [AllowEmptyString()] - [string]$application + [Alias("param")] + [string]$parameter, + [Parameter(Mandatory=$false)] + [string]$subparameter ) - if (!$application) - { - Throw "Error installing service ""$name"". No application was supplied." + $arguments = @("get", $service, $parameter) + if($subparameter -ne "") { + $arguments += $subparameter } - If (-Not (Test-Path -Path $application -PathType Leaf)) { - 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""" - } - } + return Invoke-NssmCommand -arguments $arguments } -Function ParseAppParameters() -{ - [CmdletBinding()] +function Set-NssmServiceParameter { + [CmdletBinding()] param( [Parameter(Mandatory=$true)] - [AllowEmptyString()] - [string]$appParameters + [string]$service, + [Parameter(Mandatory=$true)] + [string]$parameter, + [Parameter(Mandatory=$true,ValueFromRemainingArguments=$true)] + [Alias("value")] + [string[]]$arguments ) - $escapedAppParameters = $appParameters.TrimStart("@").TrimStart("{").TrimEnd("}").Replace("; ","`n").Replace("\","\\") - - return ConvertFrom-StringData -StringData $escapedAppParameters + return Invoke-NssmCommand -arguments (@("set", $service, $parameter) + $arguments) } -Function Nssm-Update-AppParameters -{ +function Reset-NssmServiceParameter { [CmdletBinding()] param( [Parameter(Mandatory=$true)] - [string]$name, - $appParameters, - [string]$appParametersFree + [string]$service, + [Parameter(Mandatory=$true)] + [Alias("param")] + [string]$parameter ) - $cmd = "get ""$name"" AppParameters" - $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 - } + return Invoke-NssmCommand -arguments @("reset", $service, $parameter) } -Function Nssm-Set-Output-Files -{ - [CmdletBinding()] +function Update-NssmServiceParameter { + <# + .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( [Parameter(Mandatory=$true)] - [string]$name, - [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""" - } + [string]$service, - if ($nssm_result.stdout.split("`n`r")[0] -ne $stdout) - { - if (!$stdout) - { - $cmd = "reset ""$name"" AppStdout" - } else { - $cmd = "set ""$name"" AppStdout $stdout" - } + [Parameter(Mandatory=$true)] + [string]$parameter, - $nssm_result = Nssm-Invoke $cmd + [Parameter(Mandatory=$true,ValueFromRemainingArguments=$true)] + [AllowEmptyString()] + [AllowNull()] + [Alias("value")] + [string[]]$arguments, - if ($nssm_result.rc -ne 0) - { - $result.nssm_error_cmd = $cmd - $result.nssm_error_log = $nssm_result.stderr - Throw "Error setting stdout file for service ""$name""" - } + [Parameter()] + [scriptblock]$compare = {param($actual,$expected) @(Compare-Object -ReferenceObject $actual -DifferenceObject $expected).Length -eq 0} + ) - $result.changed_by = "set_stdout" - $result.changed = $true - } + if($null -eq $arguments) { return } + $arguments = @($arguments | Where-Object { $_ -ne '' }) - $cmd = "get ""$name"" AppStderr" - $nssm_result = Nssm-Invoke $cmd + $nssm_result = Get-NssmServiceParameter -service $service -parameter $parameter - if ($nssm_result.rc -ne 0) - { - $result.nssm_error_cmd = $cmd + if ($nssm_result.rc -ne 0) { + $result.nssm_error_cmd = $nssm_result.arguments $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) - { - if (!$stderr) - { - $cmd = "reset ""$name"" AppStderr" - $nssm_result = Nssm-Invoke $cmd + $current_values = @($nssm_result.stdout.split("`n`r") | Where-Object { $_ -ne '' }) - if ($nssm_result.rc -ne 0) - { - $result.nssm_error_cmd = $cmd - $result.nssm_error_log = $nssm_result.stderr - Throw "Error clearing stderr file setting for service ""$name""" + if (-not $compare.Invoke($current_values,$arguments)) { + if ($PSCmdlet.ShouldProcess($service, "Update '$parameter' parameter")) { + if($arguments.Count -gt 0) { + $nssm_result = Set-NssmServiceParameter -service $service -parameter $parameter -arguments $arguments + } + 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) - { - $result.nssm_error_cmd = $cmd + if ($nssm_result.rc -ne 0) { + $result.nssm_error_cmd = $nssm_result.arguments $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 } +} - ### - # Setup file rotation so we don't accidentally consume too much disk - ### - - #set files to overwrite - $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 +function Test-NssmServiceExists { + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$service + ) - #minimum size before rotating - $cmd = "set ""$name"" AppRotateBytes 104858" - $nssm_result = Nssm-Invoke $cmd + return [bool](Get-Service -Name $service -ErrorAction SilentlyContinue) } -Function Nssm-Update-Credentials -{ +function Invoke-NssmStart { [CmdletBinding()] param( [Parameter(Mandatory=$true)] - [string]$name, - [Parameter(Mandatory=$false)] - [string]$user, - [Parameter(Mandatory=$false)] - [string]$password + [string]$service ) - $cmd = "get ""$name"" ObjectName" - $nssm_result = Nssm-Invoke $cmd + $nssm_result = Invoke-NssmCommand -arguments @("start", $service) - if ($nssm_result.rc -ne 0) - { - $result.nssm_error_cmd = $cmd + if ($nssm_result.rc -ne 0) { + $result.nssm_error_cmd = $nssm_result.arguments $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) { - if (!$password) { - Throw "User without password is informed for service ""$name""" - } - else { - $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 +function Invoke-NssmStop { + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [string]$service + ) - if ($nssm_result.rc -ne 0) - { - $result.nssm_error_cmd = $cmd - $result.nssm_error_log = $nssm_result.stderr - Throw "Error updating credentials for service ""$name""" - } + $nssm_result = Invoke-NssmCommand -arguments @("stop", $service) - $result.changed_by = "update_credentials" - $result.changed = $true - } - } + 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 stopping service ""$service""" } } -Function Nssm-Update-Dependencies -{ - [CmdletBinding()] +function Start-NssmService { + [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Mandatory=$true)] - [string]$name, - [Parameter(Mandatory=$false)] - $dependencies + [string]$service ) - if($null -eq $dependencies) { - # Don't make any change to dependencies if the parameter is omitted - return - } - - $cmd = "get ""$name"" DependOnService" - $nssm_result = Nssm-Invoke $cmd + $currentStatus = Get-NssmServiceStatus -service $service - if ($nssm_result.rc -ne 0) - { - $result.nssm_error_cmd = $cmd - $result.nssm_error_log = $nssm_result.stderr - Throw "Error updating dependencies for service ""$name""" + if ($currentStatus.rc -ne 0) { + $result.nssm_error_cmd = $currentStatus.arguments + $result.nssm_error_log = $currentStatus.stderr + Fail-Json -obj $result -message "Error starting service ""$service""" } - $current_dependencies = @($nssm_result.stdout.split("`n`r") | where { $_ -ne '' }) - - If (@(Compare-Object -ReferenceObject $current_dependencies -DifferenceObject $dependencies).Length -ne 0) { - $dependencies_str = Argv-ToString -arguments $dependencies - $cmd = "set ""$name"" DependOnService $dependencies_str" - $nssm_result = Nssm-Invoke $cmd + if ($currentStatus.stdout -notlike "*SERVICE_RUNNING*") { + if ($PSCmdlet.ShouldProcess($service, "Start service")) { + switch -wildcard ($currentStatus.stdout) { + "*SERVICE_STOPPED*" { Invoke-NssmStart -service $service } - if ($nssm_result.rc -ne 0) - { - $result.nssm_error_cmd = $cmd - $result.nssm_error_log = $nssm_result.stderr - Throw "Error updating dependencies for service ""$name""" + "*SERVICE_CONTINUE_PENDING*" { Invoke-NssmStop -service $service; Invoke-NssmStart -service $service } + "*SERVICE_PAUSE_PENDING*" { Invoke-NssmStop -service $service; Invoke-NssmStart -service $service } + "*SERVICE_PAUSED*" { Invoke-NssmStop -service $service; Invoke-NssmStart -service $service } + "*SERVICE_START_PENDING*" { Invoke-NssmStop -service $service; Invoke-NssmStart -service $service } + "*SERVICE_STOP_PENDING*" { Invoke-NssmStop -service $service; Invoke-NssmStart -service $service } + } } - $result.changed_by = "update-dependencies" + $result.changed_by = "start_service" $result.changed = $true } } -Function Nssm-Update-StartMode -{ - [CmdletBinding()] +function Stop-NssmService { + [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Mandatory=$true)] - [string]$name, - [Parameter(Mandatory=$true)] - [string]$mode + [string]$service ) - $cmd = "get ""$name"" Start" - $nssm_result = Nssm-Invoke $cmd + $currentStatus = Get-NssmServiceStatus -service $service - 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""" + if ($currentStatus.rc -ne 0) { + $result.nssm_error_cmd = $currentStatus.arguments + $result.nssm_error_log = $currentStatus.stderr + Fail-Json -obj $result -message "Error stopping service ""$service""" } - $modes=@{"auto" = "SERVICE_AUTO_START"; "delayed" = "SERVICE_DELAYED_AUTO_START"; "manual" = "SERVICE_DEMAND_START"; "disabled" = "SERVICE_DISABLED"} - $mappedMode = $modes.$mode - if ($nssm_result.stdout -notlike "*$mappedMode*") { - $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""" + if ($currentStatus.stdout -notlike "*SERVICE_STOPPED*") { + if ($PSCmdlet.ShouldProcess($service, "Stop service")) { + Invoke-NssmStop -service $service } - $result.changed_by = "start_mode" + $result.changed_by = "stop_service" $result.changed = $true } } -Function Nssm-Get-Status -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string]$name - ) - - $cmd = "status ""$name""" - $nssm_result = Nssm-Invoke $cmd - - return $nssm_result +if (($null -ne $appParameters) -and ($null -ne $appArguments)) { + Fail-Json $result "'app_parameters' and 'arguments' are mutually exclusive but have both been set." } -Function Nssm-Start -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string]$name - ) - - $currentStatus = Nssm-Get-Status -name $name +# Backward compatibility for old parameters style. Remove the block bellow in 2.12 +if ($null -ne $appParameters) { + Add-DeprecationWarning -obj $result -message "The parameter 'app_parameters' will be removed soon, use 'arguments' instead" -version 2.12 - if ($currentStatus.rc -ne 0) - { - $result.nssm_error_cmd = $cmd - $result.nssm_error_log = $currentStatus.stderr - Throw "Error starting service ""$name""" + if ($appParameters -isnot [string]) { + Fail-Json -obj $result -message "The app_parameters parameter must be a string representing a dictionary." } - switch -wildcard ($currentStatus.stdout) - { - "*SERVICE_RUNNING*" { <# Nothing to do #> } - "*SERVICE_STOPPED*" { Nssm-Start-Service-Command -name $name } + # Convert dict-as-string form to list + $escapedAppParameters = $appParameters.TrimStart("@").TrimStart("{").TrimEnd("}").Replace("; ","`n").Replace("\","\\") + $appParametersHash = ConvertFrom-StringData -StringData $escapedAppParameters - "*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 } + $appParamsArray = @() + $appParametersHash.GetEnumerator() | Foreach-Object { + if ($_.Name -ne "_") { + $appParamsArray += $_.Name + } + $appParamsArray += $_.Value } + $appArguments = @($appParamsArray) + + # The rest of the code should use only the new $appArguments variable } -Function Nssm-Start-Service-Command -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string]$name - ) +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 +} +if ($params.ContainsKey('start_mode')) { + Add-DeprecationWarning -obj $result -message "The parameter 'start_mode' will be removed soon, use the win_service module instead" -version 2.12 +} +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) - { - $result.nssm_error_cmd = $cmd - $result.nssm_error_log = $nssm_result.stderr - Throw "Error starting service ""$name""" + if($null -eq $appDirectory) { + $appDirectory = (Get-Item -LiteralPath $application).DirectoryName } - $result.changed_by = "start_service" - $result.changed = $true + if ($user -and -not $password) { + 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) - { - $result.nssm_error_cmd = $cmd - $result.nssm_error_log = $nssm_result.stderr - Throw "Error stopping service ""$name""" + $nssm_result = Invoke-NssmCommand -arguments @("remove", $name, "confirm") + + 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 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" - $result.changed = $true -} + $diff_text_added_prefix = '+' + $result.changed_by = "install_service" + $result.changed = $true + } -Function Nssm-Stop -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string]$name - ) + $diff_text += "$diff_text_added_prefix[$name]`n" - $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) - { - $result.nssm_error_cmd = $cmd - $result.nssm_error_log = $currentStatus.stderr - Throw "Error stopping service ""$name""" - } + Update-NssmServiceParameter -parameter "Application" -value $application @common_params + Update-NssmServiceParameter -parameter "DisplayName" -value $display_name @common_params + Update-NssmServiceParameter -parameter "Description" -value $description @common_params + + 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) - { - $result.nssm_error_cmd = $cmd - $result.nssm_error_log = $nssm_result.stderr - Throw "Error stopping service ""$name""" + Update-NssmServiceParameter -parameter "AppParameters" -value $singleLineParams @common_params } - $result.changed_by = "stop_service" - $result.changed = $true - } -} -Function Nssm-Restart -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string]$name - ) + Update-NssmServiceParameter -parameter "AppStdout" -value $stdoutFile @common_params + Update-NssmServiceParameter -parameter "AppStderr" -value $stderrFile @common_params - 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 -{ - Nssm-Install -name $name -application $application - 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 -} + #set files to overwrite + Update-NssmServiceParameter -parameter "AppStdoutCreationDisposition" -value 2 @common_params + Update-NssmServiceParameter -parameter "AppStderrCreationDisposition" -value 2 @common_params -Try -{ - switch ($state) - { - "absent" { - Nssm-Remove -name $name - } - "present" { - NssmProcedure - } - "started" { - NssmProcedure - Nssm-Start -name $name + #enable file rotation + Update-NssmServiceParameter -parameter "AppRotateFiles" -value 1 @common_params + + #don't rotate until the service restarts + Update-NssmServiceParameter -parameter "AppRotateOnline" -value 0 @common_params + + #both of the below conditions must be met before rotation will happen + #minimum age before rotating + Update-NssmServiceParameter -parameter "AppRotateSeconds" -value 86400 @common_params + + #minimum size before rotating + Update-NssmServiceParameter -parameter "AppRotateBytes" -value 104858 @common_params + + + ############## 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" { - NssmProcedure - Nssm-Stop -name $name + $mappedMode = $start_modes_map.$startMode + Update-NssmServiceParameter -parameter "Start" -value $mappedMode @common_params + if ($state -in "stopped","restarted") { + Stop-NssmService @common_params } - "restarted" { - NssmProcedure - Nssm-Restart -name $name + + if($state -in "started","restarted") { + Start-NssmService @common_params } - } + ######################################################################## - Exit-Json $result + } } -Catch -{ - Fail-Json $result $_.Exception.Message + +if ($diff_mode -and $result.changed -eq $true) { + $result.diff = @{ + prepared = $diff_text + } } + +Exit-Json $result diff --git a/lib/ansible/modules/windows/win_nssm.py b/lib/ansible/modules/windows/win_nssm.py index 5631624430b..9926530dc40 100644 --- a/lib/ansible/modules/windows/win_nssm.py +++ b/lib/ansible/modules/windows/win_nssm.py @@ -15,9 +15,10 @@ DOCUMENTATION = r''' --- module: win_nssm version_added: "2.0" -short_description: NSSM - the Non-Sucking Service Manager +short_description: Install a service using NSSM 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: - "nssm >= 2.24.0 # (install via M(win_chocolatey)) C(win_chocolatey: name=nssm)" options: @@ -29,50 +30,75 @@ options: state: description: - 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 - ansible command module. + - Values C(started), C(stopped), and C(restarted) are deprecated since v2.8, + please use the M(win_service) module instead to start, stop or restart the service. type: str choices: [ absent, present, started, stopped, restarted ] - default: started + default: present application: description: - The application binary to run as a service - - "Specify this whenever the service may need to be installed (state: present, started, stopped, restarted)" - - "Note that 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 commit 0b386fc1984ab74ee59b7bed14b7e8f57212c22b in the nssm.git project for more info: - U(https://git.nssm.cc/?p=nssm.git;a=commit;h=0b386fc1984ab74ee59b7bed14b7e8f57212c22b) + - Required when I(state) is C(present), C(started), C(stopped), or C(restarted). + type: path + executable: + description: + - The location of the NSSM utility (in case it is not located in your PATH). + 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: description: - Path to receive output. - type: str + type: path stderr_file: description: - Path to receive error output. - type: str + type: path app_parameters: description: - 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 - app_parameters_free_form: + arguments: description: - - Single string of parameters to be passed to the service. - - Use either this or C(app_parameters), not both. + - Parameters to be passed to the application when it starts. + - 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 version_added: "2.3" dependencies: description: - 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 user: description: - User to be used for service startup. + - DEPRECATED since v2.8, please use the M(win_service) module instead. type: str password: description: - Password to be used for service startup. + - DEPRECATED since v2.8, please use the M(win_service) module instead. type: str start_mode: description: @@ -80,84 +106,66 @@ options: - 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(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 choices: [ auto, delayed, disabled, manual ] default: auto 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: - Adam Keech (@smadam813) - George Frank (@georgefrank) - Hans-Joachim Kliemeck (@h0nIg) - Michael Wild (@themiwi) + - Kevin Subileau (@ksubileau) ''' EXAMPLES = r''' -# 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 single parameter -# This will yield the following command: C:\windows\\foo.exe bar -- win_nssm: +- name: Install the foo service + 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 -foo false -- win_nssm: - name: foo - application: C:\windows\foo.exe - app_parameters: _=bar; -file=output.bat; -foo=false +# This will yield the following command: C:\windows\foo.exe bar "true" +- name: Install the Consul service with a list of parameters + win_nssm: + name: Consul + application: C:\consul\consul.exe + arguments: + - agent + - -config-dir=C:\consul\config -# Use the single line parameters option to specify an arbitrary string of parameters -# for the service executable -- name: Make sure the Consul service runs +# This is strictly equivalent to the previous example +- name: Install the Consul service with an arbitrary string of parameters win_nssm: - name: consul + name: Consul application: C:\consul\consul.exe - app_parameters_free_form: agent -config-dir=C:\consul\config - stdout_file: C:\consul\log.txt - stderr_file: C:\consul\error.txt + arguments: agent -config-dir=C:\consul\config -# 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 application: C:\windows\foo.exe stdout_file: C:\windows\foo.log stderr_file: C:\windows\foo.log -# Install and start the foo service, but wait for dependencies tcpip and adf -- win_nssm: - name: foo - application: C:\windows\foo.exe - dependencies: 'adf,tcpip' - -# Install and start the foo service with dedicated user -- win_nssm: +- name: Configure and start the foo service using win_service + win_service: name: foo - application: C:\windows\foo.exe + dependencies: [ adf, tcpip ] user: foouser 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 + state: started -# Remove the foo service -- win_nssm: +- name: Remove the foo service + win_nssm: name: foo state: absent ''' diff --git a/test/integration/targets/win_nssm/tasks/tests.yml b/test/integration/targets/win_nssm/tasks/tests.yml index 78ab9b0cdfe..d5d9d389ca7 100644 --- a/test/integration/targets/win_nssm/tasks/tests.yml +++ b/test/integration/targets/win_nssm/tasks/tests.yml @@ -3,11 +3,11 @@ set_fact: test_service_cmd: | $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) { $srvobj | Get-Member -MemberType *Property | % { $res.($_.name) = $srvobj.($_.name) } $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 = @{} $srvkey = "HKLM:\SYSTEM\CurrentControlSet\Services\$service\Parameters" Get-Item "$srvkey" | Select-Object -ExpandProperty property | % { $res.Parameters.$_ = (Get-ItemProperty -Path "$srvkey" -Name $_).$_} @@ -16,6 +16,24 @@ } 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 win_nssm: name: '{{ test_service_name }}' @@ -35,6 +53,7 @@ - (install_service_actual.stdout|from_json).State == 'Stopped' - (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.AppDirectory == "C:\Windows\System32" - name: test install service (idempotent) win_nssm: @@ -55,6 +74,7 @@ - (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).Parameters.Application == "C:\Windows\System32\cmd.exe" + - (install_service_again_actual.stdout|from_json).Parameters.AppDirectory == "C:\Windows\System32" - name: install and start service win_nssm: @@ -75,12 +95,52 @@ - (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).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 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 }}' @@ -98,10 +158,13 @@ that: - install_service_complex.changed == 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).StartMode == 'Manual' - (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.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.AppStderr == test_win_nssm_path + '\\error.txt' - (install_service_complex_actual.stdout|from_json).Dependencies|length == 2 @@ -111,8 +174,11 @@ - name: install and start service with more parameters (idempotent) 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 order should not trigger a change dependencies: 'dnscache,tcpip' user: '{{ test_win_nssm_username }}' @@ -131,70 +197,76 @@ that: - install_service_complex_again.changed == false - (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).StartMode == 'Manual' - (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.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.AppStderr == test_win_nssm_path + '\\error.txt' - (install_service_complex_again_actual.stdout|from_json).Dependencies|length == 2 - '"Tcpip" 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: name: '{{ test_service_name }}' 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 - 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 }}' - 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: that: - - free_params.changed == true - - (free_params_actual.stdout|from_json).Exists == true - - (free_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) - - (free_params_actual.stdout|from_json).Parameters.AppParameters == '-v -Dcom.test.string=value "C:\\with space\\\\"' + - str_params.changed == true + - (str_params_actual.stdout|from_json).Exists == true + - (str_params_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe" + # Expected value: -v -Dtest.str=value "C:\with space\\" (backslashes doubled for jinja) + - (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: name: '{{ test_service_name }}' 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 - 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 }}' - 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: that: - - free_params_again.changed == false - - (free_params_again_actual.stdout|from_json).Exists == true - - (free_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) - - (free_params_again_actual.stdout|from_json).Parameters.AppParameters == '-v -Dcom.test.string=value "C:\\with space\\\\"' - -- name: install service with dict parameters + - str_params_again.changed == false + - (str_params_again_actual.stdout|from_json).Exists == true + - (str_params_again_actual.stdout|from_json).Parameters.Application == "C:\Windows\System32\cmd.exe" + # Expected value: -v -Dtest.str=value "C:\with space\\" (backslashes doubled for jinja) + - (str_params_again_actual.stdout|from_json).Parameters.AppParameters == '-v -Dtest.str=value "C:\\with space\\\\"' + +# deprecated in 2.12 +- name: install service with dict-as-string parameters win_nssm: name: '{{ test_service_name }}' application: C:\Windows\System32\cmd.exe app_parameters: foo=true; -file.out=output.bat; -path=C:\with space\; -str=test"quotes; _=bar 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 }}' 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: that: - 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) - (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: name: '{{ test_service_name }}' application: C:\Windows\System32\cmd.exe app_parameters: foo=true; -file.out=output.bat; -path=C:\with space\; -str=test"quotes; _=bar 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 }}' 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: that: - 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) - (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 win_nssm: name: '{{ test_service_name }}' diff --git a/test/sanity/pslint/ignore.txt b/test/sanity/pslint/ignore.txt index b68890576c3..0e2a1f3823b 100644 --- a/test/sanity/pslint/ignore.txt +++ b/test/sanity/pslint/ignore.txt @@ -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_mapped_drive.ps1 PSCustomUseLiteralPath 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 PSCustomUseLiteralPath lib/ansible/modules/windows/win_package.ps1 PSUseApprovedVerbs