diff --git a/lib/ansible/modules/windows/win_service.ps1 b/lib/ansible/modules/windows/win_service.ps1 index 5f568d1bf76..e95964e156e 100644 --- a/lib/ansible/modules/windows/win_service.ps1 +++ b/lib/ansible/modules/windows/win_service.ps1 @@ -22,19 +22,20 @@ $ErrorActionPreference = "Stop" $params = Parse-Args $args -supports_check_mode $true -$check_mode = Get-AnsibleParam -obj $params "_ansible_check_mode" -type "bool" -default $false - -$dependencies = Get-AnsibleParam -obj $params -name 'dependencies' -failifempty $false -default $null -$dependency_action = Get-AnsibleParam -obj $params -name 'dependency_action' -failifempty $false -ValidateSet 'add', 'remove', 'set' -default 'set' -$description = Get-AnsibleParam -obj $params -name 'description' -failifempty $false -default $null -$desktop_interact = Get-AnsibleParam -obj $params -name 'desktop_interact' -type "bool" -failifempty $false -default $false -$display_name = Get-AnsibleParam -obj $params -name 'display_name' -failifempty $false -default $null -$name = Get-AnsibleParam -obj $params -name 'name' -failifempty $true -$password = Get-AnsibleParam -obj $params -name 'password' -failifempty $false -default $null -$path = Get-AnsibleParam -obj $params -name 'path' -failifempty $false -default $null -$start_mode = Get-AnsibleParam -obj $params -name 'start_mode' -failifempty $false -ValidateSet 'auto', 'manual', 'disabled', 'delayed' -$state = Get-AnsibleParam -obj $params -name 'state' -failifempty $false -ValidateSet 'started', 'stopped', 'restarted', 'absent' -$username = Get-AnsibleParam -obj $params -name 'username' -failifempty $false -default $null +$check_mode = Get-AnsibleParam -obj $params -name '_ansible_check_mode' -type 'bool' -default $false + +$dependencies = Get-AnsibleParam -obj $params -name 'dependencies' -default $null +$dependency_action = Get-AnsibleParam -obj $params -name 'dependency_action' -type 'str' -default 'set' -validateset 'add','remove','set' +$description = Get-AnsibleParam -obj $params -name 'description' -type 'str' +$desktop_interact = Get-AnsibleParam -obj $params -name 'desktop_interact' -type 'bool' -default $false +$display_name = Get-AnsibleParam -obj $params -name 'display_name' -type 'str' +$force_dependent_services = Get-AnsibleParam -obj $params -name 'force_dependent_services' -type 'bool' -default $false +$name = Get-AnsibleParam -obj $params -name 'name' -type 'str' -failifempty $true +$password = Get-AnsibleParam -obj $params -name 'password' -type 'str' +$path = Get-AnsibleParam -obj $params -name 'path' -type 'path' +$start_mode = Get-AnsibleParam -obj $params -name 'start_mode' -type 'str' -validateset 'auto','manual','disabled','delayed' +$state = Get-AnsibleParam -obj $params -name 'state' -type 'str' -validateset 'started','stopped','restarted','absent' +$username = Get-AnsibleParam -obj $params -name 'username' -type 'str' $result = @{ changed = $false @@ -68,11 +69,11 @@ Function Get-ServiceInfo($name) { $actual_start_mode = 'delayed' } - $existing_depenencies = @() + $existing_dependencies = @() $existing_depended_by = @() if ($svc.ServicesDependedOn.Count -gt 0) { foreach ($dependency in $svc.ServicesDependedOn.Name) { - $existing_depenencies += $dependency + $existing_dependencies += $dependency } } if ($svc.DependentServices.Count -gt 0) { @@ -80,6 +81,10 @@ Function Get-ServiceInfo($name) { $existing_depended_by += $dependency } } + $description = $wmi_svc.Description + if ($description -eq $null) { + $description = "" + } $result.exists = $true $result.name = $svc.Name @@ -87,10 +92,10 @@ Function Get-ServiceInfo($name) { $result.state = $svc.Status.ToString().ToLower() $result.start_mode = $actual_start_mode $result.path = $wmi_svc.PathName - $result.description = $wmi_svc.Description + $result.description = $description $result.username = $wmi_svc.startname $result.desktop_interact = (ConvertTo-Bool $wmi_svc.DesktopInteract) - $result.dependencies = $existing_depenencies + $result.dependencies = $existing_dependencies $result.depended_by = $existing_depended_by } @@ -99,25 +104,25 @@ Function Get-WmiErrorMessage($return_value) { switch ($return_value) { 1 { "Not Supported: The request is not supported" } 2 { "Access Denied: The user did not have the necessary access" } - 3 { "Dependent Servies Running: The service cannot be stopped because other services that are running are dependent on it" } - 4 { "Invalid Service Control: Thre requested control code is not valid, or it is unacceptable to the service" } - 5 { "The requested control code cannot be sent to the service because the state of the service is equal to 0, 1, or 2" } - 6 { "Service Not Ative: The service has not been started" } - 7 { "Service Request Timeout: The service did not response to the start request in a timely fashion" } + 3 { "Dependent Services Running: The service cannot be stopped because other services that are running are dependent on it" } + 4 { "Invalid Service Control: The requested control code is not valid, or it is unacceptable to the service" } + 5 { "Service Cannot Accept Control: The requested control code cannot be sent to the service because the state of the service (Win32_BaseService.State property) is equal to 0, 1, or 2" } + 6 { "Service Not Active: The service has not been started" } + 7 { "Service Request Timeout: The service did not respond to the start request in a timely fashion" } 8 { "Unknown Failure: Unknown failure when starting the service" } - 9 { "Path Not Found: THe directory path to the service executable file was not found" } + 9 { "Path Not Found: The directory path to the service executable file was not found" } 10 { "Service Already Running: The service is already running" } 11 { "Service Database Locked: The database to add a new service is locked" } 12 { "Service Dependency Deleted: A dependency this service relies on has been removed from the system" } 13 { "Service Dependency Failure: The service failed to find the service needed from a dependent service" } - 14 { "Servoce Disabled: The service has been disbaled from the system" } - 15 { "Service Logon Failed: The service does not have the correct authentication to run on this system" } + 14 { "Service Disabled: The service has been disabled from the system" } + 15 { "Service Logon Failed: The service does not have the correct authentication to run on the system" } 16 { "Service Marked For Deletion: This service is being removed from the system" } 17 { "Service No Thread: The service has no execution thread" } - 18 { "Status Circular Dependecy: The service has circular dependencies when it starts" } + 18 { "Status Circular Dependency: The service has circular dependencies when it starts" } 19 { "Status Duplicate Name: A service is running under the same name" } - 20 { "Status Invalid Name: The service name has invalide characters" } - 21 { "Status Invalid Paramter: Invalid paramters have been passed to the service" } + 20 { "Status Invalid Name: The service name has invalid characters" } + 21 { "Status Invalid Parameter: Invalid parameters have been passed to the service" } 22 { "Status Invalid Service Account: The account under which this service runs is either invalid or lacks the permissions to run the service" } 23 { "Status Service Exists: The service exists in the database of services available from the system" } 24 { "Service Already Paused: The service is currently paused in the system" } @@ -140,33 +145,19 @@ Function Set-ServiceStartMode($svc, $start_mode) { if ($result.start_mode -ne $start_mode) { try { $delayed_key = "HKLM:\System\CurrentControlSet\Services\$($svc.Name)" - - if ($check_mode) { - # Original start up type was auto (delayed) and we want auto, need to removed delayed key - if ($start_mode -eq 'auto' -and $result.start_mode -eq 'delayed') { - Set-ItemProperty -Path $delayed_key -Name "DelayedAutostart" -Value 0 -Type DWORD -WhatIf - # Original start up type was auto and we want auto (delayed), need to add delayed key - } elseif ($start_mode -eq 'delayed' -and $result.start_mode -eq 'auto') { - Set-ItemProperty -Path $delayed_key -Name "DelayedAutostart" -Value 1 -Type DWORD -WhatIf - # Original start up type was not auto or auto (delayed), need to change to auto and add delayed key - } elseif ($start_mode -eq 'delayed') { - $svc | Set-Service -StartupType "auto" -WhatIf - Set-ItemProperty -Path $delayed_key -Name "DelayedAutostart" -Value 1 -Type DWORD -WhatIf - # Original start up type was not what we were looking for, just change to that type - } else { - $svc | Set-Service -StartupType $start_mode -WhatIf - } + # Original start up type was auto (delayed) and we want auto, need to removed delayed key + if ($start_mode -eq 'auto' -and $result.start_mode -eq 'delayed') { + Set-ItemProperty -Path $delayed_key -Name "DelayedAutostart" -Value 0 -Type DWORD -WhatIf:$check_mode + # Original start up type was auto and we want auto (delayed), need to add delayed key + } elseif ($start_mode -eq 'delayed' -and $result.start_mode -eq 'auto') { + Set-ItemProperty -Path $delayed_key -Name "DelayedAutostart" -Value 1 -Type DWORD -WhatIf:$check_mode + # Original start up type was not auto or auto (delayed), need to change to auto and add delayed key + } elseif ($start_mode -eq 'delayed') { + $svc | Set-Service -StartupType "auto" -WhatIf:$check_mode + Set-ItemProperty -Path $delayed_key -Name "DelayedAutostart" -Value 1 -Type DWORD -WhatIf:$check_mode + # Original start up type was not what we were looking for, just change to that type } else { - if ($start_mode -eq 'auto' -and $result.start_mode -eq 'delayed') { - Set-ItemProperty -Path $delayed_key -Name "DelayedAutostart" -Value 0 -Type DWORD - } elseif ($start_mode -eq 'delayed' -and $result.start_mode -eq 'auto') { - Set-ItemProperty -Path $delayed_key -Name "DelayedAutostart" -Value 1 -Type DWORD - } elseif ($start_mode -eq 'delayed') { - $svc | Set-Service -StartupType "auto" - Set-ItemProperty -Path $delayed_key -Name "DelayedAutostart" -Value 1 -Type DWORD - } else { - $svc | Set-Service -StartupType $start_mode - } + $svc | Set-Service -StartupType $start_mode -WhatIf:$check_mode } } catch { Fail-Json $result $_.Exception.Message @@ -206,11 +197,7 @@ Function Set-ServiceDesktopInteract($wmi_svc, $desktop_interact) { Function Set-ServiceDisplayName($svc, $display_name) { if ($result.display_name -ne $display_name) { try { - if ($check_mode) { - $svc | Set-Service -DisplayName $display_name -WhatIf - } else { - $svc | Set-Service -DisplayName $display_name - } + $svc | Set-Service -DisplayName $display_name -WhatIf:$check_mode } catch { Fail-Json $result $_.Exception.Message } @@ -222,11 +209,7 @@ Function Set-ServiceDisplayName($svc, $display_name) { Function Set-ServiceDescription($svc, $description) { if ($result.description -ne $description) { try { - if ($check_mode) { - $svc | Set-Service -Description $description -WhatIf - } else { - $svc | Set-Service -Description $description - } + $svc | Set-Service -Description $description -WhatIf:$check_mode } catch { Fail-Json $result $_.Exception.Message } @@ -238,11 +221,7 @@ Function Set-ServiceDescription($svc, $description) { Function Set-ServicePath($name, $path) { if ($result.path -ne $path) { try { - if ($check_mode) { - Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Services\$name" -Name ImagePath -Value $path -WhatIf - } else { - Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Services\$name" -Name ImagePath -Value $path - } + Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Services\$name" -Name ImagePath -Value $path -WhatIf:$check_mode } catch { Fail-Json $result $_.Exception.Message } @@ -299,11 +278,7 @@ Function Set-ServiceDependencies($wmi_svc, $dependency_action, $dependencies) { Function Set-ServiceState($svc, $wmi_svc, $state) { if ($state -eq "started" -and $result.state -ne "running") { try { - if ($check_mode) { - Start-Service -Name $svc.Name -WhatIf - } else { - Start-Service -Name $svc.Name - } + Start-Service -Name $svc.Name -WhatIf:$check_mode } catch { Fail-Json $result $_.Exception.Message } @@ -313,11 +288,7 @@ Function Set-ServiceState($svc, $wmi_svc, $state) { if ($state -eq "stopped" -and $result.state -ne "stopped") { try { - if ($check_mode) { - Stop-Service -Name $svc.Name -Force -WhatIf - } else { - Stop-Service -Name $svc.Name -Force - } + Stop-Service -Name $svc.Name -Force:$force_dependent_services -WhatIf:$check_mode } catch { Fail-Json $result $_.Exception.Message } @@ -327,11 +298,7 @@ Function Set-ServiceState($svc, $wmi_svc, $state) { if ($state -eq "restarted") { try { - if ($check_mode) { - Restart-Service -Name $svc.Name -Force -WhatIf - } else { - Restart-Service -Name $svc.Name -Force - } + Restart-Service -Name $svc.Name -Force:$force_dependent_services -WhatIf:$check_mode } catch { Fail-Json $result $_.Exception.Message } @@ -341,11 +308,7 @@ Function Set-ServiceState($svc, $wmi_svc, $state) { if ($state -eq "absent") { try { - if ($check_mode) { - Stop-Service -Name $svc.Name -Force -WhatIf - } else { - Stop-Service -Name $svc.Name -Force - } + Stop-Service -Name $svc.Name -Force:$force_dependent_services -WhatIf:$check_mode } catch { Fail-Json $result $_.Exception.Message } @@ -409,11 +372,7 @@ if ($svc) { # Check if path is defined, if so create the service if ($path -ne $null) { try { - if ($check_mode) { - New-Service -Name $name -BinaryPathname $path -WhatIf - } else { - New-Service -Name $name -BinaryPathname $path - } + New-Service -Name $name -BinaryPathname $path -WhatIf:$check_mode } catch { Fail-Json $result $_.Exception.Message } diff --git a/lib/ansible/modules/windows/win_service.py b/lib/ansible/modules/windows/win_service.py index ae98323edcb..6c51e1d3653 100644 --- a/lib/ansible/modules/windows/win_service.py +++ b/lib/ansible/modules/windows/win_service.py @@ -41,7 +41,6 @@ options: service. - This works by C(dependency_action) to either add/remove or set the services in this list. - required: False version_added: "2.3" dependency_action: description: @@ -50,7 +49,6 @@ options: - Remove the dependencies to the existing dependencies. - Set the dependencies to only the values in the list replacing the existing dependencies. - required: False default: set choices: - set @@ -61,18 +59,23 @@ options: description: - Whether to allow the service user to interact with the desktop. - This should only be set to true when using the LocalSystem username. - required: False default: False version_added: "2.3" description: description: - The description to set for the service. - required: False version_added: "2.3" display_name: description: - The display name to set for the service. - required: False + version_added: "2.3" + force_dependent_services: + description: + - If True, stopping or restarting a service with dependent services will + force the dependent services to stop or restart also. + - If False, stopping or restarting a service with dependent services may + fail. + default: False version_added: "2.3" name: description: @@ -81,7 +84,6 @@ options: path: description: - The path to the executable to set for the service. - required: False version_added: "2.3" password: description: @@ -89,13 +91,11 @@ options: - This and the C(username) argument must be supplied together. - If specifying LocalSystem, NetworkService or LocalService this field must be an empty string and not null. - required: False version_added: "2.3" start_mode: description: - Set the startup type for the service. - C(delayed) added in Ansible 2.3 - required: false choices: - auto - manual @@ -107,7 +107,6 @@ options: commands unless necessary. - C(restarted) will always bounce the service. - C(absent) added in Ansible 2.3 - required: false choices: - started - stopped @@ -117,7 +116,6 @@ options: description: - The username to set the service to start as. - This and the C(password) argument must be supplied together. - required: False version_added: "2.3" author: "Chris Hoffman (@chrishoffman)" ''' diff --git a/test/integration/targets/win_service/tasks/main.yml b/test/integration/targets/win_service/tasks/main.yml index 51f0cb50e50..57a4ae06188 100644 --- a/test/integration/targets/win_service/tasks/main.yml +++ b/test/integration/targets/win_service/tasks/main.yml @@ -19,6 +19,7 @@ - name: remove the dummy test services if it is left over from previous tests win_service: name: "{{item}}" + force_dependent_services: True state: absent with_items: - "{{test_win_service_name}}" @@ -775,6 +776,7 @@ - name: make sure all services are stopped, set to LocalSystem and set to auto start before next test win_service: name: "{{item}}" + force_dependent_services: True state: stopped start_mode: auto username: LocalSystem @@ -817,13 +819,21 @@ - "win_service_parent1_stat.depended_by == ['TestServiceDependency']" - "win_service_parent2_stat.depended_by == ['TestServiceDependency']" -- name: remove the service +- name: fail to remove service with dependencies win_service: name: "{{test_win_service_name}}" state: absent + register: win_service_removed_failed + failed_when: win_service_removed_failed.msg != "Cannot stop service 'Test Service New (TestService)' because it has dependent services. It can only be stopped if the Force flag is set." + +- name: remove the service while ignoring dependencies + win_service: + name: "{{test_win_service_name}}" + force_dependent_services: True + state: absent register: win_service_removed -- name: check that removing the service succeeds with changes +- name: check that removing the service while ignoring dependencies succeeds with changes assert: that: - "win_service_removed|changed" @@ -839,6 +849,7 @@ - name: make sure all services are removed in the end win_service: name: "{{item}}" + force_dependent_services: True state: absent with_items: - "{{test_win_service_name}}"