ansible-collections / ansible.windows

Windows core collection for Ansible
https://galaxy.ansible.com/ansible/windows
GNU General Public License v3.0
249 stars 169 forks source link

win_package: don't wait for uninstall to finish #116

Closed ghost closed 3 years ago

ghost commented 4 years ago
SUMMARY

In my case im tried to install "FusionInventory agent" after removing of previously installed. For removing i use a win_package module with regedit provider and state absent but it's always returns me "changed: true" and don't uninstall software.

I'm tried to debug scripts (win_package.ps1) behavior and got a few guesses about a problem. I think that the root of the problem is in the Ansible.Process.cs. I watched the execution of the task in ansible and through the debug script launched in ISE. In ISE, the execution is successful and the software is removed (in step-by-step mode), but in ansible and ISE in realtime the execution is successful, and the software is not removed. This is due to the fact that the module erroneously returns the result until the moment the software is uninstalled, because uninstall.exe unpack itself in C:\Users\username\AppData\Local\Temp\~nsuA.tmp and starts a new process Un_A.exe, and rc: 0 from the terminated uninstall.exe is returned to the task, after which the runspace is closed and the deletion does not continue. Verbose output look like where is 2 exit codes:

Stopped at: ConvertTo-Json -InputObject $args[0] -Depth 99 -Compress
[DBG]: PS C:\temp\ansible\debug>> 
{"changed":true,"invocation":{"module_args":{"force_basic_auth":false,"url_username":null,"client_cert_password":null,"creates_path":null,"password":null,"creates_version":null,"timeout":30,"use_default_credential":false,"proxy_password":null,"method":nu
ll,"log_path":null,"username":null,"headers":null,"use_proxy":true,"proxy_url":null,"provider":"registry","maximum_redirection":50,"path":null,"url_password":null,"proxy_username":null,"product_id":"FusionInventory-Agent","validate_certs":true,"proxy_use
_default_credential":false,"follow_redirects":"safe","expected_return_code":[0,3010],"chdir":null,"arguments":"/S","state":"absent","http_agent":"ansible-httpget","client_cert":null,"creates_service":null}},"reboot_required":false,"rc":0,"failed":false}

Stopped at: Set-Variable -Name LASTEXITCODE -Value $args[0] -Scope Global; exit $args[0]
[DBG]: PS C:\temp\ansible\debug>> 

Stopped at: Set-Variable -Name LASTEXITCODE -Value $args[0] -Scope Global; exit $args[0]

First from Uninstall.exe and second from target Un_A.exe

My csharp skills is very low and i cant solve the problem after it calls this in Ansible.ModuleUtils.CommandUtil.psm1:

$command_result = [Ansible.Process.ProcessUtil]::CreateProcess($executable, $command, $working_directory, $environment, $stdin, $output_encoding_override)

plz help to fix this little problem.

now I work around the problem using the following set of crutches and it`s works :)

- name: Obtain information about a registry key using short form
  ansible.windows.win_reg_stat:
    path: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\FusionInventory-Agent
  register: regedit_installed

- win_shell: |
    Start-Process -Wait -FilePath "{{ regedit_installed.properties.UninstallString.value }}" -ArgumentList "/S"
  when: regedit_installed.properties.UninstallString.value is defined

I found some problems in win_package module during research:

  1. #AnsibleRequires -CSharpUtil Ansible.Process was ommited (line: 8)

before:

# AccessToken should be removed once the username/password options are gone
#AnsibleRequires -CSharpUtil Ansible.AccessToken

#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.AddType
#Requires -Module Ansible.ModuleUtils.ArgvParser
#Requires -Module Ansible.ModuleUtils.CommandUtil
#AnsibleRequires -PowerShell ..module_utils.WebRequest

after:

# AccessToken should be removed once the username/password options are gone
#AnsibleRequires -CSharpUtil Ansible.AccessToken

#AnsibleRequires -CSharpUtil Ansible.Basic
#AnsibleRequires -CSharpUtil Ansible.Process
#Requires -Module Ansible.ModuleUtils.AddType
#Requires -Module Ansible.ModuleUtils.ArgvParser
#Requires -Module Ansible.ModuleUtils.CommandUtil
#Requires -Module Ansible.ModuleUtils.WebRequest
  1. Wrong function Get-AnsibleWindowsWebRequestSpec (line: 1295) Whre is no function in Ansible.ModuleUtils.WebRequest with this name, right name is: Get-AnsibleWebRequestSpec

before:

$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec, @(Get-AnsibleWindowsWebRequestSpec))

after:

$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec, @(Get-AnsibleWebRequestSpec))
ISSUE TYPE
COMPONENT NAME

win_package Ansible.Process

ANSIBLE VERSION
ansible 2.10.2
  config file = None
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.6/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.6.8 (default, Apr  2 2020, 13:34:55) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
CONFIGURATION
none
OS / ENVIRONMENT

Windows 2019 Std

STEPS TO REPRODUCE
- name: Remove a previously installed FusionInventory agent
  ansible.windows.win_package:
    product_id: FusionInventory-Agent
    arguments: /S
    provider: registry
    state: absent
EXPECTED RESULTS

InventoryAgent must be absent

ACTUAL RESULTS

Task works 1sec with "changed: true" but without result.

changed: [win-host1] => {
    "changed": true,
    "invocation": {
        "module_args": {
            "arguments": "/S",
            "chdir": null,
            "client_cert": null,
            "client_cert_password": null,
            "creates_path": null,
            "creates_service": null,
            "creates_version": null,
            "expected_return_code": [
                0,
                3010
            ],
            "follow_redirects": "safe",
            "force_basic_auth": false,
            "headers": null,
            "http_agent": "ansible-httpget",
            "log_path": null,
            "maximum_redirection": 50,
            "method": null,
            "password": null,
            "path": null,
            "product_id": "FusionInventory-Agent",
            "provider": "registry",
            "proxy_password": null,
            "proxy_url": null,
            "proxy_use_default_credential": false,
            "proxy_username": null,
            "state": "absent",
            "timeout": 30,
            "url_password": null,
            "url_username": null,
            "use_default_credential": false,
            "use_proxy": true,
            "username": null,
            "validate_certs": true
        }
    },
    "rc": 0,
    "reboot_required": false
}

sorry for my english :)

jborean93 commented 4 years ago

AnsibleRequires -CSharpUtil Ansible.Process was ommited (line: 8)

That's because we are importing the PowerShell util #Requires -Module Ansible.ModuleUtils.CommandUtil which then has the Ansible.Process requirement https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1#L4.

Wrong function Get-AnsibleWindowsWebRequestSpec (line: 1295) Whre is no function in Ansible.ModuleUtils.WebRequest with this name, right name is: Get-AnsibleWebRequestSpec

Get-AnsibleWindowsWebRequestSpec is part of the WebRequest.psm1 module included in the ansible.windows collection. That's why the import is a relative import #AnsibleRequires -PowerShell ..module_utils.WebRequest.

The Get-AnsibleWebRequestSpec function is part of Ansible.ModuleUtils.WebRequest.psm1 which is no longer used by win_package. It sounds like you might have a mixture of different versions in play, the code as it is today should be fine. We even test it in CI.

Now for the problem at hand, the issue here is that the FusionInventory-Agent is kicking off a detached process and exiting the main process causing win_package to think that it has completed and everything is fine. When win_package exits it also closes the WinRM shell which then kills any remaining child processes that might be running in the background. This is why I believe the module is reporting a change and that it didn't fail but the program doesn't actually get uninstalled.

The fix for this is going to be difficult to debug. I'm trying to see if I can install the package myself and replicate the issue before commenting anymore.

sorry for my english :)

No need to apologise about that, I can only speak 1 language so you are ahead of me in that regard :)

jborean93 commented 4 years ago

Ok I've been able to replicate the problem, the uninstaller at the registry points to C:\Program Files\FusionInventory-Agent\Uninstall.exe which win_package starts. The uninstaller then immediately runs a new executable, which I assume is auto generated, at C:\Users\<username>\AppData\Local\Temp\<random>\Un_A.exe which does the actual work. Because the original Uninstall.exe has finished and exited with a return code of 0, win_package interprets this as everything ran fine and exits normally. When the module finishes, Ansible will close the WinRM shell which then kills any child processes stopping the uninstall from actually continuing in the background.

I found that the -Wait parameter on Start-Process has some more advanced logic for checking if any child process has finished than the simple check that win_package does. I have an idea of how we could also do the same thing but it does have some caveats

The 2nd scenario may be something I just add as a configurable wait to allow for both scenarios but I will keep you updated as I play around with it some more.

jborean93 commented 4 years ago

I've got a draft PR that should fix this problem for you. It adds the wait_for_children option to win_package and when testing it manually with the installer you mentioned it worked as expected. I still need to add some more test cases for this before it is merged but feel free to try it out when you get a chance.