Repository: ansible/ansible
Pull request: Fix win_nssm credentials quoting
##### SUMMARY

Fixes https://github.com/ansible/ansible/issues/48728

The problem is with the escaping of the `$password` variable with single quotes (introduced by PR #26228) which worked in <=2.7.0 when `Invoke-Expression` was being used to invoke nssm. With 2.7.1, nssm is being invoked differently and the escaping no longer works properly. Escaping with double double quotes fixes the issue.

##### ISSUE TYPE

- Bugfix Pull Request

##### COMPONENT NAME
win_nssm

##### ADDITIONAL INFORMATION
None



Problem statement:
Error starting service using win_nssm with user and password parameters and password contains special characters
##### SUMMARY
When running `win_nssm` module with parameters `user` and `password` and the password contains special characters (we have a number of special characters such as `#` `$` `%`) the service will fail to start with `The service did not start due to a logon failure`.

The issue did not occur in ansible version 2.6, only seen in 2.7.

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
win_nssm

##### ANSIBLE VERSION
```
ansible 2.7.1
  config file = REDACTED
  configured module search path = REDACTED
  ansible python module location = /usr/local/lib/python2.7/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.14 (default, Mar 22 2018, 14:43:05) [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)]
```

##### CONFIGURATION
```
ANSIBLE_PIPELINING = True
ANSIBLE_SSH_ARGS = -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
DEFAULT_CALLBACK_WHITELIST = [u'profile_tasks']
DEFAULT_LOAD_CALLBACK_PLUGINS = True
DEFAULT_REMOTE_USER = ansible
DEFAULT_STDOUT_CALLBACK = debug
RETRY_FILES_ENABLED = False
```

##### OS / ENVIRONMENT
Target: Windows Server 2012R2

##### STEPS TO REPRODUCE
Create a win_nssm service with `user` and `password` parameters and have a password with uses special characters including `#` `$` `%`.

```yaml
- name: Test NSSM Service with User and Password
  win_nssm:
    name: 'Test Service'
    state: started
    start_mode: manual
    application: 'C:\my_cmd'
    user: 'mytestuser@my.domain'
    password: 'MyPass12345%$#@!'
```

##### EXPECTED RESULTS
Task should pass with:
changed: [myhost.my.domain]

##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->
Command fails with Error starting service. If you check Event Viewer it states `The service did not start due to a logon failure.`
<!--- Paste verbatim command output between quotes -->
```
fatal: [myhost.my.domain]: FAILED! => {
    "changed": true,
    "changed_by": "start_mode",
    "nssm_app_parameters": null,
    "nssm_error_cmd": "start \"Test Service\"",
    "nssm_error_log": "Test Service: START: The service did not start due to a logon failure.\r\r\n",
}

MSG:

Error starting service "Test Service"

```

Hints from issue discussion:
Hi @shrabok-surge, thank you for submitting this issue!

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: thank_submitter --->
Files identified in the description:
* [lib/ansible/modules/windows/win_nssm.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/windows/win_nssm.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @dagwieers @jborean93 @jhawkesworth @nitzmahone
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Additional note: We can go into `Services` on the target windows host and re-enter the password manually and the service will start successfully.
I'm experiencing similar behavior. I'm pretty sure the cause of it is the below line (406) in `lib/ansible/modules/windows/win_nssm.ps1`

```
$cmd = "set ""$name"" ObjectName $fullUser '$password'"
```

The problem appears to be the escaping of the `$password` variable with single quotes (introduced by PR https://github.com/ansible/ansible/pull/26228) which worked pre 2.7.0 when `Invoke-Expression` was being used to invoke nssm but with 2.7.1 nssm is being invoked differently. As a workaround, I've changed the escaping to use double double quotes like below which seems to work for my situation and matches how the escaping is done in other parts of the file. I'm not sure if this potentially introduces other problems or not.

```
$cmd = "set ""$name"" ObjectName $fullUser ""$password"""
```

Commit message: Fix win_nssm credentials quoting

Fix credential quoting for win_nssm after changes to the way nssm command is invoked in Ansible 2.7.1.

Patch to review:
diff --git a/lib/ansible/modules/windows/win_nssm.ps1 b/lib/ansible/modules/windows/win_nssm.ps1
index 0a9cb420a960d6..09e578c19fe098 100644
--- a/lib/ansible/modules/windows/win_nssm.ps1
+++ b/lib/ansible/modules/windows/win_nssm.ps1
@@ -403,7 +403,7 @@ Function Nssm-Update-Credentials
             }
 
             If ($nssm_result.stdout.split("`n`r")[0] -ne $fullUser) {
-                $cmd = "set ""$name"" ObjectName $fullUser '$password'"
+                $cmd = "set ""$name"" ObjectName $fullUser ""$password"""
                 $nssm_result = Nssm-Invoke $cmd
 
                 if ($nssm_result.rc -ne 0)


Code review comments:
diff_hunk: @@ -403,7 +403,7 @@ Function Nssm-Update-Credentials
             }
 
             If ($nssm_result.stdout.split("`n`r")[0] -ne $fullUser) {
-                $cmd = "set ""$name"" ObjectName $fullUser '$password'"
+                $cmd = "set ""$name"" ObjectName $fullUser ""$password"""
path: lib/ansible/modules/windows/win_nssm.ps1
text: @user1:
Sorry for taking so long to get to review this PR. We have a better way to easily escape a list of arguments so that they are formatted properly when running the command. You can use the cmdlet `$cmd = Argv-ToString -arguments @("set", $name, "ObjectName", $fullUser, $password)`.

The `Argv-ToString` cmdlet is a module util that's provided by https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.ArgvParser.psm1. It takes in a list of arguments and produces a string that has escaped the characters that need to be escaped.

The module also already imports this util with https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/windows/win_nssm.ps1#L9 so you don't have to do that as well :).

@author:
Updated the line to use the Argv-ToString function. Did some minimal testing with the below playbook which seemed to work as intended.

```
---
- hosts:
    - all
  vars:
    testuser_password: Password1234##
  tasks:
    - name: Install NSSM
      win_chocolatey:
        name: "nssm"

    - name: Create test user
      win_user:
        name: testuser
        password: "{{ testuser_password }}"

    - name: Create service script
      win_copy:
        dest: "C:\\Users\\service.bat"
        content: |
          pause
      notify: Restart TestService

    - name: Install service via NSSM
      win_nssm:
        name: TestService
        application: "C:\\Users\\service.bat"
        start_mode: auto
        user: testuser
        password: "{{ testuser_password }}"
      notify: Restart TestService

  handlers:
    - name: Restart TestService
      win_service:
        name: TestService
        state: restarted
```