mirror of https://github.com/ansible/ansible.git
Migrated to community.windows
parent
40218535ee
commit
bb3494356a
@ -1,520 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Jordan Borean <jborean93@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: psexec
|
||||
short_description: Runs commands on a remote Windows host based on the PsExec
|
||||
model
|
||||
version_added: "2.6"
|
||||
description:
|
||||
- Runs a remote command from a Linux host to a Windows host without WinRM being
|
||||
set up.
|
||||
- Can be run on the Ansible controller to bootstrap Windows hosts to get them
|
||||
ready for WinRM.
|
||||
options:
|
||||
hostname:
|
||||
description:
|
||||
- The remote Windows host to connect to, can be either an IP address or a
|
||||
hostname.
|
||||
type: str
|
||||
required: yes
|
||||
connection_username:
|
||||
description:
|
||||
- The username to use when connecting to the remote Windows host.
|
||||
- This user must be a member of the C(Administrators) group of the Windows
|
||||
host.
|
||||
- Required if the Kerberos requirements are not installed or the username
|
||||
is a local account to the Windows host.
|
||||
- Can be omitted to use the default Kerberos principal ticket in the
|
||||
local credential cache if the Kerberos library is installed.
|
||||
- If I(process_username) is not specified, then the remote process will run
|
||||
under a Network Logon under this account.
|
||||
type: str
|
||||
connection_password:
|
||||
description:
|
||||
- The password for I(connection_user).
|
||||
- Required if the Kerberos requirements are not installed or the username
|
||||
is a local account to the Windows host.
|
||||
- Can be omitted to use a Kerberos principal ticket for the principal set
|
||||
by I(connection_user) if the Kerberos library is installed and the
|
||||
ticket has already been retrieved with the C(kinit) command before.
|
||||
type: str
|
||||
port:
|
||||
description:
|
||||
- The port that the remote SMB service is listening on.
|
||||
type: int
|
||||
default: 445
|
||||
encrypt:
|
||||
description:
|
||||
- Will use SMB encryption to encrypt the SMB messages sent to and from the
|
||||
host.
|
||||
- This requires the SMB 3 protocol which is only supported from Windows
|
||||
Server 2012 or Windows 8, older versions like Windows 7 or Windows Server
|
||||
2008 (R2) must set this to C(no) and use no encryption.
|
||||
- When setting to C(no), the packets are in plaintext and can be seen by
|
||||
anyone sniffing the network, any process options are included in this.
|
||||
type: bool
|
||||
default: yes
|
||||
connection_timeout:
|
||||
description:
|
||||
- The timeout in seconds to wait when receiving the initial SMB negotiate
|
||||
response from the server.
|
||||
type: int
|
||||
default: 60
|
||||
executable:
|
||||
description:
|
||||
- The executable to run on the Windows host.
|
||||
type: str
|
||||
required: yes
|
||||
arguments:
|
||||
description:
|
||||
- Any arguments as a single string to use when running the executable.
|
||||
type: str
|
||||
working_directory:
|
||||
description:
|
||||
- Changes the working directory set when starting the process.
|
||||
type: str
|
||||
default: C:\Windows\System32
|
||||
asynchronous:
|
||||
description:
|
||||
- Will run the command as a detached process and the module returns
|
||||
immediately after starting the process while the process continues to
|
||||
run in the background.
|
||||
- The I(stdout) and I(stderr) return values will be null when this is set
|
||||
to C(yes).
|
||||
- The I(stdin) option does not work with this type of process.
|
||||
- The I(rc) return value is not set when this is C(yes)
|
||||
type: bool
|
||||
default: no
|
||||
load_profile:
|
||||
description:
|
||||
- Runs the remote command with the user's profile loaded.
|
||||
type: bool
|
||||
default: yes
|
||||
process_username:
|
||||
description:
|
||||
- The user to run the process as.
|
||||
- This can be set to run the process under an Interactive logon of the
|
||||
specified account which bypasses limitations of a Network logon used when
|
||||
this isn't specified.
|
||||
- If omitted then the process is run under the same account as
|
||||
I(connection_username) with a Network logon.
|
||||
- Set to C(System) to run as the builtin SYSTEM account, no password is
|
||||
required with this account.
|
||||
- If I(encrypt) is C(no), the username and password are sent as a simple
|
||||
XOR scrambled byte string that is not encrypted. No special tools are
|
||||
required to get the username and password just knowledge of the protocol.
|
||||
type: str
|
||||
process_password:
|
||||
description:
|
||||
- The password for I(process_username).
|
||||
- Required if I(process_username) is defined and not C(System).
|
||||
type: str
|
||||
integrity_level:
|
||||
description:
|
||||
- The integrity level of the process when I(process_username) is defined
|
||||
and is not equal to C(System).
|
||||
- When C(default), the default integrity level based on the system setup.
|
||||
- When C(elevated), the command will be run with Administrative rights.
|
||||
- When C(limited), the command will be forced to run with
|
||||
non-Administrative rights.
|
||||
type: str
|
||||
choices:
|
||||
- limited
|
||||
- default
|
||||
- elevated
|
||||
default: default
|
||||
interactive:
|
||||
description:
|
||||
- Will run the process as an interactive process that shows a process
|
||||
Window of the Windows session specified by I(interactive_session).
|
||||
- The I(stdout) and I(stderr) return values will be null when this is set
|
||||
to C(yes).
|
||||
- The I(stdin) option does not work with this type of process.
|
||||
type: bool
|
||||
default: no
|
||||
interactive_session:
|
||||
description:
|
||||
- The Windows session ID to use when displaying the interactive process on
|
||||
the remote Windows host.
|
||||
- This is only valid when I(interactive) is C(yes).
|
||||
- The default is C(0) which is the console session of the Windows host.
|
||||
type: int
|
||||
default: 0
|
||||
priority:
|
||||
description:
|
||||
- Set the command's priority on the Windows host.
|
||||
- See U(https://msdn.microsoft.com/en-us/library/windows/desktop/ms683211.aspx)
|
||||
for more details.
|
||||
type: str
|
||||
choices:
|
||||
- above_normal
|
||||
- below_normal
|
||||
- high
|
||||
- idle
|
||||
- normal
|
||||
- realtime
|
||||
default: normal
|
||||
show_ui_on_logon_screen:
|
||||
description:
|
||||
- Shows the process UI on the Winlogon secure desktop when
|
||||
I(process_username) is C(System).
|
||||
type: bool
|
||||
default: no
|
||||
process_timeout:
|
||||
description:
|
||||
- The timeout in seconds that is placed upon the running process.
|
||||
- A value of C(0) means no timeout.
|
||||
type: int
|
||||
default: 0
|
||||
stdin:
|
||||
description:
|
||||
- Data to send on the stdin pipe once the process has started.
|
||||
- This option has no effect when I(interactive) or I(asynchronous) is
|
||||
C(yes).
|
||||
type: str
|
||||
requirements:
|
||||
- pypsexec
|
||||
- smbprotocol[kerberos] for optional Kerberos authentication
|
||||
notes:
|
||||
- This module requires the Windows host to have SMB configured and enabled,
|
||||
and port 445 opened on the firewall.
|
||||
- This module will wait until the process is finished unless I(asynchronous)
|
||||
is C(yes), ensure the process is run as a non-interactive command to avoid
|
||||
infinite hangs waiting for input.
|
||||
- The I(connection_username) must be a member of the local Administrator group
|
||||
of the Windows host. For non-domain joined hosts, the
|
||||
C(LocalAccountTokenFilterPolicy) should be set to C(1) to ensure this works,
|
||||
see U(https://support.microsoft.com/en-us/help/951016/description-of-user-account-control-and-remote-restrictions-in-windows).
|
||||
- For more information on this module and the various host requirements, see
|
||||
U(https://github.com/jborean93/pypsexec).
|
||||
seealso:
|
||||
- module: raw
|
||||
- module: win_command
|
||||
- module: win_psexec
|
||||
- module: win_shell
|
||||
author:
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Run a cmd.exe command
|
||||
psexec:
|
||||
hostname: server
|
||||
connection_username: username
|
||||
connection_password: password
|
||||
executable: cmd.exe
|
||||
arguments: /c echo Hello World
|
||||
|
||||
- name: Run a PowerShell command
|
||||
psexec:
|
||||
hostname: server.domain.local
|
||||
connection_username: username@DOMAIN.LOCAL
|
||||
connection_password: password
|
||||
executable: powershell.exe
|
||||
arguments: Write-Host Hello World
|
||||
|
||||
- name: Send data through stdin
|
||||
psexec:
|
||||
hostname: 192.168.1.2
|
||||
connection_username: username
|
||||
connection_password: password
|
||||
executable: powershell.exe
|
||||
arguments: '-'
|
||||
stdin: |
|
||||
Write-Host Hello World
|
||||
Write-Error Error Message
|
||||
exit 0
|
||||
|
||||
- name: Run the process as a different user
|
||||
psexec:
|
||||
hostname: server
|
||||
connection_user: username
|
||||
connection_password: password
|
||||
executable: whoami.exe
|
||||
arguments: /all
|
||||
process_username: anotheruser
|
||||
process_password: anotherpassword
|
||||
|
||||
- name: Run the process asynchronously
|
||||
psexec:
|
||||
hostname: server
|
||||
connection_username: username
|
||||
connection_password: password
|
||||
executable: cmd.exe
|
||||
arguments: /c rmdir C:\temp
|
||||
asynchronous: yes
|
||||
|
||||
- name: Use Kerberos authentication for the connection (requires smbprotocol[kerberos])
|
||||
psexec:
|
||||
hostname: host.domain.local
|
||||
connection_username: user@DOMAIN.LOCAL
|
||||
executable: C:\some\path\to\executable.exe
|
||||
arguments: /s
|
||||
|
||||
- name: Disable encryption to work with WIndows 7/Server 2008 (R2)
|
||||
psexec:
|
||||
hostanme: windows-pc
|
||||
connection_username: Administrator
|
||||
connection_password: Password01
|
||||
encrypt: no
|
||||
integrity_level: elevated
|
||||
process_username: Administrator
|
||||
process_password: Password01
|
||||
executable: powershell.exe
|
||||
arguments: (New-Object -ComObject Microsoft.Update.Session).CreateUpdateInstaller().IsBusy
|
||||
|
||||
- name: Download and run ConfigureRemotingForAnsible.ps1 to setup WinRM
|
||||
psexec:
|
||||
hostname: '{{ hostvars[inventory_hostname]["ansible_host"] | default(inventory_hostname) }}'
|
||||
connection_username: '{{ ansible_user }}'
|
||||
connection_password: '{{ ansible_password }}'
|
||||
encrypt: yes
|
||||
executable: powershell.exe
|
||||
arguments: '-'
|
||||
stdin: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$sec_protocols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault
|
||||
$sec_protocols = $sec_protocols -bor [Net.SecurityProtocolType]::Tls12
|
||||
[Net.ServicePointManager]::SecurityProtocol = $sec_protocols
|
||||
$url = "https://github.com/ansible/ansible/raw/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"
|
||||
Invoke-Expression ((New-Object Net.WebClient).DownloadString($url))
|
||||
exit
|
||||
delegate_to: localhost
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
msg:
|
||||
description: Any exception details when trying to run the process
|
||||
returned: module failed
|
||||
type: str
|
||||
sample: 'Received exception from remote PAExec service: Failed to start "invalid.exe". The system cannot find the file specified. [Err=0x2, 2]'
|
||||
stdout:
|
||||
description: The stdout from the remote process
|
||||
returned: success and interactive or asynchronous is 'no'
|
||||
type: str
|
||||
sample: Hello World
|
||||
stderr:
|
||||
description: The stderr from the remote process
|
||||
returned: success and interactive or asynchronous is 'no'
|
||||
type: str
|
||||
sample: Error [10] running process
|
||||
pid:
|
||||
description: The process ID of the asynchronous process that was created
|
||||
returned: success and asynchronous is 'yes'
|
||||
type: int
|
||||
sample: 719
|
||||
rc:
|
||||
description: The return code of the remote process
|
||||
returned: success and asynchronous is 'no'
|
||||
type: int
|
||||
sample: 0
|
||||
'''
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
|
||||
PYPSEXEC_IMP_ERR = None
|
||||
try:
|
||||
from pypsexec import client
|
||||
from pypsexec.exceptions import PypsexecException, PAExecException, \
|
||||
PDUException, SCMRException
|
||||
from pypsexec.paexec import ProcessPriority
|
||||
from smbprotocol.exceptions import SMBException, SMBAuthenticationError, \
|
||||
SMBResponseException
|
||||
import socket
|
||||
HAS_PYPSEXEC = True
|
||||
except ImportError:
|
||||
PYPSEXEC_IMP_ERR = traceback.format_exc()
|
||||
HAS_PYPSEXEC = False
|
||||
|
||||
KERBEROS_IMP_ERR = None
|
||||
try:
|
||||
import gssapi
|
||||
# GSSAPI extension required for Kerberos Auth in SMB
|
||||
from gssapi.raw import inquire_sec_context_by_oid
|
||||
HAS_KERBEROS = True
|
||||
except ImportError:
|
||||
KERBEROS_IMP_ERR = traceback.format_exc()
|
||||
HAS_KERBEROS = False
|
||||
|
||||
|
||||
def remove_artifacts(module, client):
|
||||
try:
|
||||
client.remove_service()
|
||||
except (SMBException, PypsexecException) as exc:
|
||||
module.warn("Failed to cleanup PAExec service and executable: %s"
|
||||
% to_text(exc))
|
||||
|
||||
|
||||
def main():
|
||||
module_args = dict(
|
||||
hostname=dict(type='str', required=True),
|
||||
connection_username=dict(type='str'),
|
||||
connection_password=dict(type='str', no_log=True),
|
||||
port=dict(type='int', required=False, default=445),
|
||||
encrypt=dict(type='bool', default=True),
|
||||
connection_timeout=dict(type='int', default=60),
|
||||
executable=dict(type='str', required=True),
|
||||
arguments=dict(type='str'),
|
||||
working_directory=dict(type='str', default=r'C:\Windows\System32'),
|
||||
asynchronous=dict(type='bool', default=False),
|
||||
load_profile=dict(type='bool', default=True),
|
||||
process_username=dict(type='str'),
|
||||
process_password=dict(type='str', no_log=True),
|
||||
integrity_level=dict(type='str', default='default',
|
||||
choices=['default', 'elevated', 'limited']),
|
||||
interactive=dict(type='bool', default=False),
|
||||
interactive_session=dict(type='int', default=0),
|
||||
priority=dict(type='str', default='normal',
|
||||
choices=['above_normal', 'below_normal', 'high',
|
||||
'idle', 'normal', 'realtime']),
|
||||
show_ui_on_logon_screen=dict(type='bool', default=False),
|
||||
process_timeout=dict(type='int', default=0),
|
||||
stdin=dict(type='str')
|
||||
)
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
process_username = module.params['process_username']
|
||||
process_password = module.params['process_password']
|
||||
use_system = False
|
||||
if process_username is not None and process_username.lower() == "system":
|
||||
use_system = True
|
||||
process_username = None
|
||||
process_password = None
|
||||
|
||||
if process_username is not None and process_password is None:
|
||||
module.fail_json(msg='parameters are required together when not '
|
||||
'running as System: process_username, '
|
||||
'process_password')
|
||||
if not HAS_PYPSEXEC:
|
||||
module.fail_json(msg=missing_required_lib("pypsexec"),
|
||||
exception=PYPSEXEC_IMP_ERR)
|
||||
|
||||
hostname = module.params['hostname']
|
||||
connection_username = module.params['connection_username']
|
||||
connection_password = module.params['connection_password']
|
||||
port = module.params['port']
|
||||
encrypt = module.params['encrypt']
|
||||
connection_timeout = module.params['connection_timeout']
|
||||
executable = module.params['executable']
|
||||
arguments = module.params['arguments']
|
||||
working_directory = module.params['working_directory']
|
||||
asynchronous = module.params['asynchronous']
|
||||
load_profile = module.params['load_profile']
|
||||
elevated = module.params['integrity_level'] == "elevated"
|
||||
limited = module.params['integrity_level'] == "limited"
|
||||
interactive = module.params['interactive']
|
||||
interactive_session = module.params['interactive_session']
|
||||
|
||||
priority = {
|
||||
"above_normal": ProcessPriority.ABOVE_NORMAL_PRIORITY_CLASS,
|
||||
"below_normal": ProcessPriority.BELOW_NORMAL_PRIORITY_CLASS,
|
||||
"high": ProcessPriority.HIGH_PRIORITY_CLASS,
|
||||
"idle": ProcessPriority.IDLE_PRIORITY_CLASS,
|
||||
"normal": ProcessPriority.NORMAL_PRIORITY_CLASS,
|
||||
"realtime": ProcessPriority.REALTIME_PRIORITY_CLASS
|
||||
}[module.params['priority']]
|
||||
show_ui_on_logon_screen = module.params['show_ui_on_logon_screen']
|
||||
|
||||
process_timeout = module.params['process_timeout']
|
||||
stdin = module.params['stdin']
|
||||
|
||||
if (connection_username is None or connection_password is None) and \
|
||||
not HAS_KERBEROS:
|
||||
module.fail_json(msg=missing_required_lib("gssapi"),
|
||||
execption=KERBEROS_IMP_ERR)
|
||||
|
||||
win_client = client.Client(server=hostname, username=connection_username,
|
||||
password=connection_password, port=port,
|
||||
encrypt=encrypt)
|
||||
|
||||
try:
|
||||
win_client.connect(timeout=connection_timeout)
|
||||
except SMBAuthenticationError as exc:
|
||||
module.fail_json(msg='Failed to authenticate over SMB: %s'
|
||||
% to_text(exc))
|
||||
except SMBResponseException as exc:
|
||||
module.fail_json(msg='Received unexpected SMB response when opening '
|
||||
'the connection: %s' % to_text(exc))
|
||||
except PDUException as exc:
|
||||
module.fail_json(msg='Received an exception with RPC PDU message: %s'
|
||||
% to_text(exc))
|
||||
except SCMRException as exc:
|
||||
module.fail_json(msg='Received an exception when dealing with SCMR on '
|
||||
'the Windows host: %s' % to_text(exc))
|
||||
except (SMBException, PypsexecException) as exc:
|
||||
module.fail_json(msg=to_text(exc))
|
||||
except socket.error as exc:
|
||||
module.fail_json(msg=to_text(exc))
|
||||
|
||||
# create PAExec service and run the process
|
||||
result['changed'] = True
|
||||
b_stdin = to_bytes(stdin, encoding='utf-8') if stdin else None
|
||||
run_args = dict(
|
||||
executable=executable, arguments=arguments, asynchronous=asynchronous,
|
||||
load_profile=load_profile, interactive=interactive,
|
||||
interactive_session=interactive_session,
|
||||
run_elevated=elevated, run_limited=limited,
|
||||
username=process_username, password=process_password,
|
||||
use_system_account=use_system, working_dir=working_directory,
|
||||
priority=priority, show_ui_on_win_logon=show_ui_on_logon_screen,
|
||||
timeout_seconds=process_timeout, stdin=b_stdin
|
||||
)
|
||||
try:
|
||||
win_client.create_service()
|
||||
except (SMBException, PypsexecException) as exc:
|
||||
module.fail_json(msg='Failed to create PAExec service: %s'
|
||||
% to_text(exc))
|
||||
|
||||
try:
|
||||
proc_result = win_client.run_executable(**run_args)
|
||||
except (SMBException, PypsexecException) as exc:
|
||||
module.fail_json(msg='Received error when running remote process: %s'
|
||||
% to_text(exc))
|
||||
finally:
|
||||
remove_artifacts(module, win_client)
|
||||
|
||||
if asynchronous:
|
||||
result['pid'] = proc_result[2]
|
||||
elif interactive:
|
||||
result['rc'] = proc_result[2]
|
||||
else:
|
||||
result['stdout'] = proc_result[0]
|
||||
result['stderr'] = proc_result[1]
|
||||
result['rc'] = proc_result[2]
|
||||
|
||||
# close the SMB connection
|
||||
try:
|
||||
win_client.disconnect()
|
||||
except (SMBException, PypsexecException) as exc:
|
||||
module.warn("Failed to close the SMB connection: %s" % to_text(exc))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,143 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Noah Sparks <nsparks@outlook.com>
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#Requires -Module Ansible.ModuleUtils.CommandUtil
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$params = Parse-Args -arguments $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
|
||||
$results = @{
|
||||
changed = $false
|
||||
}
|
||||
|
||||
######################################
|
||||
### populate sets for -validateset ###
|
||||
######################################
|
||||
$categories_rc = run-command -command 'auditpol /list /category /r'
|
||||
$subcategories_rc = run-command -command 'auditpol /list /subcategory:* /r'
|
||||
|
||||
If ($categories_rc.item('rc') -eq 0)
|
||||
{
|
||||
$categories = ConvertFrom-Csv $categories_rc.item('stdout') | Select-Object -expand Category*
|
||||
}
|
||||
Else
|
||||
{
|
||||
Fail-Json -obj $results -message "Failed to retrive audit policy categories. Please make sure the auditpol command is functional on
|
||||
the system and that the account ansible is running under is able to retrieve them. $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
If ($subcategories_rc.item('rc') -eq 0)
|
||||
{
|
||||
$subcategories = ConvertFrom-Csv $subcategories_rc.item('stdout') | Select-Object -expand Category* |
|
||||
Where-Object {$_ -notin $categories}
|
||||
}
|
||||
Else
|
||||
{
|
||||
Fail-Json -obj $results -message "Failed to retrive audit policy subcategories. Please make sure the auditpol command is functional on
|
||||
the system and that the account ansible is running under is able to retrieve them. $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
######################
|
||||
### ansible params ###
|
||||
######################
|
||||
$category = Get-AnsibleParam -obj $params -name "category" -type "str" -ValidateSet $categories
|
||||
$subcategory = Get-AnsibleParam -obj $params -name "subcategory" -type "str" -ValidateSet $subcategories
|
||||
$audit_type = Get-AnsibleParam -obj $params -name "audit_type" -type "list" -failifempty -
|
||||
|
||||
########################
|
||||
### Start Processing ###
|
||||
########################
|
||||
Function Get-AuditPolicy ($GetString) {
|
||||
$auditpolcsv = Run-Command -command $GetString
|
||||
If ($auditpolcsv.item('rc') -eq 0)
|
||||
{
|
||||
$Obj = ConvertFrom-CSV $auditpolcsv.item('stdout') | Select-Object @{n='subcategory';e={$_.Subcategory.ToLower()}},
|
||||
@{n='audit_type';e={$_."Inclusion Setting".ToLower()}}
|
||||
}
|
||||
Else {
|
||||
return $auditpolcsv.item('stderr')
|
||||
}
|
||||
|
||||
$HT = @{}
|
||||
Foreach ( $Item in $Obj )
|
||||
{
|
||||
$HT.Add($Item.subcategory,$Item.audit_type)
|
||||
}
|
||||
$HT
|
||||
}
|
||||
|
||||
################
|
||||
### Validate ###
|
||||
################
|
||||
|
||||
#make sure category and subcategory are valid
|
||||
If (-Not $category -and -Not $subcategory) {Fail-Json -obj $results -message "You must provide either a Category or Subcategory parameter"}
|
||||
If ($category -and $subcategory) {Fail-Json -obj $results -message "Must pick either a specific subcategory or category. You cannot define both"}
|
||||
|
||||
|
||||
$possible_audit_types = 'success','failure','none'
|
||||
$audit_type | ForEach-Object {
|
||||
If ($_ -notin $possible_audit_types)
|
||||
{
|
||||
Fail-Json -obj $result -message "$_ is not a valid audit_type. Please choose from $($possible_audit_types -join ',')"
|
||||
}
|
||||
}
|
||||
|
||||
#############################################################
|
||||
### build lists for setting, getting, and comparing rules ###
|
||||
#############################################################
|
||||
$audit_type_string = $audit_type -join ' and '
|
||||
|
||||
$SetString = 'auditpol /set'
|
||||
$GetString = 'auditpol /get /r'
|
||||
|
||||
If ($category) {$SetString = "$SetString /category:`"$category`""; $GetString = "$GetString /category:`"$category`""}
|
||||
If ($subcategory) {$SetString= "$SetString /subcategory:`"$subcategory`""; $GetString = "$GetString /subcategory:`"$subcategory`""}
|
||||
|
||||
|
||||
Switch ($audit_type_string)
|
||||
{
|
||||
'success and failure' {$SetString = "$SetString /success:enable /failure:enable"; $audit_type_check = $audit_type_string}
|
||||
'failure' {$SetString = "$SetString /success:disable /failure:enable"; $audit_type_check = $audit_type_string}
|
||||
'success' {$SetString = "$SetString /success:enable /failure:disable"; $audit_type_check = $audit_type_string}
|
||||
'none' {$SetString = "$SetString /success:disable /failure:disable"; $audit_type_check = 'No Auditing'}
|
||||
default {Fail-Json -obj $result -message "It seems you have specified an invalid combination of items for audit_type. Please review documentation"}
|
||||
}
|
||||
|
||||
#########################
|
||||
### check Idempotence ###
|
||||
#########################
|
||||
|
||||
$CurrentRule = Get-AuditPolicy $GetString
|
||||
|
||||
#exit if the audit_type is already set properly for the category
|
||||
If (-not ($CurrentRule.Values | Where-Object {$_ -ne $audit_type_check}) )
|
||||
{
|
||||
$results.current_audit_policy = Get-AuditPolicy $GetString
|
||||
Exit-Json -obj $results
|
||||
}
|
||||
|
||||
####################
|
||||
### Apply Change ###
|
||||
####################
|
||||
|
||||
If (-not $check_mode)
|
||||
{
|
||||
$ApplyPolicy = Run-Command -command $SetString
|
||||
|
||||
If ($ApplyPolicy.Item('rc') -ne 0)
|
||||
{
|
||||
$results.current_audit_policy = Get-AuditPolicy $GetString
|
||||
Fail-Json $results "Failed to set audit policy - $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
$results.changed = $true
|
||||
$results.current_audit_policy = Get-AuditPolicy $GetString
|
||||
Exit-Json $results
|
@ -1,73 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Noah Sparks <nsparks@outlook.com>
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_audit_policy_system
|
||||
short_description: Used to make changes to the system wide Audit Policy
|
||||
description:
|
||||
- Used to make changes to the system wide Audit Policy.
|
||||
version_added: "2.5"
|
||||
options:
|
||||
category:
|
||||
description:
|
||||
- Single string value for the category you would like to adjust the policy on.
|
||||
- Cannot be used with I(subcategory). You must define one or the other.
|
||||
- Changing this setting causes all subcategories to be adjusted to the defined I(audit_type).
|
||||
type: str
|
||||
subcategory:
|
||||
description:
|
||||
- Single string value for the subcategory you would like to adjust the policy on.
|
||||
- Cannot be used with I(category). You must define one or the other.
|
||||
type: str
|
||||
audit_type:
|
||||
description:
|
||||
- The type of event you would like to audit for.
|
||||
- Accepts a list. See examples.
|
||||
type: list
|
||||
required: yes
|
||||
choices: [ failure, none, success ]
|
||||
notes:
|
||||
- It is recommended to take a backup of the policies before adjusting them for the first time.
|
||||
- See this page for in depth information U(https://technet.microsoft.com/en-us/library/cc766468.aspx).
|
||||
seealso:
|
||||
- module: win_audit_rule
|
||||
author:
|
||||
- Noah Sparks (@nwsparks)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Enable failure auditing for the subcategory "File System"
|
||||
win_audit_policy_system:
|
||||
subcategory: File System
|
||||
audit_type: failure
|
||||
|
||||
- name: Enable all auditing types for the category "Account logon events"
|
||||
win_audit_policy_system:
|
||||
category: Account logon events
|
||||
audit_type: success, failure
|
||||
|
||||
- name: Disable auditing for the subcategory "File System"
|
||||
win_audit_policy_system:
|
||||
subcategory: File System
|
||||
audit_type: none
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
current_audit_policy:
|
||||
description: details on the policy being targetted
|
||||
returned: always
|
||||
type: dict
|
||||
sample: |-
|
||||
{
|
||||
"File Share":"failure"
|
||||
}
|
||||
'''
|
@ -1,193 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Noah Sparks <nsparks@outlook.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#Requires -Module Ansible.ModuleUtils.SID
|
||||
|
||||
$params = Parse-Args -arguments $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
|
||||
# module parameters
|
||||
$path = Get-AnsibleParam -obj $params -name "path" -type "path" -failifempty $true -aliases "destination","dest"
|
||||
$user = Get-AnsibleParam -obj $params -name "user" -type "str" -failifempty $true
|
||||
$rights = Get-AnsibleParam -obj $params -name "rights" -type "list"
|
||||
$inheritance_flags = Get-AnsibleParam -obj $params -name "inheritance_flags" -type "list" -default 'ContainerInherit','ObjectInherit'
|
||||
$propagation_flags = Get-AnsibleParam -obj $params -name "propagation_flags" -type "str" -default "none" -ValidateSet 'InheritOnly','None','NoPropagateInherit'
|
||||
$audit_flags = Get-AnsibleParam -obj $params -name "audit_flags" -type "list" -default 'success'
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset 'present','absent'
|
||||
|
||||
#Make sure target path is valid
|
||||
If (-not (Test-Path -Path $path) )
|
||||
{
|
||||
Fail-Json -obj $result -message "defined path ($path) is not found/invalid"
|
||||
}
|
||||
|
||||
#function get current audit rules and convert to hashtable
|
||||
Function Get-CurrentAuditRules ($path) {
|
||||
Try {
|
||||
$ACL = Get-Acl $path -Audit
|
||||
}
|
||||
Catch {
|
||||
Return "Unable to retrieve the ACL on $Path"
|
||||
}
|
||||
|
||||
$HT = Foreach ($Obj in $ACL.Audit)
|
||||
{
|
||||
@{
|
||||
user = $Obj.IdentityReference.ToString()
|
||||
rights = ($Obj | Select-Object -expand "*rights").ToString()
|
||||
audit_flags = $Obj.AuditFlags.ToString()
|
||||
is_inherited = $Obj.IsInherited.ToString()
|
||||
inheritance_flags = $Obj.InheritanceFlags.ToString()
|
||||
propagation_flags = $Obj.PropagationFlags.ToString()
|
||||
}
|
||||
}
|
||||
|
||||
If (-Not $HT)
|
||||
{
|
||||
"No audit rules defined on $path"
|
||||
}
|
||||
Else {$HT}
|
||||
}
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
current_audit_rules = Get-CurrentAuditRules $path
|
||||
}
|
||||
|
||||
#Make sure identity is valid and can be looked up
|
||||
Try {
|
||||
$SID = Convert-ToSid $user
|
||||
}
|
||||
Catch {
|
||||
Fail-Json -obj $result -message "Failed to lookup the identity ($user) - $($_.exception.message)"
|
||||
}
|
||||
|
||||
#get the path type
|
||||
$ItemType = (Get-Item $path).GetType()
|
||||
switch ($ItemType)
|
||||
{
|
||||
([Microsoft.Win32.RegistryKey]) {$registry = $true; $result.path_type = 'registry'}
|
||||
([System.IO.FileInfo]) {$file = $true; $result.path_type = 'file'}
|
||||
([System.IO.DirectoryInfo]) {$result.path_type = 'directory'}
|
||||
}
|
||||
|
||||
#Get current acl/audit rules on the target
|
||||
Try {
|
||||
$ACL = Get-Acl $path -Audit
|
||||
}
|
||||
Catch {
|
||||
Fail-Json -obj $result -message "Unable to retrieve the ACL on $Path - $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
#configure acl object to remove the specified user
|
||||
If ($state -eq 'absent')
|
||||
{
|
||||
#Try and find an identity on the object that matches user
|
||||
#We skip inherited items since we can't remove those
|
||||
$ToRemove = ($ACL.Audit | Where-Object {$_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -eq $SID -and
|
||||
$_.IsInherited -eq $false}).IdentityReference
|
||||
|
||||
#Exit with changed false if no identity is found
|
||||
If (-Not $ToRemove)
|
||||
{
|
||||
$result.current_audit_rules = Get-CurrentAuditRules $path
|
||||
Exit-Json -obj $result
|
||||
}
|
||||
|
||||
#update the ACL object if identity found
|
||||
Try
|
||||
{
|
||||
$ToRemove | ForEach-Object { $ACL.PurgeAuditRules($_) }
|
||||
}
|
||||
Catch
|
||||
{
|
||||
$result.current_audit_rules = Get-CurrentAuditRules $path
|
||||
Fail-Json -obj $result -message "Failed to remove audit rule: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
Else
|
||||
{
|
||||
If ($registry)
|
||||
{
|
||||
$PossibleRights = [System.Enum]::GetNames([System.Security.AccessControl.RegistryRights])
|
||||
|
||||
Foreach ($right in $rights)
|
||||
{
|
||||
if ($right -notin $PossibleRights)
|
||||
{
|
||||
Fail-Json -obj $result -message "$right does not seem to be a valid REGISTRY right"
|
||||
}
|
||||
}
|
||||
|
||||
$NewAccessRule = New-Object System.Security.AccessControl.RegistryAuditRule($user,$rights,$inheritance_flags,$propagation_flags,$audit_flags)
|
||||
}
|
||||
Else
|
||||
{
|
||||
$PossibleRights = [System.Enum]::GetNames([System.Security.AccessControl.FileSystemRights])
|
||||
|
||||
Foreach ($right in $rights)
|
||||
{
|
||||
if ($right -notin $PossibleRights)
|
||||
{
|
||||
Fail-Json -obj $result -message "$right does not seem to be a valid FILE SYSTEM right"
|
||||
}
|
||||
}
|
||||
|
||||
If ($file -and $inheritance_flags -ne 'none')
|
||||
{
|
||||
Fail-Json -obj $result -message "The target type is a file. inheritance_flags must be changed to 'none'"
|
||||
}
|
||||
|
||||
$NewAccessRule = New-Object System.Security.AccessControl.FileSystemAuditRule($user,$rights,$inheritance_flags,$propagation_flags,$audit_flags)
|
||||
}
|
||||
|
||||
#exit here if any existing rule matches defined rule since no change is needed
|
||||
#if we need to ignore inherited rules in the future, this would be where to do it
|
||||
#Just filter out inherited rules from $ACL.Audit
|
||||
Foreach ($group in $ACL.Audit | Where-Object {$_.IsInherited -eq $false})
|
||||
{
|
||||
If (
|
||||
($group | Select-Object -expand "*Rights") -eq ($NewAccessRule | Select-Object -expand "*Rights") -and
|
||||
$group.AuditFlags -eq $NewAccessRule.AuditFlags -and
|
||||
$group.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -eq $SID -and
|
||||
$group.InheritanceFlags -eq $NewAccessRule.InheritanceFlags -and
|
||||
$group.PropagationFlags -eq $NewAccessRule.PropagationFlags
|
||||
)
|
||||
{
|
||||
$result.current_audit_rules = Get-CurrentAuditRules $path
|
||||
Exit-Json -obj $result
|
||||
}
|
||||
}
|
||||
|
||||
#try and set the acl object. AddAuditRule allows for multiple entries to exist under the same
|
||||
#identity...so if someone wanted success: write and failure: delete for example, that setup would be
|
||||
#possible. The alternative is SetAuditRule which would instead modify an existing rule and not allow
|
||||
#for setting the above example.
|
||||
Try
|
||||
{
|
||||
$ACL.AddAuditRule($NewAccessRule)
|
||||
}
|
||||
Catch
|
||||
{
|
||||
Fail-Json -obj $result -message "Failed to set the audit rule: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#finally set the permissions
|
||||
Try {
|
||||
Set-Acl -Path $path -ACLObject $ACL -WhatIf:$check_mode
|
||||
}
|
||||
Catch {
|
||||
$result.current_audit_rules = Get-CurrentAuditRules $path
|
||||
Fail-Json -obj $result -message "Failed to apply audit change: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
#exit here after a change is applied
|
||||
$result.current_audit_rules = Get-CurrentAuditRules $path
|
||||
$result.changed = $true
|
||||
Exit-Json -obj $result
|
@ -1,142 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Noah Sparks <nsparks@outlook.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_audit_rule
|
||||
short_description: Adds an audit rule to files, folders, or registry keys
|
||||
description:
|
||||
- Used to apply audit rules to files, folders or registry keys.
|
||||
- Once applied, it will begin recording the user who performed the operation defined into the Security
|
||||
Log in the Event viewer.
|
||||
- The behavior is designed to ignore inherited rules since those cannot be adjusted without first disabling
|
||||
the inheritance behavior. It will still print inherited rules in the output though for debugging purposes.
|
||||
version_added: "2.5"
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- Path to the file, folder, or registry key.
|
||||
- Registry paths should be in Powershell format, beginning with an abbreviation for the root
|
||||
such as, C(HKLM:\Software).
|
||||
type: path
|
||||
required: yes
|
||||
aliases: [ dest, destination ]
|
||||
user:
|
||||
description:
|
||||
- The user or group to adjust rules for.
|
||||
type: str
|
||||
required: yes
|
||||
rights:
|
||||
description:
|
||||
- Comma separated list of the rights desired. Only required for adding a rule.
|
||||
- If I(path) is a file or directory, rights can be any right under MSDN
|
||||
FileSystemRights U(https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx).
|
||||
- If I(path) is a registry key, rights can be any right under MSDN
|
||||
RegistryRights U(https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx).
|
||||
type: list
|
||||
required: yes
|
||||
inheritance_flags:
|
||||
description:
|
||||
- Defines what objects inside of a folder or registry key will inherit the settings.
|
||||
- If you are setting a rule on a file, this value has to be changed to C(none).
|
||||
- For more information on the choices see MSDN PropagationFlags enumeration
|
||||
at U(https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.inheritanceflags.aspx).
|
||||
type: list
|
||||
choices: [ ContainerInherit, ObjectInherit ]
|
||||
default: ContainerInherit,ObjectInherit
|
||||
propagation_flags:
|
||||
description:
|
||||
- Propagation flag on the audit rules.
|
||||
- This value is ignored when the path type is a file.
|
||||
- For more information on the choices see MSDN PropagationFlags enumeration
|
||||
at U(https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.propagationflags.aspx).
|
||||
choices: [ None, InherityOnly, NoPropagateInherit ]
|
||||
default: "None"
|
||||
audit_flags:
|
||||
description:
|
||||
- Defines whether to log on failure, success, or both.
|
||||
- To log both define as comma separated list "Success, Failure".
|
||||
type: list
|
||||
required: yes
|
||||
choices: [ Failure, Success ]
|
||||
state:
|
||||
description:
|
||||
- Whether the rule should be C(present) or C(absent).
|
||||
- For absent, only I(path), I(user), and I(state) are required.
|
||||
- Specifying C(absent) will remove all rules matching the defined I(user).
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: win_audit_policy_system
|
||||
author:
|
||||
- Noah Sparks (@nwsparks)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add filesystem audit rule for a folder
|
||||
win_audit_rule:
|
||||
path: C:\inetpub\wwwroot\website
|
||||
user: BUILTIN\Users
|
||||
rights: write,delete,changepermissions
|
||||
audit_flags: success,failure
|
||||
inheritance_flags: ContainerInherit,ObjectInherit
|
||||
|
||||
- name: Add filesystem audit rule for a file
|
||||
win_audit_rule:
|
||||
path: C:\inetpub\wwwroot\website\web.config
|
||||
user: BUILTIN\Users
|
||||
rights: write,delete,changepermissions
|
||||
audit_flags: success,failure
|
||||
inheritance_flags: None
|
||||
|
||||
- name: Add registry audit rule
|
||||
win_audit_rule:
|
||||
path: HKLM:\software
|
||||
user: BUILTIN\Users
|
||||
rights: delete
|
||||
audit_flags: 'success'
|
||||
|
||||
- name: Remove filesystem audit rule
|
||||
win_audit_rule:
|
||||
path: C:\inetpub\wwwroot\website
|
||||
user: BUILTIN\Users
|
||||
state: absent
|
||||
|
||||
- name: Remove registry audit rule
|
||||
win_audit_rule:
|
||||
path: HKLM:\software
|
||||
user: BUILTIN\Users
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
current_audit_rules:
|
||||
description:
|
||||
- The current rules on the defined I(path)
|
||||
- Will return "No audit rules defined on I(path)"
|
||||
returned: always
|
||||
type: dict
|
||||
sample: |
|
||||
{
|
||||
"audit_flags": "Success",
|
||||
"user": "Everyone",
|
||||
"inheritance_flags": "False",
|
||||
"is_inherited": "False",
|
||||
"propagation_flags": "None",
|
||||
"rights": "Delete"
|
||||
}
|
||||
path_type:
|
||||
description:
|
||||
- The type of I(path) being targetted.
|
||||
- Will be one of file, directory, registry.
|
||||
returned: always
|
||||
type: str
|
||||
'''
|
@ -1,403 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2019, Prasoon Karunan V (@prasoonkarunan) <kvprasoon@Live.in>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# All helper methods are written in a binary module and has to be loaded for consuming them.
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#Requires -Module Ansible.ModuleUtils.AddType
|
||||
|
||||
Set-StrictMode -Version 2.0
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
logon_count = @{type = "int"}
|
||||
password = @{type = "str"; no_log = $true}
|
||||
state = @{type = "str"; choices = "absent", "present"; default = "present"}
|
||||
username = @{type = "str"}
|
||||
}
|
||||
required_if = @(
|
||||
,@("state", "present", @("username", "password"))
|
||||
)
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$logonCount = $module.Params.logon_count
|
||||
$password = $module.Params.password
|
||||
$state = $module.Params.state
|
||||
$username = $module.Params.username
|
||||
$domain = $null
|
||||
|
||||
if ($username) {
|
||||
# Try and get the Netlogon form of the username specified. Translating to and from a SID gives us an NTAccount
|
||||
# in the Netlogon form that we desire.
|
||||
$ntAccount = New-Object -TypeName System.Security.Principal.NTAccount -ArgumentList $username
|
||||
try {
|
||||
$accountSid = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])
|
||||
} catch [System.Security.Principal.IdentityNotMappedException] {
|
||||
$module.FailJson("Failed to find a local or domain user with the name '$username'", $_)
|
||||
}
|
||||
$ntAccount = $accountSid.Translate([System.Security.Principal.NTAccount])
|
||||
|
||||
$domain, $username = $ntAccount.Value -split '\\'
|
||||
}
|
||||
|
||||
# Make sure $null regardless of any input value if state: absent
|
||||
if ($state -eq 'absent') {
|
||||
$password = $null
|
||||
}
|
||||
|
||||
Add-CSharpType -AnsibleModule $module -References @'
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ansible.WinAutoLogon
|
||||
{
|
||||
internal class NativeHelpers
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class LSA_OBJECT_ATTRIBUTES
|
||||
{
|
||||
public UInt32 Length = 0;
|
||||
public IntPtr RootDirectory = IntPtr.Zero;
|
||||
public IntPtr ObjectName = IntPtr.Zero;
|
||||
public UInt32 Attributes = 0;
|
||||
public IntPtr SecurityDescriptor = IntPtr.Zero;
|
||||
public IntPtr SecurityQualityOfService = IntPtr.Zero;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct LSA_UNICODE_STRING
|
||||
{
|
||||
public UInt16 Length;
|
||||
public UInt16 MaximumLength;
|
||||
public IntPtr Buffer;
|
||||
|
||||
public static explicit operator string(LSA_UNICODE_STRING s)
|
||||
{
|
||||
byte[] strBytes = new byte[s.Length];
|
||||
Marshal.Copy(s.Buffer, strBytes, 0, s.Length);
|
||||
return Encoding.Unicode.GetString(strBytes);
|
||||
}
|
||||
|
||||
public static SafeMemoryBuffer CreateSafeBuffer(string s)
|
||||
{
|
||||
if (s == null)
|
||||
return new SafeMemoryBuffer(IntPtr.Zero);
|
||||
|
||||
byte[] stringBytes = Encoding.Unicode.GetBytes(s);
|
||||
int structSize = Marshal.SizeOf(typeof(LSA_UNICODE_STRING));
|
||||
IntPtr buffer = Marshal.AllocHGlobal(structSize + stringBytes.Length);
|
||||
try
|
||||
{
|
||||
LSA_UNICODE_STRING lsaString = new LSA_UNICODE_STRING()
|
||||
{
|
||||
Length = (UInt16)(stringBytes.Length),
|
||||
MaximumLength = (UInt16)(stringBytes.Length),
|
||||
Buffer = IntPtr.Add(buffer, structSize),
|
||||
};
|
||||
Marshal.StructureToPtr(lsaString, buffer, false);
|
||||
Marshal.Copy(stringBytes, 0, lsaString.Buffer, stringBytes.Length);
|
||||
return new SafeMemoryBuffer(buffer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Make sure we free the pointer before raising the exception.
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("Advapi32.dll")]
|
||||
public static extern UInt32 LsaClose(
|
||||
IntPtr ObjectHandle);
|
||||
|
||||
[DllImport("Advapi32.dll")]
|
||||
public static extern UInt32 LsaFreeMemory(
|
||||
IntPtr Buffer);
|
||||
|
||||
[DllImport("Advapi32.dll")]
|
||||
internal static extern Int32 LsaNtStatusToWinError(
|
||||
UInt32 Status);
|
||||
|
||||
[DllImport("Advapi32.dll")]
|
||||
public static extern UInt32 LsaOpenPolicy(
|
||||
IntPtr SystemName,
|
||||
NativeHelpers.LSA_OBJECT_ATTRIBUTES ObjectAttributes,
|
||||
LsaPolicyAccessMask AccessMask,
|
||||
out SafeLsaHandle PolicyHandle);
|
||||
|
||||
[DllImport("Advapi32.dll")]
|
||||
public static extern UInt32 LsaRetrievePrivateData(
|
||||
SafeLsaHandle PolicyHandle,
|
||||
SafeMemoryBuffer KeyName,
|
||||
out SafeLsaMemory PrivateData);
|
||||
|
||||
[DllImport("Advapi32.dll")]
|
||||
public static extern UInt32 LsaStorePrivateData(
|
||||
SafeLsaHandle PolicyHandle,
|
||||
SafeMemoryBuffer KeyName,
|
||||
SafeMemoryBuffer PrivateData);
|
||||
}
|
||||
|
||||
internal class SafeLsaMemory : SafeBuffer
|
||||
{
|
||||
internal SafeLsaMemory() : base(true) { }
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return NativeMethods.LsaFreeMemory(handle) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SafeMemoryBuffer : SafeBuffer
|
||||
{
|
||||
internal SafeMemoryBuffer() : base(true) { }
|
||||
|
||||
internal SafeMemoryBuffer(IntPtr ptr) : base(true)
|
||||
{
|
||||
base.SetHandle(ptr);
|
||||
}
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (handle != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class SafeLsaHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
internal SafeLsaHandle() : base(true) { }
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return NativeMethods.LsaClose(handle) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class Win32Exception : System.ComponentModel.Win32Exception
|
||||
{
|
||||
private string _exception_msg;
|
||||
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
|
||||
public Win32Exception(int errorCode, string message) : base(errorCode)
|
||||
{
|
||||
_exception_msg = String.Format("{0} - {1} (Win32 Error Code {2}: 0x{3})", message, base.Message, errorCode, errorCode.ToString("X8"));
|
||||
}
|
||||
public override string Message { get { return _exception_msg; } }
|
||||
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum LsaPolicyAccessMask : uint
|
||||
{
|
||||
ViewLocalInformation = 0x00000001,
|
||||
ViewAuditInformation = 0x00000002,
|
||||
GetPrivateInformation = 0x00000004,
|
||||
TrustAdmin = 0x00000008,
|
||||
CreateAccount = 0x00000010,
|
||||
CreateSecret = 0x00000020,
|
||||
CreatePrivilege = 0x00000040,
|
||||
SetDefaultQuotaLimits = 0x00000080,
|
||||
SetAuditRequirements = 0x00000100,
|
||||
AuditLogAdmin = 0x00000200,
|
||||
ServerAdmin = 0x00000400,
|
||||
LookupNames = 0x00000800,
|
||||
Read = 0x00020006,
|
||||
Write = 0x000207F8,
|
||||
Execute = 0x00020801,
|
||||
AllAccess = 0x000F0FFF,
|
||||
}
|
||||
|
||||
public class LsaUtil
|
||||
{
|
||||
public static SafeLsaHandle OpenPolicy(LsaPolicyAccessMask access)
|
||||
{
|
||||
NativeHelpers.LSA_OBJECT_ATTRIBUTES oa = new NativeHelpers.LSA_OBJECT_ATTRIBUTES();
|
||||
SafeLsaHandle lsaHandle;
|
||||
UInt32 res = NativeMethods.LsaOpenPolicy(IntPtr.Zero, oa, access, out lsaHandle);
|
||||
if (res != 0)
|
||||
throw new Win32Exception(NativeMethods.LsaNtStatusToWinError(res),
|
||||
String.Format("LsaOpenPolicy({0}) failed", access.ToString()));
|
||||
return lsaHandle;
|
||||
}
|
||||
|
||||
public static string RetrievePrivateData(SafeLsaHandle handle, string key)
|
||||
{
|
||||
using (SafeMemoryBuffer keyBuffer = NativeHelpers.LSA_UNICODE_STRING.CreateSafeBuffer(key))
|
||||
{
|
||||
SafeLsaMemory buffer;
|
||||
UInt32 res = NativeMethods.LsaRetrievePrivateData(handle, keyBuffer, out buffer);
|
||||
using (buffer)
|
||||
{
|
||||
if (res != 0)
|
||||
{
|
||||
// If the data object was not found we return null to indicate it isn't set.
|
||||
if (res == 0xC0000034) // STATUS_OBJECT_NAME_NOT_FOUND
|
||||
return null;
|
||||
|
||||
throw new Win32Exception(NativeMethods.LsaNtStatusToWinError(res),
|
||||
String.Format("LsaRetrievePrivateData({0}) failed", key));
|
||||
}
|
||||
|
||||
NativeHelpers.LSA_UNICODE_STRING lsaString = (NativeHelpers.LSA_UNICODE_STRING)
|
||||
Marshal.PtrToStructure(buffer.DangerousGetHandle(),
|
||||
typeof(NativeHelpers.LSA_UNICODE_STRING));
|
||||
return (string)lsaString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void StorePrivateData(SafeLsaHandle handle, string key, string data)
|
||||
{
|
||||
using (SafeMemoryBuffer keyBuffer = NativeHelpers.LSA_UNICODE_STRING.CreateSafeBuffer(key))
|
||||
using (SafeMemoryBuffer dataBuffer = NativeHelpers.LSA_UNICODE_STRING.CreateSafeBuffer(data))
|
||||
{
|
||||
UInt32 res = NativeMethods.LsaStorePrivateData(handle, keyBuffer, dataBuffer);
|
||||
if (res != 0)
|
||||
{
|
||||
// When clearing the private data with null it may return this error which we can ignore.
|
||||
if (data == null && res == 0xC0000034) // STATUS_OBJECT_NAME_NOT_FOUND
|
||||
return;
|
||||
|
||||
throw new Win32Exception(NativeMethods.LsaNtStatusToWinError(res),
|
||||
String.Format("LsaStorePrivateData({0}) failed", key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
$autoLogonRegPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
|
||||
$logonDetails = Get-ItemProperty -LiteralPath $autoLogonRegPath
|
||||
|
||||
$before = @{
|
||||
state = 'absent'
|
||||
}
|
||||
if ('AutoAdminLogon' -in $logonDetails.PSObject.Properties.Name -and $logonDetails.AutoAdminLogon -eq 1) {
|
||||
$before.state = 'present'
|
||||
}
|
||||
|
||||
$mapping = @{
|
||||
DefaultUserName = 'username'
|
||||
DefaultDomainName = 'domain'
|
||||
AutoLogonCount = 'logon_count'
|
||||
}
|
||||
foreach ($map_detail in $mapping.GetEnumerator()) {
|
||||
if ($map_detail.Key -in $logonDetails.PSObject.Properties.Name) {
|
||||
$before."$($map_detail.Value)" = $logonDetails."$($map_detail.Key)"
|
||||
}
|
||||
}
|
||||
|
||||
$module.Diff.before = $before
|
||||
|
||||
$propParams = @{
|
||||
LiteralPath = $autoLogonRegPath
|
||||
WhatIf = $module.CheckMode
|
||||
Force = $true
|
||||
}
|
||||
|
||||
# First set the registry information
|
||||
# The DefaultPassword reg key should never be set, we use LSA to store the password in a more secure way.
|
||||
if ('DefaultPassword' -in (Get-Item -LiteralPath $autoLogonRegPath).Property) {
|
||||
# Bug on older Windows hosts where -WhatIf causes it fail to find the property
|
||||
if (-not $module.CheckMode) {
|
||||
Remove-ItemProperty -Name 'DefaultPassword' @propParams
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
|
||||
$autoLogonKeyList = @{
|
||||
DefaultUserName = @{
|
||||
before = if ($before.ContainsKey('username')) { $before.username } else { $null }
|
||||
after = $username
|
||||
}
|
||||
DefaultDomainName = @{
|
||||
before = if ($before.ContainsKey('domain')) { $before.domain } else { $null }
|
||||
after = $domain
|
||||
}
|
||||
AutoLogonCount = @{
|
||||
before = if ($before.ContainsKey('logon_count')) { $before.logon_count } else { $null }
|
||||
after = $logonCount
|
||||
}
|
||||
}
|
||||
|
||||
# Check AutoAdminLogon separately as it has different logic (key must exist)
|
||||
if ($state -ne $before.state) {
|
||||
$newValue = if ($state -eq 'present') { 1 } else { 0 }
|
||||
$null = New-ItemProperty -Name 'AutoAdminLogon' -Value $newValue -PropertyType DWord @propParams
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
|
||||
foreach ($key in $autoLogonKeyList.GetEnumerator()) {
|
||||
$beforeVal = $key.Value.before
|
||||
$after = $key.Value.after
|
||||
|
||||
if ($state -eq 'present' -and $beforeVal -cne $after) {
|
||||
if ($null -ne $after) {
|
||||
$null = New-ItemProperty -Name $key.Key -Value $after @propParams
|
||||
}
|
||||
elseif (-not $module.CheckMode) {
|
||||
Remove-ItemProperty -Name $key.Key @propParams
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
elseif ($state -eq 'absent' -and $null -ne $beforeVal) {
|
||||
if (-not $module.CheckMode) {
|
||||
Remove-ItemProperty -Name $key.Key @propParams
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Finally update the password in the LSA private store.
|
||||
$lsaHandle = [Ansible.WinAutoLogon.LsaUtil]::OpenPolicy('CreateSecret, GetPrivateInformation')
|
||||
try {
|
||||
$beforePass = [Ansible.WinAutoLogon.LsaUtil]::RetrievePrivateData($lsaHandle, 'DefaultPassword')
|
||||
|
||||
if ($beforePass -cne $password) {
|
||||
# Due to .NET marshaling we need to pass in $null as NullString.Value so it's truly a null value.
|
||||
if ($null -eq $password) {
|
||||
$password = [NullString]::Value
|
||||
}
|
||||
if (-not $module.CheckMode) {
|
||||
[Ansible.WinAutoLogon.LsaUtil]::StorePrivateData($lsaHandle, 'DefaultPassword', $password)
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$lsaHandle.Dispose()
|
||||
}
|
||||
|
||||
# Need to manually craft the after diff in case we are running in check mode
|
||||
$module.Diff.after = @{
|
||||
state = $state
|
||||
}
|
||||
if ($state -eq 'present') {
|
||||
$module.Diff.after.username = $username
|
||||
$module.Diff.after.domain = $domain
|
||||
if ($null -ne $logonCount) {
|
||||
$module.Diff.after.logon_count = $logonCount
|
||||
}
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
||||
|
@ -1,77 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Prasoon Karunan V (@prasoonkarunan)
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_auto_logon
|
||||
short_description: Adds or Sets auto logon registry keys.
|
||||
description:
|
||||
- Used to apply auto logon registry setting.
|
||||
version_added: "2.10"
|
||||
options:
|
||||
logon_count:
|
||||
description:
|
||||
- The number of times to do an automatic logon.
|
||||
- This count is deremented by Windows everytime an automatic logon is
|
||||
performed.
|
||||
- Once the count reaches C(0) then the automatic logon process is
|
||||
disabled.
|
||||
type: int
|
||||
username:
|
||||
description:
|
||||
- Username to login automatically.
|
||||
- Must be set when C(state=present).
|
||||
- This can be the Netlogon or UPN of a domain account and is
|
||||
automatically parsed to the C(DefaultUserName) and C(DefaultDomainName)
|
||||
registry properties.
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- Password to be used for automatic login.
|
||||
- Must be set when C(state=present).
|
||||
- Value of this input will be used as password for I(username).
|
||||
- While this value is encrypted by LSA it is decryptable to any user who
|
||||
is an Administrator on the remote host.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Whether the registry key should be C(present) or C(absent).
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
author:
|
||||
- Prasoon Karunan V (@prasoonkarunan)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Set autologon for user1
|
||||
win_auto_logon:
|
||||
username: User1
|
||||
password: str0ngp@ssword
|
||||
|
||||
- name: Set autologon for abc.com\user1
|
||||
win_auto_logon:
|
||||
username: abc.com\User1
|
||||
password: str0ngp@ssword
|
||||
|
||||
- name: Remove autologon for user1
|
||||
win_auto_logon:
|
||||
state: absent
|
||||
|
||||
- name: Set autologon for user1 with a limited logon count
|
||||
win_auto_logon:
|
||||
username: User1
|
||||
password: str0ngp@ssword
|
||||
logon_count: 5
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
#
|
||||
'''
|
@ -1,132 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2019, Micah Hunsberger
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
|
||||
function ConvertTo-Timestamp($start_date, $end_date)
|
||||
{
|
||||
if ($start_date -and $end_date)
|
||||
{
|
||||
return (New-TimeSpan -Start $start_date -End $end_date).TotalSeconds
|
||||
}
|
||||
}
|
||||
|
||||
function Format-Date([DateTime]$date)
|
||||
{
|
||||
return $date.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssK')
|
||||
}
|
||||
|
||||
function Get-CertificateInfo ($cert)
|
||||
{
|
||||
$epoch_date = Get-Date -Date "01/01/1970"
|
||||
|
||||
$cert_info = @{ extensions = @() }
|
||||
$cert_info.friendly_name = $cert.FriendlyName
|
||||
$cert_info.thumbprint = $cert.Thumbprint
|
||||
$cert_info.subject = $cert.Subject
|
||||
$cert_info.issuer = $cert.Issuer
|
||||
$cert_info.valid_from = (ConvertTo-Timestamp -start_date $epoch_date -end_date $cert.NotBefore.ToUniversalTime())
|
||||
$cert_info.valid_from_iso8601 = Format-Date -date $cert.NotBefore
|
||||
$cert_info.valid_to = (ConvertTo-Timestamp -start_date $epoch_date -end_date $cert.NotAfter.ToUniversalTime())
|
||||
$cert_info.valid_to_iso8601 = Format-Date -date $cert.NotAfter
|
||||
$cert_info.serial_number = $cert.SerialNumber
|
||||
$cert_info.archived = $cert.Archived
|
||||
$cert_info.version = $cert.Version
|
||||
$cert_info.has_private_key = $cert.HasPrivateKey
|
||||
$cert_info.issued_by = $cert.GetNameInfo('SimpleName', $true)
|
||||
$cert_info.issued_to = $cert.GetNameInfo('SimpleName', $false)
|
||||
$cert_info.signature_algorithm = $cert.SignatureAlgorithm.FriendlyName
|
||||
$cert_info.dns_names = [System.Collections.Generic.List`1[String]]@($cert_info.issued_to)
|
||||
$cert_info.raw = [System.Convert]::ToBase64String($cert.GetRawCertData())
|
||||
$cert_info.public_key = [System.Convert]::ToBase64String($cert.GetPublicKey())
|
||||
if ($cert.Extensions.Count -gt 0)
|
||||
{
|
||||
[array]$cert_info.extensions = foreach ($extension in $cert.Extensions)
|
||||
{
|
||||
$extension_info = @{
|
||||
critical = $extension.Critical
|
||||
field = $extension.Oid.FriendlyName
|
||||
value = $extension.Format($false)
|
||||
}
|
||||
if ($extension -is [System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension])
|
||||
{
|
||||
$cert_info.is_ca = $extension.CertificateAuthority
|
||||
$cert_info.path_length_constraint = $extension.PathLengthConstraint
|
||||
}
|
||||
elseif ($extension -is [System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension])
|
||||
{
|
||||
$cert_info.intended_purposes = $extension.EnhancedKeyUsages.FriendlyName -as [string[]]
|
||||
}
|
||||
elseif ($extension -is [System.Security.Cryptography.X509Certificates.X509KeyUsageExtension])
|
||||
{
|
||||
$cert_info.key_usages = $extension.KeyUsages.ToString().Split(',').Trim() -as [string[]]
|
||||
}
|
||||
elseif ($extension -is [System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension])
|
||||
{
|
||||
$cert_info.ski = $extension.SubjectKeyIdentifier
|
||||
}
|
||||
elseif ($extension.Oid.value -eq '2.5.29.17')
|
||||
{
|
||||
$sans = $extension.Format($true).Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
foreach ($san in $sans)
|
||||
{
|
||||
$san_parts = $san.Split("=")
|
||||
if ($san_parts.Length -ge 2 -and $san_parts[0].Trim() -eq 'DNS Name')
|
||||
{
|
||||
$cert_info.dns_names.Add($san_parts[1].Trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
$extension_info
|
||||
}
|
||||
}
|
||||
return $cert_info
|
||||
}
|
||||
|
||||
$store_location_values = ([System.Security.Cryptography.X509Certificates.StoreLocation]).GetEnumValues() | ForEach-Object { $_.ToString() }
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
thumbprint = @{ type = "str"; required = $false }
|
||||
store_name = @{ type = "str"; default = "My"; }
|
||||
store_location = @{ type = "str"; default = "LocalMachine"; choices = $store_location_values; }
|
||||
}
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$thumbprint = $module.Params.thumbprint
|
||||
$store_name = $module.Params.store_name
|
||||
$store_location = [System.Security.Cryptography.X509Certificates.Storelocation]"$($module.Params.store_location)"
|
||||
|
||||
$store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $store_name, $store_location
|
||||
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)
|
||||
|
||||
$module.Result.exists = $false
|
||||
$module.Result.certificates = @()
|
||||
|
||||
try
|
||||
{
|
||||
if ($null -ne $thumbprint)
|
||||
{
|
||||
$found_certs = $store.Certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $thumbprint, $false)
|
||||
}
|
||||
else
|
||||
{
|
||||
$found_certs = $store.Certificates
|
||||
}
|
||||
|
||||
if ($found_certs.Count -gt 0)
|
||||
{
|
||||
$module.Result.exists = $true
|
||||
[array]$module.Result.certificates = $found_certs | ForEach-Object { Get-CertificateInfo -cert $_ } | Sort-Object -Property { $_.thumbprint }
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
$store.Close()
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
@ -1,236 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016, Ansible, inc
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_certificate_info
|
||||
version_added: "2.10"
|
||||
short_description: Get information on certificates from a Windows Certificate Store
|
||||
description:
|
||||
- Returns information about certificates in a Windows Certificate Store.
|
||||
options:
|
||||
thumbprint:
|
||||
description:
|
||||
- The thumbprint as a hex string of a certificate to find.
|
||||
- When specified, filters the I(certificates) return value to a single certificate
|
||||
- See the examples for how to format the thumbprint.
|
||||
type: str
|
||||
required: no
|
||||
store_name:
|
||||
description:
|
||||
- The name of the store to search.
|
||||
- See U(https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.storename)
|
||||
for a list of built-in store names.
|
||||
type: str
|
||||
default: My
|
||||
store_location:
|
||||
description:
|
||||
- The location of the store to search.
|
||||
type: str
|
||||
choices: [ CurrentUser, LocalMachine ]
|
||||
default: LocalMachine
|
||||
seealso:
|
||||
- module: win_certificate_store
|
||||
author:
|
||||
- Micah Hunsberger (@mhunsber)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Obtain information about a particular certificate in the computer's personal store
|
||||
win_certificate_info:
|
||||
thumbprint: BD7AF104CF1872BDB518D95C9534EA941665FD27
|
||||
register: mycert
|
||||
|
||||
# thumbprint can also be lower case
|
||||
- name: Obtain information about a particular certificate in the computer's personal store
|
||||
win_certificate_info:
|
||||
thumbprint: bd7af104cf1872bdb518d95c9534ea941665fd27
|
||||
register: mycert
|
||||
|
||||
- name: Obtain information about all certificates in the root store
|
||||
win_certificate_info:
|
||||
store_name: Root
|
||||
register: ca
|
||||
|
||||
# Import a pfx and then get information on the certificates
|
||||
- name: Import pfx certificate that is password protected
|
||||
win_certificate_store:
|
||||
path: C:\Temp\cert.pfx
|
||||
state: present
|
||||
password: VeryStrongPasswordHere!
|
||||
become: yes
|
||||
become_method: runas
|
||||
register: mycert
|
||||
|
||||
- name: Obtain information on each certificate that was touched
|
||||
win_certificate_info:
|
||||
thumbprint: "{{ item }}"
|
||||
register: mycert_stats
|
||||
loop: "{{ mycert.thumbprints }}"
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
exists:
|
||||
description:
|
||||
- Whether any certificates were found in the store.
|
||||
- When I(thumbprint) is specified, returns true only if the certificate mathing the thumbprint exists.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: true
|
||||
certificates:
|
||||
description:
|
||||
- A list of information about certificates found in the store, sorted by thumbprint.
|
||||
returned: success
|
||||
type: list
|
||||
elements: dict
|
||||
contains:
|
||||
archived:
|
||||
description: Indicates that the certificate is archived.
|
||||
type: bool
|
||||
sample: false
|
||||
dns_names:
|
||||
description: Lists the registered dns names for the certificate.
|
||||
type: list
|
||||
elements: str
|
||||
sample: [ '*.m.wikiquote.org', '*.wikipedia.org' ]
|
||||
extensions:
|
||||
description: The collection of the certificates extensions.
|
||||
type: list
|
||||
elements: dict
|
||||
sample: [
|
||||
{
|
||||
"critical": false,
|
||||
"field": "Subject Key Identifier",
|
||||
"value": "88 27 17 09 a9 b6 18 60 8b ec eb ba f6 47 59 c5 52 54 a3 b7"
|
||||
},
|
||||
{
|
||||
"critical": true,
|
||||
"field": "Basic Constraints",
|
||||
"value": "Subject Type=CA, Path Length Constraint=None"
|
||||
},
|
||||
{
|
||||
"critical": false,
|
||||
"field": "Authority Key Identifier",
|
||||
"value": "KeyID=2b d0 69 47 94 76 09 fe f4 6b 8d 2e 40 a6 f7 47 4d 7f 08 5e"
|
||||
},
|
||||
{
|
||||
"critical": false,
|
||||
"field": "CRL Distribution Points",
|
||||
"value": "[1]CRL Distribution Point: Distribution Point Name:Full Name:URL=http://crl.apple.com/root.crl"
|
||||
},
|
||||
{
|
||||
"critical": true,
|
||||
"field": "Key Usage",
|
||||
"value": "Digital Signature, Certificate Signing, Off-line CRL Signing, CRL Signing (86)"
|
||||
},
|
||||
{
|
||||
"critical": false,
|
||||
"field": null,
|
||||
"value": "05 00"
|
||||
}
|
||||
]
|
||||
friendly_name:
|
||||
description: The associated alias for the certificate.
|
||||
type: str
|
||||
sample: Microsoft Root Authority
|
||||
has_private_key:
|
||||
description: Indicates that the certificate contains a private key.
|
||||
type: bool
|
||||
sample: false
|
||||
intended_purposes:
|
||||
description: lists the intended applications for the certificate.
|
||||
returned: enhanced key usages extension exists.
|
||||
type: list
|
||||
sample: [ "Server Authentication" ]
|
||||
is_ca:
|
||||
description: Indicates that the certificate is a certificate authority (CA) certificate.
|
||||
returned: basic constraints extension exists.
|
||||
type: bool
|
||||
sample: true
|
||||
issued_by:
|
||||
description: The certificate issuer's common name.
|
||||
type: str
|
||||
sample: Apple Root CA
|
||||
issued_to:
|
||||
description: The certificate's common name.
|
||||
type: str
|
||||
sample: Apple Worldwide Developer Relations Certification Authority
|
||||
issuer:
|
||||
description: The certificate issuer's distinguished name.
|
||||
type: str
|
||||
sample: 'CN=Apple Root CA, OU=Apple Certification Authority, O=Apple Inc., C=US'
|
||||
key_usages:
|
||||
description:
|
||||
- Defines how the certificate key can be used.
|
||||
- If this value is not defined, the key can be used for any purpose.
|
||||
returned: key usages extension exists.
|
||||
type: list
|
||||
elements: str
|
||||
sample: [ "CrlSign", "KeyCertSign", "DigitalSignature" ]
|
||||
path_length_constraint:
|
||||
description:
|
||||
- The number of levels allowed in a certificates path.
|
||||
- If this value is 0, the certificate does not have a restriction.
|
||||
returned: basic constraints extension exists
|
||||
type: int
|
||||
sample: 0
|
||||
public_key:
|
||||
description: The base64 encoded public key of the certificate.
|
||||
type: str
|
||||
cert_data:
|
||||
description: The base64 encoded data of the entire certificate.
|
||||
type: str
|
||||
serial_number:
|
||||
description: The serial number of the certificate represented as a hexadecimal string
|
||||
type: str
|
||||
sample: 01DEBCC4396DA010
|
||||
signature_algorithm:
|
||||
description: The algorithm used to create the certificate's signature
|
||||
type: str
|
||||
sample: sha1RSA
|
||||
ski:
|
||||
description: The certificate's subject key identifier
|
||||
returned: subject key identifier extension exists.
|
||||
type: str
|
||||
sample: 88271709A9B618608BECEBBAF64759C55254A3B7
|
||||
subject:
|
||||
description: The certificate's distinguished name.
|
||||
type: str
|
||||
sample: 'CN=Apple Worldwide Developer Relations Certification Authority, OU=Apple Worldwide Developer Relations, O=Apple Inc., C=US'
|
||||
thumbprint:
|
||||
description:
|
||||
- The thumbprint as a hex string of the certificate.
|
||||
- The return format will always be upper case.
|
||||
type: str
|
||||
sample: FF6797793A3CD798DC5B2ABEF56F73EDC9F83A64
|
||||
valid_from:
|
||||
description: The start date of the certificate represented in seconds since epoch.
|
||||
type: float
|
||||
sample: 1360255727
|
||||
valid_from_iso8601:
|
||||
description: The start date of the certificate represented as an iso8601 formatted date.
|
||||
type: str
|
||||
sample: '2017-12-15T08:39:32Z'
|
||||
valid_to:
|
||||
description: The expiry date of the certificate represented in seconds since epoch.
|
||||
type: float
|
||||
sample: 1675788527
|
||||
valid_to_iso8601:
|
||||
description: The expiry date of the certificate represented as an iso8601 formatted date.
|
||||
type: str
|
||||
sample: '2086-01-02T08:39:32Z'
|
||||
version:
|
||||
description: The x509 format version of the certificate
|
||||
type: int
|
||||
sample: 3
|
||||
'''
|
@ -1,803 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2014, Trond Hindenes <trond@hindenes.com>
|
||||
# Copyright: (c) 2017, Dag Wieers <dag@wieers.com>
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.ArgvParser
|
||||
#Requires -Module Ansible.ModuleUtils.CommandUtil
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
|
||||
# As of chocolatey 0.9.10, non-zero success exit codes can be returned
|
||||
# See https://github.com/chocolatey/choco/issues/512#issuecomment-214284461
|
||||
$successexitcodes = (0, 1605, 1614, 1641, 3010)
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
allow_empty_checksums = @{ type = "bool"; default = $false }
|
||||
allow_multiple = @{ type = "bool"; default = $false }
|
||||
allow_prerelease = @{ type = "bool"; default = $false }
|
||||
architecture = @{ type = "str"; default = "default"; choices = "default", "x86" }
|
||||
force = @{ type = "bool"; default = $false }
|
||||
ignore_checksums = @{ type = "bool"; default = $false }
|
||||
ignore_dependencies = @{ type = "bool"; default = $false }
|
||||
install_args = @{ type = "str" }
|
||||
name = @{ type = "list"; elements = "str"; required = $true }
|
||||
override_args = @{ type = "bool"; default = $false }
|
||||
package_params = @{ type = "str"; aliases = @("params") }
|
||||
pinned = @{ type = "bool" }
|
||||
proxy_url = @{ type = "str" }
|
||||
proxy_username = @{ type = "str" }
|
||||
proxy_password = @{ type = "str"; no_log = $true }
|
||||
skip_scripts = @{ type = "bool"; default = $false }
|
||||
source = @{ type = "str" }
|
||||
source_username = @{ type = "str" }
|
||||
source_password = @{ type = "str"; no_log = $true }
|
||||
state = @{ type = "str"; default = "present"; choices = "absent", "downgrade", "latest", "present", "reinstalled" }
|
||||
timeout = @{ type = "int"; default = 2700; aliases = @("execution_timeout") }
|
||||
validate_certs = @{ type = "bool"; default = $true }
|
||||
version = @{ type = "str" }
|
||||
}
|
||||
supports_check_mode = $true
|
||||
}
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$allow_empty_checksums = $module.Params.allow_empty_checksums
|
||||
$allow_multiple = $module.Params.allow_multiple
|
||||
$allow_prerelease = $module.Params.allow_prerelease
|
||||
$architecture = $module.Params.architecture
|
||||
$force = $module.Params.force
|
||||
$ignore_checksums = $module.Params.ignore_checksums
|
||||
$ignore_dependencies = $module.Params.ignore_dependencies
|
||||
$install_args = $module.Params.install_args
|
||||
$name = $module.Params.name
|
||||
$override_args = $module.Params.override_args
|
||||
$package_params = $module.Params.package_params
|
||||
$pinned = $module.Params.pinned
|
||||
$proxy_url = $module.Params.proxy_url
|
||||
$proxy_username = $module.Params.proxy_username
|
||||
$proxy_password = $module.Params.proxy_password
|
||||
$skip_scripts = $module.Params.skip_scripts
|
||||
$source = $module.Params.source
|
||||
$source_username = $module.Params.source_username
|
||||
$source_password = $module.Params.source_password
|
||||
$state = $module.Params.state
|
||||
$timeout = $module.Params.timeout
|
||||
$validate_certs = $module.Params.validate_certs
|
||||
$version = $module.Params.version
|
||||
|
||||
$module.Result.rc = 0
|
||||
|
||||
if (-not $validate_certs) {
|
||||
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
|
||||
}
|
||||
|
||||
Function Get-CommonChocolateyArguments {
|
||||
# uses global vars like check_mode and verbosity to control the common args
|
||||
# run with Chocolatey
|
||||
$arguments = [System.Collections.ArrayList]@("--yes", "--no-progress")
|
||||
# global vars that control the arguments
|
||||
if ($module.CheckMode) {
|
||||
$arguments.Add("--what-if") > $null
|
||||
}
|
||||
if ($module.Verbosity -gt 4) {
|
||||
$arguments.Add("--debug") > $null
|
||||
$arguments.Add("--verbose") > $null
|
||||
} elseif ($module.Verbosity -gt 3) {
|
||||
$arguments.Add("--verbose") > $null
|
||||
} else {
|
||||
$arguments.Add("--limit-output") > $null
|
||||
}
|
||||
|
||||
return ,$arguments
|
||||
}
|
||||
|
||||
Function Get-InstallChocolateyArguments {
|
||||
param(
|
||||
[bool]$allow_downgrade,
|
||||
[bool]$allow_empty_checksums,
|
||||
[bool]$allow_multiple,
|
||||
[bool]$allow_prerelease,
|
||||
[String]$architecture,
|
||||
[bool]$force,
|
||||
[bool]$ignore_dependencies,
|
||||
[String]$install_args,
|
||||
[bool]$override_args,
|
||||
[String]$package_params,
|
||||
[String]$proxy_url,
|
||||
[String]$proxy_username,
|
||||
[String]$proxy_password,
|
||||
[bool]$skip_scripts,
|
||||
[String]$source,
|
||||
[String]$source_usename,
|
||||
[String]$source_password,
|
||||
[int]$timeout,
|
||||
[String]$version
|
||||
)
|
||||
# returns an ArrayList of common arguments for install/updated a Chocolatey
|
||||
# package
|
||||
$arguments = [System.Collections.ArrayList]@("--fail-on-unfound")
|
||||
$common_args = Get-CommonChocolateyArguments
|
||||
$arguments.AddRange($common_args)
|
||||
|
||||
if ($allow_downgrade) {
|
||||
$arguments.Add("--allow-downgrade") > $null
|
||||
}
|
||||
if ($allow_empty_checksums) {
|
||||
$arguments.Add("--allow-empty-checksums") > $null
|
||||
}
|
||||
if ($allow_multiple) {
|
||||
$arguments.Add("--allow-multiple") > $null
|
||||
}
|
||||
if ($allow_prerelease) {
|
||||
$arguments.Add("--prerelease") > $null
|
||||
}
|
||||
if ($architecture -eq "x86") {
|
||||
$arguments.Add("--x86") > $null
|
||||
}
|
||||
if ($force) {
|
||||
$arguments.Add("--force") > $null
|
||||
}
|
||||
if ($ignore_checksums) {
|
||||
$arguments.Add("--ignore-checksums") > $null
|
||||
}
|
||||
if ($ignore_dependencies) {
|
||||
$arguments.Add("--ignore-dependencies") > $null
|
||||
}
|
||||
if ($install_args) {
|
||||
$arguments.Add("--install-arguments") > $null
|
||||
$arguments.add($install_args) > $null
|
||||
}
|
||||
if ($override_args) {
|
||||
$arguments.Add("--override-arguments") > $null
|
||||
}
|
||||
if ($package_params) {
|
||||
$arguments.Add("--package-parameters") > $null
|
||||
$arguments.Add($package_params) > $null
|
||||
}
|
||||
if ($proxy_url) {
|
||||
$arguments.Add("--proxy") > $null
|
||||
$arguments.Add($proxy_url) > $null
|
||||
}
|
||||
if ($proxy_username) {
|
||||
$arguments.Add("--proxy-user") > $null
|
||||
$arguments.Add($proxy_username) > $null
|
||||
}
|
||||
if ($proxy_password) {
|
||||
$arguments.Add("--proxy-password") > $null
|
||||
$arguments.Add($proxy_password) > $null
|
||||
}
|
||||
if ($skip_scripts) {
|
||||
$arguments.Add("--skip-scripts") > $null
|
||||
}
|
||||
if ($source) {
|
||||
$arguments.Add("--source") > $null
|
||||
$arguments.Add($source) > $null
|
||||
}
|
||||
if ($source_username) {
|
||||
$arguments.Add("--user") > $null
|
||||
$arguments.Add($source_username) > $null
|
||||
$arguments.Add("--password") > $null
|
||||
$arguments.Add($source_password) > $null
|
||||
}
|
||||
if ($null -ne $timeout) {
|
||||
$arguments.Add("--timeout") > $null
|
||||
$arguments.Add($timeout) > $null
|
||||
}
|
||||
if ($version) {
|
||||
$arguments.Add("--version") > $null
|
||||
$arguments.Add($version) > $null
|
||||
}
|
||||
|
||||
return ,$arguments
|
||||
}
|
||||
|
||||
Function Install-Chocolatey {
|
||||
param(
|
||||
[String]$proxy_url,
|
||||
[String]$proxy_username,
|
||||
[String]$proxy_password,
|
||||
[String]$source,
|
||||
[String]$source_username,
|
||||
[String]$source_password,
|
||||
[String]$version
|
||||
)
|
||||
|
||||
$choco_app = Get-Command -Name choco.exe -CommandType Application -ErrorAction SilentlyContinue
|
||||
if ($null -eq $choco_app) {
|
||||
# We need to install chocolatey
|
||||
# Enable TLS1.1/TLS1.2 if they're available but disabled (eg. .NET 4.5)
|
||||
$security_protocols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault
|
||||
if ([Net.SecurityProtocolType].GetMember("Tls11").Count -gt 0) {
|
||||
$security_protocols = $security_protcols -bor [Net.SecurityProtocolType]::Tls11
|
||||
}
|
||||
if ([Net.SecurityProtocolType].GetMember("Tls12").Count -gt 0) {
|
||||
$security_protocols = $security_protcols -bor [Net.SecurityProtocolType]::Tls12
|
||||
}
|
||||
[Net.ServicePointManager]::SecurityProtocol = $security_protocols
|
||||
|
||||
$client = New-Object -TypeName System.Net.WebClient
|
||||
$new_environment = @{}
|
||||
if ($proxy_url) {
|
||||
# the env values are used in the install.ps1 script when getting
|
||||
# external dependencies
|
||||
$new_environment.chocolateyProxyLocation = $proxy_url
|
||||
$web_proxy = New-Object -TypeName System.Net.WebProxy -ArgumentList $proxy_url, $true
|
||||
$client.Proxy = $web_proxy
|
||||
if ($proxy_username -and $proxy_password) {
|
||||
$new_environment.chocolateyProxyUser = $proxy_username
|
||||
$new_environment.chocolateyProxyPassword = $proxy_password
|
||||
$sec_proxy_password = ConvertTo-SecureString -String $proxy_password -AsPlainText -Force
|
||||
$web_proxy.Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $proxy_username, $sec_proxy_password
|
||||
}
|
||||
}
|
||||
if ($version) {
|
||||
# Set the chocolateyVersion environment variable when bootstrapping Chocolatey to install that specific
|
||||
# version.
|
||||
$new_environment.chocolateyVersion = $version
|
||||
}
|
||||
|
||||
$environment = @{}
|
||||
if ($new_environment.Count -gt 0) {
|
||||
$environment = [Environment]::GetEnvironmentVariables()
|
||||
$environment += $new_environment
|
||||
}
|
||||
|
||||
if ($source) {
|
||||
# check if the URL already contains the path to PS script
|
||||
if ($source.EndsWith(".ps1")) {
|
||||
$script_url = $source
|
||||
} else {
|
||||
# chocolatey server automatically serves a script at
|
||||
# http://host/install.ps1, we rely on this behaviour when a
|
||||
# user specifies the choco source URL. If a custom URL or file
|
||||
# path is desired, they should use win_get_url/win_shell
|
||||
# manually
|
||||
# we need to strip the path off the URL and append install.ps1
|
||||
$uri_info = [System.Uri]$source
|
||||
$script_url = "$($uri_info.Scheme)://$($uri_info.Authority)/install.ps1"
|
||||
}
|
||||
if ($source_username) {
|
||||
# while the choco-server does not require creds on install.ps1,
|
||||
# Net.WebClient will only send the credentials if the initial
|
||||
# req fails so we will add the creds in case the source URL
|
||||
# is not choco-server and requires authentication
|
||||
$sec_source_password = ConvertTo-SecureString -String $source_password -AsPlainText -Force
|
||||
$client.Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $source_username, $sec_source_password
|
||||
}
|
||||
} else {
|
||||
$script_url = "https://chocolatey.org/install.ps1"
|
||||
}
|
||||
|
||||
try {
|
||||
$install_script = $client.DownloadString($script_url)
|
||||
} catch {
|
||||
$module.FailJson("Failed to download Chocolatey script from '$script_url'; $($_.Exception.Message)", $_)
|
||||
}
|
||||
if (-not $module.CheckMode) {
|
||||
$res = Run-Command -command "powershell.exe -" -stdin $install_script -environment $environment
|
||||
if ($res.rc -ne 0) {
|
||||
$module.Result.rc = $res.rc
|
||||
$module.Result.stdout = $res.stdout
|
||||
$module.Result.stderr = $res.stderr
|
||||
$module.FailJson("Chocolatey bootstrap installation failed.")
|
||||
}
|
||||
$module.Warn("Chocolatey was missing from this system, so it was installed during this task run.")
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
|
||||
# locate the newly installed choco.exe
|
||||
$choco_app = Get-Command -Name choco.exe -CommandType Application -ErrorAction SilentlyContinue
|
||||
if ($null -eq $choco_app) {
|
||||
$choco_dir = $env:ChocolateyInstall
|
||||
if ($null -eq $choco_dir) {
|
||||
$choco_dir = "$env:SYSTEMDRIVE\ProgramData\Chocolatey"
|
||||
}
|
||||
$choco_app = Get-Command -Name "$choco_dir\bin\choco.exe" -CommandType Application -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
if ($module.CheckMode -and $null -eq $choco_app) {
|
||||
$module.Result.skipped = $true
|
||||
$module.Result.msg = "Skipped check mode run on win_chocolatey as choco.exe cannot be found on the system"
|
||||
$module.ExitJson()
|
||||
}
|
||||
|
||||
if ($null -eq $choco_app -or -not (Test-Path -LiteralPath $choco_app.Path)) {
|
||||
$module.FailJson("Failed to find choco.exe, make sure it is added to the PATH or the env var 'ChocolateyInstall' is set")
|
||||
}
|
||||
|
||||
$actual_version = (Get-ChocolateyPackageVersion -choco_path $choco_app.Path -name chocolatey)[0]
|
||||
try {
|
||||
# The Chocolatey version may not be in the strict form of major.minor.build and will fail to cast to
|
||||
# System.Version. We want to warn if this is the case saying module behaviour may be incorrect.
|
||||
$actual_version = [Version]$actual_version
|
||||
} catch {
|
||||
$module.Warn("Failed to parse Chocolatey version '$actual_version' for checking module requirements, module may not work correctly: $($_.Exception.Message)")
|
||||
$actual_version = $null
|
||||
}
|
||||
if ($null -ne $actual_version -and $actual_version -lt [Version]"0.10.5") {
|
||||
if ($module.CheckMode) {
|
||||
$module.Result.skipped = $true
|
||||
$module.Result.msg = "Skipped check mode run on win_chocolatey as choco.exe is too old, a real run would have upgraded the executable. Actual: '$actual_version', Minimum Version: '0.10.5'"
|
||||
$module.ExitJson()
|
||||
}
|
||||
$module.Warn("Chocolatey was older than v0.10.5 so it was upgraded during this task run.")
|
||||
Update-ChocolateyPackage -choco_path $choco_app.Path -packages @("chocolatey") `
|
||||
|
||||
-proxy_url $proxy_url -proxy_username $proxy_username `
|
||||
-proxy_password $proxy_password -source $source `
|
||||
-source_username $source_username -source_password $source_password
|
||||
}
|
||||
|
||||
return $choco_app.Path
|
||||
}
|
||||
|
||||
Function Get-ChocolateyPackageVersion {
|
||||
Param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[System.String]
|
||||
$choco_path,
|
||||
|
||||
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
|
||||
[System.String]
|
||||
$name
|
||||
)
|
||||
|
||||
Begin {
|
||||
# Due to https://github.com/chocolatey/choco/issues/1843, we get a list of all the installed packages and
|
||||
# filter it ourselves. This has the added benefit of being quicker when dealing with multiple packages as we
|
||||
# only call choco.exe once.
|
||||
$command = Argv-ToString -arguments @($choco_path, 'list', '--local-only', '--limit-output', '--all-versions')
|
||||
$res = Run-Command -command $command
|
||||
|
||||
# Chocolatey v0.10.12 introduced enhanced exit codes, 2 means no results, e.g. no package
|
||||
if ($res.rc -notin @(0, 2)) {
|
||||
$module.Result.command = $command
|
||||
$module.Result.rc = $res.rc
|
||||
$module.Result.stdout = $res.stdout
|
||||
$module.Result.stderr = $res.stderr
|
||||
$module.FailJson('Error checking installation status for chocolatey packages')
|
||||
}
|
||||
|
||||
# Parse the stdout to get a list of all packages installed and their versions.
|
||||
$installed_packages = $res.stdout.Trim().Split([System.Environment]::NewLine) | ForEach-Object -Process {
|
||||
if ($_.Contains('|')) { # Sanity in case further output is added in the future.
|
||||
$package_split = $_.Split('|', 2)
|
||||
@{ Name = $package_split[0]; Version = $package_split[1] }
|
||||
}
|
||||
}
|
||||
|
||||
# Create a hashtable that will store our package version info.
|
||||
$installed_info = @{}
|
||||
}
|
||||
|
||||
Process {
|
||||
if ($name -eq 'all') {
|
||||
# All is a special package name that means all installed packages, we set a dummy version so absent, latest
|
||||
# and downgrade will run with all.
|
||||
$installed_info.'all' = @('0.0.0')
|
||||
} else {
|
||||
$package_info = $installed_packages | Where-Object { $_.Name -eq $name }
|
||||
if ($null -eq $package_info) {
|
||||
$installed_info.$name = $null
|
||||
} else {
|
||||
$installed_info.$name = @($package_info.Version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
End {
|
||||
return $installed_info
|
||||
}
|
||||
}
|
||||
|
||||
Function Get-ChocolateyPin {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][String]$choco_path
|
||||
)
|
||||
|
||||
$command = Argv-ToString -arguments @($choco_path, "pin", "list", "--limit-output")
|
||||
$res = Run-Command -command $command
|
||||
if ($res.rc -ne 0) {
|
||||
$module.Result.command = $command
|
||||
$module.Result.rc = $res.rc
|
||||
$module.Result.stdout = $res.stdout
|
||||
$module.Result.stderr = $res.stderr
|
||||
$module.FailJson("Error getting list of pinned packages")
|
||||
}
|
||||
|
||||
$stdout = $res.stdout.Trim()
|
||||
$pins = @{}
|
||||
|
||||
$stdout.Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries) | ForEach-Object {
|
||||
$package = $_.Substring(0, $_.LastIndexOf("|"))
|
||||
$version = $_.Substring($_.LastIndexOf("|") + 1)
|
||||
|
||||
if ($pins.ContainsKey($package)) {
|
||||
$pinned_versions = $pins.$package
|
||||
} else {
|
||||
$pinned_versions = [System.Collections.Generic.List`1[String]]@()
|
||||
}
|
||||
$pinned_versions.Add($version)
|
||||
$pins.$package = $pinned_versions
|
||||
}
|
||||
return ,$pins
|
||||
}
|
||||
|
||||
Function Set-ChocolateyPin {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][String]$choco_path,
|
||||
[Parameter(Mandatory=$true)][String]$name,
|
||||
[Switch]$pin,
|
||||
[String]$version
|
||||
)
|
||||
if ($pin) {
|
||||
$action = "add"
|
||||
$err_msg = "Error pinning package '$name'"
|
||||
} else {
|
||||
$action = "remove"
|
||||
$err_msg = "Error unpinning package '$name'"
|
||||
}
|
||||
|
||||
$arguments = [System.Collections.ArrayList]@($choco_path, "pin", $action, "--name", $name)
|
||||
if ($version) {
|
||||
$err_msg += " at '$version'"
|
||||
$arguments.Add("--version") > $null
|
||||
$arguments.Add($version) > $null
|
||||
}
|
||||
$common_args = Get-CommonChocolateyArguments
|
||||
$arguments.AddRange($common_args)
|
||||
|
||||
$command = Argv-ToString -arguments $arguments
|
||||
$res = Run-Command -command $command
|
||||
if ($res.rc -ne 0) {
|
||||
$module.Result.command = $command
|
||||
$module.Result.rc = $res.rc
|
||||
$module.Result.stdout = $res.stdout
|
||||
$module.Result.stderr = $res.stderr
|
||||
$module.FailJson($err_msg)
|
||||
}
|
||||
$module.result.changed = $true
|
||||
}
|
||||
|
||||
Function Update-ChocolateyPackage {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][String]$choco_path,
|
||||
[Parameter(Mandatory=$true)][String[]]$packages,
|
||||
[bool]$allow_downgrade,
|
||||
[bool]$allow_empty_checksums,
|
||||
[bool]$allow_multiple,
|
||||
[bool]$allow_prerelease,
|
||||
[String]$architecture,
|
||||
[bool]$force,
|
||||
[bool]$ignore_checksums,
|
||||
[bool]$ignore_dependencies,
|
||||
[String]$install_args,
|
||||
[bool]$override_args,
|
||||
[String]$package_params,
|
||||
[String]$proxy_url,
|
||||
[String]$proxy_username,
|
||||
[String]$proxy_password,
|
||||
[bool]$skip_scripts,
|
||||
[String]$source,
|
||||
[String]$source_username,
|
||||
[String]$source_password,
|
||||
[int]$timeout,
|
||||
[String]$version
|
||||
)
|
||||
|
||||
$arguments = [System.Collections.ArrayList]@($choco_path, "upgrade")
|
||||
$arguments.AddRange($packages)
|
||||
|
||||
$common_params = @{
|
||||
allow_downgrade = $allow_downgrade
|
||||
allow_empty_checksums = $allow_empty_checksums
|
||||
allow_multiple = $allow_multiple
|
||||
allow_prerelease = $allow_prerelease
|
||||
architecture = $architecture
|
||||
force = $force
|
||||
ignore_checksums = $ignore_checksums
|
||||
ignore_dependencies = $ignore_dependencies
|
||||
install_args = $install_args
|
||||
override_args = $override_args
|
||||
package_params = $package_params
|
||||
proxy_url = $proxy_url
|
||||
proxy_username = $proxy_username
|
||||
proxy_password = $proxy_password
|
||||
skip_scripts = $skip_scripts
|
||||
source = $source
|
||||
source_username = $source_username
|
||||
source_password = $source_password
|
||||
timeout = $timeout
|
||||
version = $version
|
||||
}
|
||||
$common_args = Get-InstallChocolateyArguments @common_params
|
||||
$arguments.AddRange($common_args)
|
||||
|
||||
$command = Argv-ToString -arguments $arguments
|
||||
$res = Run-Command -command $command
|
||||
$module.Result.rc = $res.rc
|
||||
if ($res.rc -notin $successexitcodes) {
|
||||
$module.Result.command = $command
|
||||
$module.Result.stdout = $res.stdout
|
||||
$module.Result.stderr = $res.stderr
|
||||
$module.FailJson("Error updating package(s) '$($packages -join ", ")'")
|
||||
}
|
||||
|
||||
if ($module.Verbosity -gt 1) {
|
||||
$module.Result.stdout = $res.stdout
|
||||
}
|
||||
|
||||
if ($res.stdout -match ' upgraded (\d+)/\d+ package') {
|
||||
if ($Matches[1] -gt 0) {
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
# need to set to false in case the rc is not 0 and a failure didn't actually occur
|
||||
$module.Result.failed = $false
|
||||
}
|
||||
|
||||
Function Install-ChocolateyPackage {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][String]$choco_path,
|
||||
[Parameter(Mandatory=$true)][String[]]$packages,
|
||||
[bool]$allow_downgrade,
|
||||
[bool]$allow_empty_checksums,
|
||||
[bool]$allow_multiple,
|
||||
[bool]$allow_prerelease,
|
||||
[String]$architecture,
|
||||
[bool]$force,
|
||||
[bool]$ignore_checksums,
|
||||
[bool]$ignore_dependencies,
|
||||
[String]$install_args,
|
||||
[bool]$override_args,
|
||||
[String]$package_params,
|
||||
[String]$proxy_url,
|
||||
[String]$proxy_username,
|
||||
[String]$proxy_password,
|
||||
[bool]$skip_scripts,
|
||||
[String]$source,
|
||||
[String]$source_username,
|
||||
[String]$source_password,
|
||||
[int]$timeout,
|
||||
[String]$version
|
||||
)
|
||||
|
||||
$arguments = [System.Collections.ArrayList]@($choco_path, "install")
|
||||
$arguments.AddRange($packages)
|
||||
$common_params = @{
|
||||
allow_downgrade = $allow_downgrade
|
||||
allow_empty_checksums = $allow_empty_checksums
|
||||
allow_multiple = $allow_multiple
|
||||
allow_prerelease = $allow_prerelease
|
||||
architecture = $architecture
|
||||
force = $force
|
||||
ignore_checksums = $ignore_checksums
|
||||
ignore_dependencies = $ignore_dependencies
|
||||
install_args = $install_args
|
||||
override_args = $override_args
|
||||
package_params = $package_params
|
||||
proxy_url = $proxy_url
|
||||
proxy_username = $proxy_username
|
||||
proxy_password = $proxy_password
|
||||
skip_scripts = $skip_scripts
|
||||
source = $source
|
||||
source_username = $source_username
|
||||
source_password = $source_password
|
||||
timeout = $timeout
|
||||
version = $version
|
||||
}
|
||||
$common_args = Get-InstallChocolateyArguments @common_params
|
||||
$arguments.AddRange($common_args)
|
||||
|
||||
$command = Argv-ToString -arguments $arguments
|
||||
$res = Run-Command -command $command
|
||||
$module.Result.rc = $res.rc
|
||||
if ($res.rc -notin $successexitcodes) {
|
||||
$module.Result.command = $command
|
||||
$module.Result.stdout = $res.stdout
|
||||
$module.Result.stderr = $res.stderr
|
||||
$module.FailJson("Error installing package(s) '$($packages -join ', ')'")
|
||||
}
|
||||
|
||||
if ($module.Verbosity -gt 1) {
|
||||
$module.Result.stdout = $res.stdout
|
||||
}
|
||||
|
||||
$module.Result.changed = $true
|
||||
# need to set to false in case the rc is not 0 and a failure didn't actually occur
|
||||
$module.Result.failed = $false
|
||||
}
|
||||
|
||||
Function Uninstall-ChocolateyPackage {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][String]$choco_path,
|
||||
[Parameter(Mandatory=$true)][String[]]$packages,
|
||||
[bool]$force,
|
||||
[String]$package_params,
|
||||
[bool]$skip_scripts,
|
||||
[int]$timeout,
|
||||
[String]$version
|
||||
)
|
||||
|
||||
$arguments = [System.Collections.ArrayList]@($choco_path, "uninstall")
|
||||
$arguments.AddRange($packages)
|
||||
$common_args = Get-CommonChocolateyArguments
|
||||
$arguments.AddRange($common_args)
|
||||
|
||||
if ($force) {
|
||||
$arguments.Add("--force") > $null
|
||||
}
|
||||
if ($package_params) {
|
||||
$arguments.Add("--package-params") > $null
|
||||
$arguments.Add($package_params) > $null
|
||||
}
|
||||
if ($skip_scripts) {
|
||||
$arguments.Add("--skip-scripts") > $null
|
||||
}
|
||||
if ($null -ne $timeout) {
|
||||
$arguments.Add("--timeout") > $null
|
||||
$arguments.Add($timeout) > $null
|
||||
}
|
||||
if ($version) {
|
||||
# Need to set allow-multiple to make sure choco doesn't uninstall all versions
|
||||
$arguments.Add("--allow-multiple") > $null
|
||||
$arguments.Add("--version") > $null
|
||||
$arguments.Add($version) > $null
|
||||
} else {
|
||||
$arguments.Add("--all-versions") > $null
|
||||
}
|
||||
|
||||
$command = Argv-ToString -arguments $arguments
|
||||
$res = Run-Command -command $command
|
||||
$module.Result.rc = $res.rc
|
||||
if ($res.rc -notin $successexitcodes) {
|
||||
$module.Result.command = $command
|
||||
$module.Result.stdout = $res.stdout
|
||||
$module.Result.stderr = $res.stderr
|
||||
$module.FailJson("Error uninstalling package(s) '$($packages -join ", ")'")
|
||||
}
|
||||
|
||||
if ($module.Verbosity -gt 1) {
|
||||
$module.Result.stdout = $res.stdout
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
# need to set to false in case the rc is not 0 and a failure didn't actually occur
|
||||
$module.Result.failed = $false
|
||||
}
|
||||
|
||||
# get the full path to choco.exe, otherwise install/upgrade to at least 0.10.5
|
||||
$install_params = @{
|
||||
proxy_url = $proxy_url
|
||||
proxy_username = $proxy_username
|
||||
proxy_password = $proxy_password
|
||||
source = $source
|
||||
source_username = $source_username
|
||||
source_password = $source_password
|
||||
}
|
||||
if ($version -and "chocolatey" -in $name) {
|
||||
# If a version is set and chocolatey is in the package list, pass the chocolatey version to the bootstrapping
|
||||
# process.
|
||||
$install_params.version = $version
|
||||
}
|
||||
$choco_path = Install-Chocolatey @install_params
|
||||
|
||||
if ('all' -in $name -and $state -in @('present', 'reinstalled')) {
|
||||
$module.FailJson("Cannot specify the package name as 'all' when state=$state")
|
||||
}
|
||||
|
||||
# get the version of all specified packages
|
||||
$package_info = $name | Get-ChocolateyPackageVersion -choco_path $choco_path
|
||||
|
||||
if ($state -in "absent", "reinstalled") {
|
||||
$installed_packages = ($package_info.GetEnumerator() | Where-Object { $null -ne $_.Value }).Key
|
||||
if ($null -ne $installed_packages) {
|
||||
Uninstall-ChocolateyPackage -choco_path $choco_path -packages $installed_packages `
|
||||
-force $force -package_params $package_params -skip_scripts $skip_scripts `
|
||||
-timeout $timeout -version $version
|
||||
}
|
||||
|
||||
# ensure the package info for the uninstalled versions has been removed
|
||||
# so state=reinstall will install them in the next step
|
||||
foreach ($package in $installed_packages) {
|
||||
$package_info.$package = $null
|
||||
}
|
||||
}
|
||||
|
||||
if ($state -in @("downgrade", "latest", "present", "reinstalled")) {
|
||||
if ($state -eq "present" -and $force) {
|
||||
# when present and force, we just run the install step with the packages specified
|
||||
$missing_packages = $name
|
||||
} else {
|
||||
# otherwise only install the packages that are not installed
|
||||
$missing_packages = [System.Collections.ArrayList]@()
|
||||
foreach ($package in $package_info.GetEnumerator()) {
|
||||
if ($null -eq $package.Value) {
|
||||
$missing_packages.Add($package.Key) > $null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# if version is specified and installed version does not match or not
|
||||
# allow_multiple, throw error ignore this if force is set
|
||||
if ($state -eq "present" -and $null -ne $version -and -not $force) {
|
||||
foreach ($package in $name) {
|
||||
$package_versions = [System.Collections.ArrayList]$package_info.$package
|
||||
if ($package_versions.Count -gt 0) {
|
||||
if (-not $package_versions.Contains($version) -and -not $allow_multiple) {
|
||||
$module.FailJson("Chocolatey package '$package' is already installed with version(s) '$($package_versions -join "', '")' but was expecting '$version'. Either change the expected version, set state=latest, set allow_multiple=yes, or set force=yes to continue")
|
||||
} elseif ($version -notin $package_versions -and $allow_multiple) {
|
||||
# add the package back into the list of missing packages if installing multiple
|
||||
$missing_packages.Add($package) > $null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$common_args = @{
|
||||
choco_path = $choco_path
|
||||
allow_downgrade = ($state -eq "downgrade")
|
||||
allow_empty_checksums = $allow_empty_checksums
|
||||
allow_multiple = $allow_multiple
|
||||
allow_prerelease = $allow_prerelease
|
||||
architecture = $architecture
|
||||
force = $force
|
||||
ignore_checksums = $ignore_checksums
|
||||
ignore_dependencies = $ignore_dependencies
|
||||
install_args = $install_args
|
||||
override_args = $override_args
|
||||
package_params = $package_params
|
||||
proxy_url = $proxy_url
|
||||
proxy_username = $proxy_username
|
||||
proxy_password = $proxy_password
|
||||
skip_scripts = $skip_scripts
|
||||
source = $source
|
||||
source_username = $source_username
|
||||
source_password = $source_password
|
||||
timeout = $timeout
|
||||
version = $version
|
||||
}
|
||||
|
||||
if ($missing_packages) {
|
||||
Install-ChocolateyPackage -packages $missing_packages @common_args
|
||||
}
|
||||
|
||||
if ($state -eq "latest" -or ($state -eq "downgrade" -and $null -ne $version)) {
|
||||
# when in a downgrade/latest situation, we want to run choco upgrade on
|
||||
# the remaining packages that were already installed, don't run this if
|
||||
# state=downgrade and a version isn't specified (this will actually
|
||||
# upgrade a package)
|
||||
$installed_packages = ($package_info.GetEnumerator() | Where-Object { $null -ne $_.Value }).Key
|
||||
if ($null -ne $installed_packages) {
|
||||
Update-ChocolateyPackage -packages $installed_packages @common_args
|
||||
}
|
||||
}
|
||||
|
||||
# Now we want to pin/unpin any packages now that it has been installed/upgraded
|
||||
if ($null -ne $pinned) {
|
||||
$pins = Get-ChocolateyPin -choco_path $choco_path
|
||||
|
||||
foreach ($package in $name) {
|
||||
if ($pins.ContainsKey($package)) {
|
||||
if (-not $pinned -and $null -eq $version) {
|
||||
# No version is set and pinned=no, we want to remove all pins on the package. There is a bug in
|
||||
# 'choco pin remove' with multiple versions where an older version might be pinned but
|
||||
# 'choco pin remove' will still fail without an explicit version. Instead we take the literal
|
||||
# interpretation that pinned=no and no version means the package has no pins at all
|
||||
foreach ($v in $pins.$package) {
|
||||
Set-ChocolateyPin -choco_path $choco_path -name $package -version $v
|
||||
}
|
||||
} elseif ($null -ne $version -and $pins.$package.Contains($version) -ne $pinned) {
|
||||
Set-ChocolateyPin -choco_path $choco_path -name $package -pin:$pinned -version $version
|
||||
}
|
||||
} elseif ($pinned) {
|
||||
# Package had no pins but pinned=yes is set.
|
||||
Set-ChocolateyPin -choco_path $choco_path -name $package -pin -version $version
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
||||
|
@ -1,385 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2014, Trond Hindenes <trond@hindenes.com>
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_chocolatey
|
||||
version_added: '1.9'
|
||||
short_description: Manage packages using chocolatey
|
||||
description:
|
||||
- Manage packages using Chocolatey.
|
||||
- If Chocolatey is missing from the system, the module will install it.
|
||||
requirements:
|
||||
- chocolatey >= 0.10.5 (will be upgraded if older)
|
||||
options:
|
||||
allow_empty_checksums:
|
||||
description:
|
||||
- Allow empty checksums to be used for downloaded resource from non-secure
|
||||
locations.
|
||||
- Use M(win_chocolatey_feature) with the name C(allowEmptyChecksums) to
|
||||
control this option globally.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '2.2'
|
||||
allow_multiple:
|
||||
description:
|
||||
- Allow the installation of multiple packages when I(version) is specified.
|
||||
- Having multiple packages at different versions can cause issues if the
|
||||
package doesn't support this. Use at your own risk.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '2.8'
|
||||
allow_prerelease:
|
||||
description:
|
||||
- Allow the installation of pre-release packages.
|
||||
- If I(state) is C(latest), the latest pre-release package will be
|
||||
installed.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '2.6'
|
||||
architecture:
|
||||
description:
|
||||
- Force Chocolatey to install the package of a specific process
|
||||
architecture.
|
||||
- When setting C(x86), will ensure Chocolatey installs the x86 package
|
||||
even when on an x64 bit OS.
|
||||
type: str
|
||||
choices: [ default, x86 ]
|
||||
default: default
|
||||
version_added: '2.7'
|
||||
force:
|
||||
description:
|
||||
- Forces the install of a package, even if it already is installed.
|
||||
- Using I(force) will cause Ansible to always report that a change was
|
||||
made.
|
||||
type: bool
|
||||
default: no
|
||||
ignore_checksums:
|
||||
description:
|
||||
- Ignore the checksums provided by the package.
|
||||
- Use M(win_chocolatey_feature) with the name C(checksumFiles) to control
|
||||
this option globally.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '2.2'
|
||||
ignore_dependencies:
|
||||
description:
|
||||
- Ignore dependencies, only install/upgrade the package itself.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '2.1'
|
||||
install_args:
|
||||
description:
|
||||
- Arguments to pass to the native installer.
|
||||
- These are arguments that are passed directly to the installer the
|
||||
Chocolatey package runs, this is generally an advanced option.
|
||||
type: str
|
||||
version_added: '2.1'
|
||||
name:
|
||||
description:
|
||||
- Name of the package(s) to be installed.
|
||||
- Set to C(all) to run the action on all the installed packages.
|
||||
type: list
|
||||
required: yes
|
||||
override_args:
|
||||
description:
|
||||
- Override arguments of native installer with arguments provided by user.
|
||||
- Should install arguments be used exclusively without appending
|
||||
to current package passed arguments.
|
||||
type: bool
|
||||
version_added: '2.10'
|
||||
package_params:
|
||||
description:
|
||||
- Parameters to pass to the package.
|
||||
- These are parameters specific to the Chocolatey package and are generally
|
||||
documented by the package itself.
|
||||
- Before Ansible 2.7, this option was just I(params).
|
||||
type: str
|
||||
version_added: '2.1'
|
||||
aliases: [ params ]
|
||||
pinned:
|
||||
description:
|
||||
- Whether to pin the Chocolatey package or not.
|
||||
- If omitted then no checks on package pins are done.
|
||||
- Will pin/unpin the specific version if I(version) is set.
|
||||
- Will pin the latest version of a package if C(yes), I(version) is not set
|
||||
and and no pin already exists.
|
||||
- Will unpin all versions of a package if C(no) and I(version) is not set.
|
||||
- This is ignored when C(state=absent).
|
||||
type: bool
|
||||
version_added: '2.8'
|
||||
proxy_url:
|
||||
description:
|
||||
- Proxy URL used to install chocolatey and the package.
|
||||
- Use M(win_chocolatey_config) with the name C(proxy) to control this
|
||||
option globally.
|
||||
type: str
|
||||
version_added: '2.4'
|
||||
proxy_username:
|
||||
description:
|
||||
- Proxy username used to install Chocolatey and the package.
|
||||
- Before Ansible 2.7, users with double quote characters C(") would need to
|
||||
be escaped with C(\) beforehand. This is no longer necessary.
|
||||
- Use M(win_chocolatey_config) with the name C(proxyUser) to control this
|
||||
option globally.
|
||||
type: str
|
||||
version_added: '2.4'
|
||||
proxy_password:
|
||||
description:
|
||||
- Proxy password used to install Chocolatey and the package.
|
||||
- This value is exposed as a command argument and any privileged account
|
||||
can see this value when the module is running Chocolatey, define the
|
||||
password on the global config level with M(win_chocolatey_config) with
|
||||
name C(proxyPassword) to avoid this.
|
||||
type: str
|
||||
version_added: '2.4'
|
||||
skip_scripts:
|
||||
description:
|
||||
- Do not run I(chocolateyInstall.ps1) or I(chocolateyUninstall.ps1) scripts
|
||||
when installing a package.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '2.4'
|
||||
source:
|
||||
description:
|
||||
- Specify the source to retrieve the package from.
|
||||
- Use M(win_chocolatey_source) to manage global sources.
|
||||
- This value can either be the URL to a Chocolatey feed, a path to a folder
|
||||
containing C(.nupkg) packages or the name of a source defined by
|
||||
M(win_chocolatey_source).
|
||||
- This value is also used when Chocolatey is not installed as the location
|
||||
of the install.ps1 script and only supports URLs for this case.
|
||||
type: str
|
||||
source_username:
|
||||
description:
|
||||
- A username to use with I(source) when accessing a feed that requires
|
||||
authentication.
|
||||
- It is recommended you define the credentials on a source with
|
||||
M(win_chocolatey_source) instead of passing it per task.
|
||||
type: str
|
||||
version_added: '2.7'
|
||||
source_password:
|
||||
description:
|
||||
- The password for I(source_username).
|
||||
- This value is exposed as a command argument and any privileged account
|
||||
can see this value when the module is running Chocolatey, define the
|
||||
credentials with a source with M(win_chocolatey_source) to avoid this.
|
||||
type: str
|
||||
version_added: '2.7'
|
||||
state:
|
||||
description:
|
||||
- State of the package on the system.
|
||||
- When C(absent), will ensure the package is not installed.
|
||||
- When C(present), will ensure the package is installed.
|
||||
- When C(downgrade), will allow Chocolatey to downgrade a package if
|
||||
I(version) is older than the installed version.
|
||||
- When C(latest), will ensure the package is installed to the latest
|
||||
available version.
|
||||
- When C(reinstalled), will uninstall and reinstall the package.
|
||||
type: str
|
||||
choices: [ absent, downgrade, latest, present, reinstalled ]
|
||||
default: present
|
||||
timeout:
|
||||
description:
|
||||
- The time to allow chocolatey to finish before timing out.
|
||||
type: int
|
||||
default: 2700
|
||||
version_added: '2.3'
|
||||
aliases: [ execution_timeout ]
|
||||
validate_certs:
|
||||
description:
|
||||
- Used when downloading the Chocolatey install script if Chocolatey is not
|
||||
already installed, this does not affect the Chocolatey package install
|
||||
process.
|
||||
- When C(no), no SSL certificates will be validated.
|
||||
- This should only be used on personally controlled sites using self-signed
|
||||
certificate.
|
||||
type: bool
|
||||
default: yes
|
||||
version_added: '2.7'
|
||||
version:
|
||||
description:
|
||||
- Specific version of the package to be installed.
|
||||
- When I(state) is set to C(absent), will uninstall the specific version
|
||||
otherwise all versions of that package will be removed.
|
||||
- If a different version of package is installed, I(state) must be C(latest)
|
||||
or I(force) set to C(yes) to install the desired version.
|
||||
- Provide as a string (e.g. C('6.1')), otherwise it is considered to be
|
||||
a floating-point number and depending on the locale could become C(6,1),
|
||||
which will cause a failure.
|
||||
- If I(name) is set to C(chocolatey) and Chocolatey is not installed on the
|
||||
host, this will be the version of Chocolatey that is installed. You can
|
||||
also set the C(chocolateyVersion) environment var.
|
||||
type: str
|
||||
notes:
|
||||
- This module will install or upgrade Chocolatey when needed.
|
||||
- When using verbosity 2 or less (C(-vv)) the C(stdout) output will be restricted.
|
||||
When using verbosity 4 (C(-vvvv)) the C(stdout) output will be more verbose.
|
||||
When using verbosity 5 (C(-vvvvv)) the C(stdout) output will include debug output.
|
||||
- Some packages, like hotfixes or updates need an interactive user logon in
|
||||
order to install. You can use C(become) to achieve this, see
|
||||
:ref:`become_windows`.
|
||||
Even if you are connecting as local Administrator, using C(become) to
|
||||
become Administrator will give you an interactive user logon, see examples
|
||||
below.
|
||||
- If C(become) is unavailable, use M(win_hotfix) to install hotfixes instead
|
||||
of M(win_chocolatey) as M(win_hotfix) avoids using C(wusa.exe) which cannot
|
||||
be run without C(become).
|
||||
seealso:
|
||||
- module: win_chocolatey_config
|
||||
- module: win_chocolatey_facts
|
||||
- module: win_chocolatey_feature
|
||||
- module: win_chocolatey_source
|
||||
- module: win_feature
|
||||
- module: win_hotfix
|
||||
description: Use when C(become) is unavailable, to avoid using C(wusa.exe).
|
||||
- module: win_package
|
||||
- module: win_updates
|
||||
- name: Chocolatey website
|
||||
description: More information about the Chocolatey tool.
|
||||
link: http://chocolatey.org/
|
||||
- name: Chocolatey packages
|
||||
description: An overview of the available Chocolatey packages.
|
||||
link: http://chocolatey.org/packages
|
||||
- ref: become_windows
|
||||
description: Some packages, like hotfixes or updates need an interactive user logon
|
||||
in order to install. You can use C(become) to achieve this.
|
||||
author:
|
||||
- Trond Hindenes (@trondhindenes)
|
||||
- Peter Mounce (@petemounce)
|
||||
- Pepe Barbe (@elventear)
|
||||
- Adam Keech (@smadam813)
|
||||
- Pierre Templier (@ptemplier)
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
# TODO:
|
||||
# * Better parsing when a package has dependencies - currently fails
|
||||
# * Time each item that is run
|
||||
# * Support 'changed' with gems - would require shelling out to `gem list` first and parsing, kinda defeating the point of using chocolatey.
|
||||
# * Version provided not as string might be translated to 6,6 depending on Locale (results in errors)
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Install git
|
||||
win_chocolatey:
|
||||
name: git
|
||||
state: present
|
||||
|
||||
- name: Upgrade installed packages
|
||||
win_chocolatey:
|
||||
name: all
|
||||
state: latest
|
||||
|
||||
- name: Install notepadplusplus version 6.6
|
||||
win_chocolatey:
|
||||
name: notepadplusplus
|
||||
version: '6.6'
|
||||
|
||||
- name: Install notepadplusplus 32 bit version
|
||||
win_chocolatey:
|
||||
name: notepadplusplus
|
||||
architecture: x86
|
||||
|
||||
- name: Install git from specified repository
|
||||
win_chocolatey:
|
||||
name: git
|
||||
source: https://someserver/api/v2/
|
||||
|
||||
- name: Install git from a pre configured source (win_chocolatey_source)
|
||||
win_chocolatey:
|
||||
name: git
|
||||
source: internal_repo
|
||||
|
||||
- name: Ensure Chocolatey itself is installed and use internal repo as source
|
||||
win_chocolatey:
|
||||
name: chocolatey
|
||||
source: http://someserver/chocolatey
|
||||
|
||||
- name: Uninstall git
|
||||
win_chocolatey:
|
||||
name: git
|
||||
state: absent
|
||||
|
||||
- name: Install multiple packages
|
||||
win_chocolatey:
|
||||
name:
|
||||
- procexp
|
||||
- putty
|
||||
- windirstat
|
||||
state: present
|
||||
|
||||
- name: Install multiple packages sequentially
|
||||
win_chocolatey:
|
||||
name: '{{ item }}'
|
||||
state: present
|
||||
loop:
|
||||
- procexp
|
||||
- putty
|
||||
- windirstat
|
||||
|
||||
- name: Uninstall multiple packages
|
||||
win_chocolatey:
|
||||
name:
|
||||
- procexp
|
||||
- putty
|
||||
- windirstat
|
||||
state: absent
|
||||
|
||||
- name: Install curl using proxy
|
||||
win_chocolatey:
|
||||
name: curl
|
||||
proxy_url: http://proxy-server:8080/
|
||||
proxy_username: joe
|
||||
proxy_password: p@ssw0rd
|
||||
|
||||
- name: Install a package that requires 'become'
|
||||
win_chocolatey:
|
||||
name: officepro2013
|
||||
become: yes
|
||||
become_user: Administrator
|
||||
become_method: runas
|
||||
|
||||
- name: install and pin Notepad++ at 7.6.3
|
||||
win_chocolatey:
|
||||
name: notepadplusplus
|
||||
version: 7.6.3
|
||||
pinned: yes
|
||||
state: present
|
||||
|
||||
- name: remove all pins for Notepad++ on all versions
|
||||
win_chocolatey:
|
||||
name: notepadplusplus
|
||||
pinned: no
|
||||
state: present
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
command:
|
||||
description: The full command used in the chocolatey task.
|
||||
returned: changed
|
||||
type: str
|
||||
sample: choco.exe install -r --no-progress -y sysinternals --timeout 2700 --failonunfound
|
||||
rc:
|
||||
description: The return code from the chocolatey task.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 0
|
||||
stdout:
|
||||
description: The stdout from the chocolatey task. The verbosity level of the
|
||||
messages are affected by Ansible verbosity setting, see notes for more
|
||||
details.
|
||||
returned: changed
|
||||
type: str
|
||||
sample: Chocolatey upgraded 1/1 packages.
|
||||
'''
|
@ -1,122 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.ArgvParser
|
||||
#Requires -Module Ansible.ModuleUtils.CommandUtil
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$params = Parse-Args -arguments $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
$diff = 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 "absent", "present"
|
||||
$value = Get-AnsibleParam -obj $params -name "value" -type "str" -failifempty ($state -eq "present")
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
if ($diff) {
|
||||
$result.diff = @{
|
||||
before = $null
|
||||
after = $null
|
||||
}
|
||||
}
|
||||
|
||||
if ($state -eq "present") {
|
||||
if ($value -eq "") {
|
||||
Fail-Json -obj $result -message "Cannot set Chocolatey config as an empty string when state=present, use state=absent instead"
|
||||
}
|
||||
# make sure bool values are lower case
|
||||
if ($value -ceq "True" -or $value -ceq "False") {
|
||||
$value = $value.ToLower()
|
||||
}
|
||||
}
|
||||
|
||||
Function Get-ChocolateyConfig {
|
||||
param($choco_app)
|
||||
|
||||
# 'choco config list -r' does not display an easily parsable config entries
|
||||
# It contains config/sources/feature in the one command, and is in the
|
||||
# structure 'configKey = configValue | description', if the key or value
|
||||
# contains a = or |, it will make it quite hard to easily parse it,
|
||||
# compared to reading an XML file that already delimits these values
|
||||
$choco_config_path = "$(Split-Path -Path (Split-Path -Path $choco_app.Path))\config\chocolatey.config"
|
||||
if (-not (Test-Path -Path $choco_config_path)) {
|
||||
Fail-Json -obj $result -message "Expecting Chocolatey config file to exist at '$choco_config_path'"
|
||||
}
|
||||
|
||||
try {
|
||||
[xml]$choco_config = Get-Content -Path $choco_config_path
|
||||
} catch {
|
||||
Fail-Json -obj $result -message "Failed to parse Chocolatey config file at '$choco_config_path': $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$config_info = @{}
|
||||
foreach ($config in $choco_config.chocolatey.config.GetEnumerator()) {
|
||||
$config_info."$($config.key)" = $config.value
|
||||
}
|
||||
|
||||
return ,$config_info
|
||||
}
|
||||
|
||||
Function Remove-ChocolateyConfig {
|
||||
param(
|
||||
$choco_app,
|
||||
$name
|
||||
)
|
||||
$command = Argv-ToString -arguments @($choco_app.Path, "config", "unset", "--name", $name)
|
||||
$res = Run-Command -command $command
|
||||
if ($res.rc -ne 0) {
|
||||
Fail-Json -obj $result -message "Failed to unset Chocolatey config for '$name': $($res.stderr)"
|
||||
}
|
||||
}
|
||||
|
||||
Function Set-ChocolateyConfig {
|
||||
param(
|
||||
$choco_app,
|
||||
$name,
|
||||
$value
|
||||
)
|
||||
$command = Argv-ToString -arguments @($choco_app.Path, "config", "set", "--name", $name, "--value", $value)
|
||||
$res = Run-Command -command $command
|
||||
if ($res.rc -ne 0) {
|
||||
Fail-Json -obj $result -message "Failed to set Chocolatey config for '$name' to '$value': $($res.stderr)"
|
||||
}
|
||||
}
|
||||
|
||||
$choco_app = Get-Command -Name choco.exe -CommandType Application -ErrorAction SilentlyContinue
|
||||
if (-not $choco_app) {
|
||||
Fail-Json -obj $result -message "Failed to find Chocolatey installation, make sure choco.exe is in the PATH env value"
|
||||
}
|
||||
|
||||
$config_info = Get-ChocolateyConfig -choco_app $choco_app
|
||||
if ($name -notin $config_info.Keys) {
|
||||
Fail-Json -obj $result -message "The Chocolatey config '$name' is not an existing config value, check the spelling. Valid config names: $($config_info.Keys -join ', ')"
|
||||
}
|
||||
if ($diff) {
|
||||
$result.diff.before = $config_info.$name
|
||||
}
|
||||
|
||||
if ($state -eq "absent" -and $config_info.$name -ne "") {
|
||||
if (-not $check_mode) {
|
||||
Remove-ChocolateyConfig -choco_app $choco_app -name $name
|
||||
}
|
||||
$result.changed = $true
|
||||
# choco.exe config set is not case sensitive, it won't make a change if the
|
||||
# value is the same but doesn't match
|
||||
} elseif ($state -eq "present" -and $config_info.$name -ne $value) {
|
||||
if (-not $check_mode) {
|
||||
Set-ChocolateyConfig -choco_app $choco_app -name $name -value $value
|
||||
}
|
||||
$result.changed = $true
|
||||
if ($diff) {
|
||||
$result.diff.after = $value
|
||||
}
|
||||
}
|
||||
|
||||
Exit-Json -obj $result
|
@ -1,66 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_chocolatey_config
|
||||
version_added: '2.7'
|
||||
short_description: Manages Chocolatey config settings
|
||||
description:
|
||||
- Used to manage Chocolatey config settings as well as unset the values.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the config setting to manage.
|
||||
- See U(https://chocolatey.org/docs/chocolatey-configuration) for a list of
|
||||
valid configuration settings that can be changed.
|
||||
- Any config values that contain encrypted values like a password are not
|
||||
idempotent as the plaintext value cannot be read.
|
||||
type: str
|
||||
required: yes
|
||||
state:
|
||||
description:
|
||||
- When C(absent), it will ensure the setting is unset or blank.
|
||||
- When C(present), it will ensure the setting is set to the value of
|
||||
I(value).
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
value:
|
||||
description:
|
||||
- Used when C(state=present) that contains the value to set for the config
|
||||
setting.
|
||||
- Cannot be null or an empty string, use C(state=absent) to unset a config
|
||||
value instead.
|
||||
type: str
|
||||
seealso:
|
||||
- module: win_chocolatey
|
||||
- module: win_chocolatey_facts
|
||||
- module: win_chocolatey_feature
|
||||
- module: win_chocolatey_source
|
||||
author:
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Set the cache location
|
||||
win_chocolatey_config:
|
||||
name: cacheLocation
|
||||
state: present
|
||||
value: D:\chocolatey_temp
|
||||
|
||||
- name: Unset the cache location
|
||||
win_chocolatey_config:
|
||||
name: cacheLocation
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
@ -1,182 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# Copyright: (c) 2018, Simon Baerlocher <s.baerlocher@sbaerlocher.ch>
|
||||
# Copyright: (c) 2018, ITIGO AG <opensource@itigo.ch>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.ArgvParser
|
||||
#Requires -Module Ansible.ModuleUtils.CommandUtil
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
Set-StrictMode -Version 2.0
|
||||
|
||||
# Create a new result object
|
||||
$result = @{
|
||||
changed = $false
|
||||
ansible_facts = @{
|
||||
ansible_chocolatey = @{
|
||||
config = @{}
|
||||
feature = @{}
|
||||
sources = @()
|
||||
packages = @()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$choco_app = Get-Command -Name choco.exe -CommandType Application -ErrorAction SilentlyContinue
|
||||
if (-not $choco_app) {
|
||||
Fail-Json -obj $result -message "Failed to find Chocolatey installation, make sure choco.exe is in the PATH env value"
|
||||
}
|
||||
|
||||
Function Get-ChocolateyFeature {
|
||||
|
||||
param($choco_app)
|
||||
|
||||
$command = Argv-ToString -arguments $choco_app.Path, "feature", "list", "-r"
|
||||
$res = Run-Command -command $command
|
||||
if ($res.rc -ne 0) {
|
||||
$result.stdout = $res.stdout
|
||||
$result.stderr = $res.stderr
|
||||
$result.rc = $res.rc
|
||||
Fail-Json -obj $result -message "Failed to list Chocolatey features, see stderr"
|
||||
}
|
||||
|
||||
$feature_info = @{}
|
||||
$res.stdout -split "`r`n" | Where-Object { $_ -ne "" } | ForEach-Object {
|
||||
$feature_split = $_ -split "\|"
|
||||
$feature_info."$($feature_split[0])" = $feature_split[1] -eq "Enabled"
|
||||
}
|
||||
$result.ansible_facts.ansible_chocolatey.feature = $feature_info
|
||||
}
|
||||
|
||||
Function Get-ChocolateyConfig {
|
||||
|
||||
param($choco_app)
|
||||
|
||||
$choco_config_path = "$(Split-Path -Path (Split-Path -Path $choco_app.Path))\config\chocolatey.config"
|
||||
if (-not (Test-Path -Path $choco_config_path)) {
|
||||
Fail-Json -obj $result -message "Expecting Chocolatey config file to exist at '$choco_config_path'"
|
||||
}
|
||||
|
||||
try {
|
||||
[xml]$choco_config = Get-Content -Path $choco_config_path
|
||||
} catch {
|
||||
Fail-Json -obj $result -message "Failed to parse Chocolatey config file at '$choco_config_path': $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$config_info = @{}
|
||||
foreach ($config in $choco_config.chocolatey.config.GetEnumerator()) {
|
||||
# try and parse as a boot, then an int, fallback to string
|
||||
try {
|
||||
$value = [System.Boolean]::Parse($config.value)
|
||||
} catch {
|
||||
try {
|
||||
$value = [System.Int32]::Parse($config.value)
|
||||
} catch {
|
||||
$value = $config.value
|
||||
}
|
||||
}
|
||||
$config_info."$($config.key)" = $value
|
||||
}
|
||||
$result.ansible_facts.ansible_chocolatey.config = $config_info
|
||||
}
|
||||
|
||||
Function Get-ChocolateyPackages {
|
||||
|
||||
param($choco_app)
|
||||
|
||||
$command = Argv-ToString -arguments $choco_app.Path, "list", "--local-only", "--limit-output", "--all-versions"
|
||||
$res = Run-Command -command $command
|
||||
if ($res.rc -ne 0) {
|
||||
$result.stdout = $res.stdout
|
||||
$result.stderr = $res.stderr
|
||||
$result.rc = $res.rc
|
||||
Fail-Json -obj $result -message "Failed to list Chocolatey Packages, see stderr"
|
||||
}
|
||||
|
||||
$packages_info = [System.Collections.ArrayList]@()
|
||||
$res.stdout.Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries) | ForEach-Object {
|
||||
$packages_split = $_ -split "\|"
|
||||
$package_info = @{
|
||||
package = $packages_split[0]
|
||||
version = $packages_split[1]
|
||||
}
|
||||
$packages_info.Add($package_info) > $null
|
||||
}
|
||||
$result.ansible_facts.ansible_chocolatey.packages = $packages_info
|
||||
}
|
||||
|
||||
Function Get-ChocolateySources {
|
||||
param($choco_app)
|
||||
|
||||
$choco_config_path = "$(Split-Path -Path (Split-Path -Path $choco_app.Path))\config\chocolatey.config"
|
||||
if (-not (Test-Path -LiteralPath $choco_config_path)) {
|
||||
Fail-Json -obj $result -message "Expecting Chocolatey config file to exist at '$choco_config_path'"
|
||||
}
|
||||
|
||||
try {
|
||||
[xml]$choco_config = Get-Content -Path $choco_config_path
|
||||
} catch {
|
||||
Fail-Json -obj $result -message "Failed to parse Chocolatey config file at '$choco_config_path': $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$sources = [System.Collections.ArrayList]@()
|
||||
foreach ($xml_source in $choco_config.chocolatey.sources.GetEnumerator()) {
|
||||
$source_username = $xml_source.Attributes.GetNamedItem("user")
|
||||
if ($null -ne $source_username) {
|
||||
$source_username = $source_username.Value
|
||||
}
|
||||
|
||||
# 0.9.9.9+
|
||||
$priority = $xml_source.Attributes.GetNamedItem("priority")
|
||||
if ($null -ne $priority) {
|
||||
$priority = [int]$priority.Value
|
||||
}
|
||||
|
||||
# 0.9.10+
|
||||
$certificate = $xml_source.Attributes.GetNamedItem("certificate")
|
||||
if ($null -ne $certificate) {
|
||||
$certificate = $certificate.Value
|
||||
}
|
||||
|
||||
# 0.10.4+
|
||||
$bypass_proxy = $xml_source.Attributes.GetNamedItem("bypassProxy")
|
||||
if ($null -ne $bypass_proxy) {
|
||||
$bypass_proxy = [System.Convert]::ToBoolean($bypass_proxy.Value)
|
||||
}
|
||||
$allow_self_service = $xml_source.Attributes.GetNamedItem("selfService")
|
||||
if ($null -ne $allow_self_service) {
|
||||
$allow_self_service = [System.Convert]::ToBoolean($allow_self_service.Value)
|
||||
}
|
||||
|
||||
# 0.10.8+
|
||||
$admin_only = $xml_source.Attributes.GetNamedItem("adminOnly")
|
||||
if ($null -ne $admin_only) {
|
||||
$admin_only = [System.Convert]::ToBoolean($admin_only.Value)
|
||||
}
|
||||
|
||||
$source_info = @{
|
||||
name = $xml_source.id
|
||||
source = $xml_source.value
|
||||
disabled = [System.Convert]::ToBoolean($xml_source.disabled)
|
||||
source_username = $source_username
|
||||
priority = $priority
|
||||
certificate = $certificate
|
||||
bypass_proxy = $bypass_proxy
|
||||
allow_self_service = $allow_self_service
|
||||
admin_only = $admin_only
|
||||
}
|
||||
$sources.Add($source_info) > $null
|
||||
}
|
||||
$result.ansible_facts.ansible_chocolatey.sources = $sources
|
||||
}
|
||||
|
||||
Get-ChocolateyConfig -choco_app $choco_app
|
||||
Get-ChocolateyFeature -choco_app $choco_app
|
||||
Get-ChocolateyPackages -choco_app $choco_app
|
||||
Get-ChocolateySources -choco_app $choco_app
|
||||
|
||||
# Return result
|
||||
Exit-Json -obj $result
|
@ -1,144 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# Copyright: (c) 2018, Simon Baerlocher <s.baerlocher@sbaerlocher.ch>
|
||||
# Copyright: (c) 2018, ITIGO AG <opensource@itigo.ch>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_chocolatey_facts
|
||||
version_added: '2.8'
|
||||
short_description: Create a facts collection for Chocolatey
|
||||
description:
|
||||
- This module shows information from Chocolatey, such as installed packages, configuration, feature and sources.
|
||||
notes:
|
||||
- Chocolatey must be installed beforehand, use M(win_chocolatey) to do this.
|
||||
seealso:
|
||||
- module: win_chocolatey
|
||||
- module: win_chocolatey_config
|
||||
- module: win_chocolatey_feature
|
||||
- module: win_chocolatey_source
|
||||
author:
|
||||
- Simon Bärlocher (@sbaerlocher)
|
||||
- ITIGO AG (@itigoag)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Gather facts from chocolatey
|
||||
win_chocolatey_facts:
|
||||
|
||||
- name: Displays the Configuration
|
||||
debug:
|
||||
var: ansible_chocolatey.config
|
||||
|
||||
- name: Displays the Feature
|
||||
debug:
|
||||
var: ansible_chocolatey.feature
|
||||
|
||||
- name: Displays the Sources
|
||||
debug:
|
||||
var: ansible_chocolatey.sources
|
||||
|
||||
- name: Displays the Packages
|
||||
debug:
|
||||
var: ansible_chocolatey.packages
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
ansible_facts:
|
||||
description: Detailed information about the Chocolatey installation
|
||||
returned: always
|
||||
type: complex
|
||||
contains:
|
||||
ansible_chocolatey:
|
||||
description: Detailed information about the Chocolatey installation
|
||||
returned: always
|
||||
type: complex
|
||||
contains:
|
||||
config:
|
||||
description: Detailed information about stored the configurations
|
||||
returned: always
|
||||
type: dict
|
||||
sample:
|
||||
commandExecutionTimeoutSeconds: 2700
|
||||
containsLegacyPackageInstalls: true
|
||||
feature:
|
||||
description: Detailed information about enabled and disabled features
|
||||
returned: always
|
||||
type: dict
|
||||
sample:
|
||||
allowEmptyCheckums: false
|
||||
autoUninstaller: true
|
||||
failOnAutoUninstaller: false
|
||||
sources:
|
||||
description: List of Chocolatey sources
|
||||
returned: always
|
||||
type: complex
|
||||
contains:
|
||||
admin_only:
|
||||
description: Is the source visible to Administrators only
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
allow_self_service:
|
||||
description: Is the source allowed to be used with self-service
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
bypass_proxy:
|
||||
description: Can the source explicitly bypass configured proxies
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
certificate:
|
||||
description: Path to a PFX certificate for X509 authenticated feeds
|
||||
returned: always
|
||||
type: str
|
||||
sample: C:\chocolatey\cert.pfx
|
||||
disabled:
|
||||
description: Is the source disabled
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
name:
|
||||
description: Name of the source
|
||||
returned: always
|
||||
type: str
|
||||
sample: chocolatey
|
||||
priority:
|
||||
description: The priority order of this source, lower is better, 0 is no priority
|
||||
returned: always
|
||||
type: int
|
||||
sample: 0
|
||||
source:
|
||||
description: The source, can be a folder/file or an url
|
||||
returned: always
|
||||
type: str
|
||||
sample: https://chocolatey.org/api/v2/
|
||||
source_username:
|
||||
description: Username used to access authenticated feeds
|
||||
returned: always
|
||||
type: str
|
||||
sample: username
|
||||
packages:
|
||||
description: List of installed Packages
|
||||
returned: always
|
||||
type: complex
|
||||
contains:
|
||||
package:
|
||||
description: Name of the package
|
||||
returned: always
|
||||
type: str
|
||||
sample: vscode
|
||||
version:
|
||||
description: Version of the package
|
||||
returned: always
|
||||
type: str
|
||||
sample: '1.27.2'
|
||||
'''
|
@ -1,74 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c), 2018 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.CommandUtil
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$params = Parse-Args -arguments $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -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 "enabled" -validateset "disabled", "enabled"
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
|
||||
Function Get-ChocolateyFeatures {
|
||||
param($choco_app)
|
||||
|
||||
$res = Run-Command -command "`"$($choco_app.Path)`" feature list -r"
|
||||
if ($res.rc -ne 0) {
|
||||
Fail-Json -obj $result -message "Failed to list Chocolatey features: $($res.stderr)"
|
||||
}
|
||||
$feature_info = @{}
|
||||
$res.stdout -split "`r`n" | Where-Object { $_ -ne "" } | ForEach-Object {
|
||||
$feature_split = $_ -split "\|"
|
||||
$feature_info."$($feature_split[0])" = $feature_split[1] -eq "Enabled"
|
||||
}
|
||||
|
||||
return ,$feature_info
|
||||
}
|
||||
|
||||
Function Set-ChocolateyFeature {
|
||||
param(
|
||||
$choco_app,
|
||||
$name,
|
||||
$enabled
|
||||
)
|
||||
|
||||
if ($enabled) {
|
||||
$state_string = "enable"
|
||||
} else {
|
||||
$state_string = "disable"
|
||||
}
|
||||
$res = Run-Command -command "`"$($choco_app.Path)`" feature $state_string --name `"$name`""
|
||||
if ($res.rc -ne 0) {
|
||||
Fail-Json -obj $result -message "Failed to set Chocolatey feature $name to $($state_string): $($res.stderr)"
|
||||
}
|
||||
}
|
||||
|
||||
$choco_app = Get-Command -Name choco.exe -CommandType Application -ErrorAction SilentlyContinue
|
||||
if (-not $choco_app) {
|
||||
Fail-Json -obj $result -message "Failed to find Chocolatey installation, make sure choco.exe is in the PATH env value"
|
||||
}
|
||||
|
||||
$feature_info = Get-ChocolateyFeatures -choco_app $choco_app
|
||||
if ($name -notin $feature_info.keys) {
|
||||
Fail-Json -obj $result -message "Invalid feature name '$name' specified, valid features are: $($feature_info.keys -join ', ')"
|
||||
}
|
||||
|
||||
$expected_status = $state -eq "enabled"
|
||||
$feature_status = $feature_info.$name
|
||||
if ($feature_status -ne $expected_status) {
|
||||
if (-not $check_mode) {
|
||||
Set-ChocolateyFeature -choco_app $choco_app -name $name -enabled $expected_status
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
Exit-Json -obj $result
|
@ -1,55 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_chocolatey_feature
|
||||
version_added: '2.7'
|
||||
short_description: Manages Chocolatey features
|
||||
description:
|
||||
- Used to enable or disable features in Chocolatey.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the feature to manage.
|
||||
- Run C(choco.exe feature list) to get a list of features that can be
|
||||
managed.
|
||||
type: str
|
||||
required: yes
|
||||
state:
|
||||
description:
|
||||
- When C(disabled) then the feature will be disabled.
|
||||
- When C(enabled) then the feature will be enabled.
|
||||
type: str
|
||||
choices: [ disabled, enabled ]
|
||||
default: enabled
|
||||
seealso:
|
||||
- module: win_chocolatey
|
||||
- module: win_chocolatey_config
|
||||
- module: win_chocolatey_facts
|
||||
- module: win_chocolatey_source
|
||||
author:
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Disable file checksum matching
|
||||
win_chocolatey_feature:
|
||||
name: checksumFiles
|
||||
state: disabled
|
||||
|
||||
- name: Stop Chocolatey on the first package failure
|
||||
win_chocolatey_feature:
|
||||
name: stopOnFirstPackageFailure
|
||||
state: enabled
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
@ -1,306 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.ArgvParser
|
||||
#Requires -Module Ansible.ModuleUtils.CommandUtil
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$params = Parse-Args -arguments $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
$diff = 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 "absent", "disabled", "present"
|
||||
|
||||
$admin_only = Get-AnsibleParam -obj $params -name "admin_only" -type "bool"
|
||||
$allow_self_service = Get-AnsibleParam -obj $params -name "allow_self_service" -type "bool"
|
||||
$bypass_proxy = Get-AnsibleParam -obj $params -name "bypass_proxy" -type "bool"
|
||||
$certificate = Get-AnsibleParam -obj $params -name "certificate" -type "str"
|
||||
$certificate_password = Get-AnsibleParam -obj $params -name "certificate_password" -type "str"
|
||||
$priority = Get-AnsibleParam -obj $params -name "priority" -type "int"
|
||||
$source = Get-AnsibleParam -obj $params -name "source" -type "str"
|
||||
$source_username = Get-AnsibleParam -obj $params -name "source_username" -type "str"
|
||||
$source_password = Get-AnsibleParam -obj $params -name "source_password" -type "str" -failifempty ($null -ne $source_username)
|
||||
$update_password = Get-AnsibleParam -obj $params -name "update_password" -type "str" -default "always" -validateset "always", "on_create"
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
if ($diff) {
|
||||
$result.diff = @{
|
||||
before = @{}
|
||||
after = @{}
|
||||
}
|
||||
}
|
||||
|
||||
Function Get-ChocolateySources {
|
||||
param($choco_app)
|
||||
|
||||
$choco_config_path = "$(Split-Path -Path (Split-Path -Path $choco_app.Path))\config\chocolatey.config"
|
||||
if (-not (Test-Path -LiteralPath $choco_config_path)) {
|
||||
Fail-Json -obj $result -message "Expecting Chocolatey config file to exist at '$choco_config_path'"
|
||||
}
|
||||
|
||||
# would prefer to enumerate the existing sources with an actual API but the
|
||||
# only stable interface is choco.exe source list and that does not output
|
||||
# the sources in an easily parsable list. Using -r will split each entry by
|
||||
# | like a psv but does not quote values that have a | already in it making
|
||||
# it inadequete for our tasks. Instead we will parse the chocolatey.config
|
||||
# file and get the values from there
|
||||
try {
|
||||
[xml]$choco_config = Get-Content -Path $choco_config_path
|
||||
} catch {
|
||||
Fail-Json -obj $result -message "Failed to parse Chocolatey config file at '$choco_config_path': $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$sources = [System.Collections.ArrayList]@()
|
||||
foreach ($xml_source in $choco_config.chocolatey.sources.GetEnumerator()) {
|
||||
$source_username = $xml_source.Attributes.GetNamedItem("user")
|
||||
if ($null -ne $source_username) {
|
||||
$source_username = $source_username.Value
|
||||
}
|
||||
|
||||
# 0.9.9.9+
|
||||
$priority = $xml_source.Attributes.GetNamedItem("priority")
|
||||
if ($null -ne $priority) {
|
||||
$priority = [int]$priority.Value
|
||||
}
|
||||
|
||||
# 0.9.10+
|
||||
$certificate = $xml_source.Attributes.GetNamedItem("certificate")
|
||||
if ($null -ne $certificate) {
|
||||
$certificate = $certificate.Value
|
||||
}
|
||||
|
||||
# 0.10.4+
|
||||
$bypass_proxy = $xml_source.Attributes.GetNamedItem("bypassProxy")
|
||||
if ($null -ne $bypass_proxy) {
|
||||
$bypass_proxy = [System.Convert]::ToBoolean($bypass_proxy.Value)
|
||||
}
|
||||
$allow_self_service = $xml_source.Attributes.GetNamedItem("selfService")
|
||||
if ($null -ne $allow_self_service) {
|
||||
$allow_self_service = [System.Convert]::ToBoolean($allow_self_service.Value)
|
||||
}
|
||||
|
||||
# 0.10.8+
|
||||
$admin_only = $xml_source.Attributes.GetNamedItem("adminOnly")
|
||||
if ($null -ne $admin_only) {
|
||||
$admin_only = [System.Convert]::ToBoolean($admin_only.Value)
|
||||
}
|
||||
|
||||
$source_info = @{
|
||||
name = $xml_source.id
|
||||
source = $xml_source.value
|
||||
disabled = [System.Convert]::ToBoolean($xml_source.disabled)
|
||||
source_username = $source_username
|
||||
priority = $priority
|
||||
certificate = $certificate
|
||||
bypass_proxy = $bypass_proxy
|
||||
allow_self_service = $allow_self_service
|
||||
admin_only = $admin_only
|
||||
}
|
||||
$sources.Add($source_info) > $null
|
||||
}
|
||||
return ,$sources
|
||||
}
|
||||
|
||||
Function New-ChocolateySource {
|
||||
param(
|
||||
$choco_app,
|
||||
$name,
|
||||
$source,
|
||||
$source_username,
|
||||
$source_password,
|
||||
$certificate,
|
||||
$certificate_password,
|
||||
$priority,
|
||||
$bypass_proxy,
|
||||
$allow_self_service,
|
||||
$admin_only
|
||||
)
|
||||
# build the base arguments
|
||||
$arguments = [System.Collections.ArrayList]@($choco_app.Path,
|
||||
"source", "add", "--name", $name, "--source", $source
|
||||
)
|
||||
|
||||
# add optional arguments from user input
|
||||
if ($null -ne $source_username) {
|
||||
$arguments.Add("--user") > $null
|
||||
$arguments.Add($source_username) > $null
|
||||
$arguments.Add("--password") > $null
|
||||
$arguments.Add($source_password) > $null
|
||||
}
|
||||
if ($null -ne $certificate) {
|
||||
$arguments.Add("--cert") > $null
|
||||
$arguments.Add($certificate) > $null
|
||||
}
|
||||
if ($null -ne $certificate_password) {
|
||||
$arguments.Add("--certpassword") > $null
|
||||
$arguments.Add($certificate_password) > $null
|
||||
}
|
||||
if ($null -ne $priority) {
|
||||
$arguments.Add("--priority") > $null
|
||||
$arguments.Add($priority) > $null
|
||||
} else {
|
||||
$priority = 0
|
||||
}
|
||||
if ($bypass_proxy -eq $true) {
|
||||
$arguments.Add("--bypass-proxy") > $null
|
||||
} else {
|
||||
$bypass_proxy = $false
|
||||
}
|
||||
if ($allow_self_service -eq $true) {
|
||||
$arguments.Add("--allow-self-service") > $null
|
||||
} else {
|
||||
$allow_self_service = $false
|
||||
}
|
||||
if ($admin_only -eq $true) {
|
||||
$arguments.Add("--admin-only") > $null
|
||||
} else {
|
||||
$admin_only = $false
|
||||
}
|
||||
|
||||
if ($check_mode) {
|
||||
$arguments.Add("--what-if") > $null
|
||||
}
|
||||
|
||||
$command = Argv-ToString -arguments $arguments
|
||||
$res = Run-Command -command $command
|
||||
if ($res.rc -ne 0) {
|
||||
Fail-Json -obj $result -message "Failed to add Chocolatey source '$name': $($res.stderr)"
|
||||
}
|
||||
|
||||
$source_info = @{
|
||||
name = $name
|
||||
source = $source
|
||||
disabled = $false
|
||||
source_username = $source_username
|
||||
priority = $priority
|
||||
certificate = $certificate
|
||||
bypass_proxy = $bypass_proxy
|
||||
allow_self_service = $allow_self_service
|
||||
admin_only = $admin_only
|
||||
}
|
||||
return ,$source_info
|
||||
}
|
||||
|
||||
Function Remove-ChocolateySource {
|
||||
param(
|
||||
$choco_app,
|
||||
$name
|
||||
)
|
||||
$arguments = [System.Collections.ArrayList]@($choco_app.Path, "source", "remove", "--name", $name)
|
||||
if ($check_mode) {
|
||||
$arguments.Add("--what-if") > $null
|
||||
}
|
||||
$command = Argv-ToString -arguments $arguments
|
||||
$res = Run-Command -command $command
|
||||
if ($res.rc -ne 0) {
|
||||
Fail-Json -obj $result -message "Failed to remove Chocolatey source '$name': $($_.res.stderr)"
|
||||
}
|
||||
}
|
||||
|
||||
$choco_app = Get-Command -Name choco.exe -CommandType Application -ErrorAction SilentlyContinue
|
||||
if (-not $choco_app) {
|
||||
Fail-Json -obj $result -message "Failed to find Chocolatey installation, make sure choco.exe is in the PATH env value"
|
||||
}
|
||||
$actual_sources = Get-ChocolateySources -choco_app $choco_app
|
||||
$actual_source = $actual_sources | Where-Object { $_.name -eq $name }
|
||||
if ($diff) {
|
||||
if ($null -ne $actual_source) {
|
||||
$before = $actual_source.Clone()
|
||||
} else {
|
||||
$before = @{}
|
||||
}
|
||||
$result.diff.before = $before
|
||||
}
|
||||
|
||||
if ($state -eq "absent" -and $null -ne $actual_source) {
|
||||
Remove-ChocolateySource -choco_app $choco_app -name $name
|
||||
$result.changed = $true
|
||||
} elseif ($state -in ("disabled", "present")) {
|
||||
$change = $false
|
||||
if ($null -eq $actual_source) {
|
||||
if ($null -eq $source) {
|
||||
Fail-Json -obj $result -message "The source option must be set when creating a new source"
|
||||
}
|
||||
$change = $true
|
||||
} else {
|
||||
if ($null -ne $source -and $source -ne $actual_source.source) {
|
||||
$change = $true
|
||||
}
|
||||
if ($null -ne $source_username -and $source_username -ne $actual_source.source_username) {
|
||||
$change = $true
|
||||
}
|
||||
if ($null -ne $source_password -and $update_password -eq "always") {
|
||||
$change = $true
|
||||
}
|
||||
if ($null -ne $certificate -and $certificate -ne $actual_source.certificate) {
|
||||
$change = $true
|
||||
}
|
||||
if ($null -ne $certificate_password -and $update_password -eq "always") {
|
||||
$change = $true
|
||||
}
|
||||
if ($null -ne $priority -and $priority -ne $actual_source.priority) {
|
||||
$change = $true
|
||||
}
|
||||
if ($null -ne $bypass_proxy -and $bypass_proxy -ne $actual_source.bypass_proxy) {
|
||||
$change = $true
|
||||
}
|
||||
if ($null -ne $allow_self_service -and $allow_self_service -ne $actual_source.allow_self_service) {
|
||||
$change = $true
|
||||
}
|
||||
if ($null -ne $admin_only -and $admin_only -ne $actual_source.admin_only) {
|
||||
$change = $true
|
||||
}
|
||||
|
||||
if ($change) {
|
||||
Remove-ChocolateySource -choco_app $choco_app -name $name
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($change) {
|
||||
$actual_source = New-ChocolateySource -choco_app $choco_app -name $name -source $source `
|
||||
-source_username $source_username -source_password $source_password `
|
||||
-certificate $certificate -certificate_password $certificate_password `
|
||||
-priority $priority -bypass_proxy $bypass_proxy -allow_self_service $allow_self_service `
|
||||
-admin_only $admin_only
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
# enable/disable the source if necessary
|
||||
$status_action = $null
|
||||
if ($state -ne "disabled" -and $actual_source.disabled) {
|
||||
$status_action = "enable"
|
||||
} elseif ($state -eq "disabled" -and (-not $actual_source.disabled)) {
|
||||
$status_action = "disable"
|
||||
}
|
||||
if ($null -ne $status_action) {
|
||||
$arguments = [System.Collections.ArrayList]@($choco_app.Path, "source", $status_action, "--name", $name)
|
||||
if ($check_mode) {
|
||||
$arguments.Add("--what-if") > $null
|
||||
}
|
||||
$command = Argv-ToString -arguments $arguments
|
||||
$res = Run-Command -command $command
|
||||
if ($res.rc -ne 0) {
|
||||
Fail-Json -obj $result -message "Failed to $status_action Chocolatey source '$name': $($res.stderr)"
|
||||
}
|
||||
$actual_source.disabled = ($status_action -eq "disable")
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
if ($diff) {
|
||||
$after = $actual_source
|
||||
$result.diff.after = $after
|
||||
}
|
||||
}
|
||||
|
||||
# finally remove the diff if there was no change
|
||||
if (-not $result.changed -and $diff) {
|
||||
$result.diff = @{}
|
||||
}
|
||||
|
||||
Exit-Json -obj $result
|
@ -1,128 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_chocolatey_source
|
||||
version_added: '2.7'
|
||||
short_description: Manages Chocolatey sources
|
||||
description:
|
||||
- Used to managed Chocolatey sources configured on the client.
|
||||
- Requires Chocolatey to be already installed on the remote host.
|
||||
options:
|
||||
admin_only:
|
||||
description:
|
||||
- Makes the source visible to Administrators only.
|
||||
- Requires Chocolatey >= 0.10.8.
|
||||
- When creating a new source, this defaults to C(no).
|
||||
type: bool
|
||||
allow_self_service:
|
||||
description:
|
||||
- Allow the source to be used with self-service
|
||||
- Requires Chocolatey >= 0.10.4.
|
||||
- When creating a new source, this defaults to C(no).
|
||||
type: bool
|
||||
bypass_proxy:
|
||||
description:
|
||||
- Bypass the proxy when using this source.
|
||||
- Requires Chocolatey >= 0.10.4.
|
||||
- When creating a new source, this defaults to C(no).
|
||||
type: bool
|
||||
certificate:
|
||||
description:
|
||||
- The path to a .pfx file to use for X509 authenticated feeds.
|
||||
- Requires Chocolatey >= 0.9.10.
|
||||
type: str
|
||||
certificate_password:
|
||||
description:
|
||||
- The password for I(certificate) if required.
|
||||
- Requires Chocolatey >= 0.9.10.
|
||||
name:
|
||||
description:
|
||||
- The name of the source to configure.
|
||||
required: yes
|
||||
priority:
|
||||
description:
|
||||
- The priority order of this source compared to other sources, lower is
|
||||
better.
|
||||
- All priorities above C(0) will be evaluated first, then zero-based values
|
||||
will be evaluated in config file order.
|
||||
- Requires Chocolatey >= 0.9.9.9.
|
||||
- When creating a new source, this defaults to C(0).
|
||||
type: int
|
||||
source:
|
||||
description:
|
||||
- The file/folder/url of the source.
|
||||
- Required when I(state) is C(present) or C(disabled) and the source does
|
||||
not already exist.
|
||||
source_username:
|
||||
description:
|
||||
- The username used to access I(source).
|
||||
source_password:
|
||||
description:
|
||||
- The password for I(source_username).
|
||||
- Required if I(source_username) is set.
|
||||
state:
|
||||
description:
|
||||
- When C(absent), will remove the source.
|
||||
- When C(disabled), will ensure the source exists but is disabled.
|
||||
- When C(present), will ensure the source exists and is enabled.
|
||||
choices:
|
||||
- absent
|
||||
- disabled
|
||||
- present
|
||||
default: present
|
||||
update_password:
|
||||
description:
|
||||
- When C(always), the module will always set the password and report a
|
||||
change if I(certificate_password) or I(source_password) is set.
|
||||
- When C(on_create), the module will only set the password if the source
|
||||
is being created.
|
||||
choices:
|
||||
- always
|
||||
- on_create
|
||||
default: always
|
||||
seealso:
|
||||
- module: win_chocolatey
|
||||
- module: win_chocolatey_config
|
||||
- module: win_chocolatey_facts
|
||||
- module: win_chocolatey_feature
|
||||
author:
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Remove the default public source
|
||||
win_chocolatey_source:
|
||||
name: chocolatey
|
||||
state: absent
|
||||
|
||||
- name: Add new internal source
|
||||
win_chocolatey_source:
|
||||
name: internal repo
|
||||
state: present
|
||||
source: http://chocolatey-server/chocolatey
|
||||
|
||||
- name: Create HTTP source with credentials
|
||||
win_chocolatey_source:
|
||||
name: internal repo
|
||||
state: present
|
||||
source: https://chocolatey-server/chocolatey
|
||||
source_username: username
|
||||
source_password: password
|
||||
|
||||
- name: Disable Chocolatey source
|
||||
win_chocolatey_source:
|
||||
name: chocolatey
|
||||
state: disabled
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
@ -1,54 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2019, RusoSova
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#AnsibleRequires -OSVersion 6.1
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
owner = @{ type="str" }
|
||||
organization = @{ type="str" }
|
||||
description = @{ type="str" }
|
||||
}
|
||||
required_one_of = @(
|
||||
,@('owner', 'organization', 'description')
|
||||
)
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$owner = $module.Params.owner
|
||||
$organization = $module.Params.organization
|
||||
$description = $module.Params.description
|
||||
$regPath="HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\"
|
||||
|
||||
#Change description
|
||||
if ($description -or $description -eq "") {
|
||||
$descriptionObject=Get-CimInstance -class "Win32_OperatingSystem"
|
||||
if ($description -cne $descriptionObject.description) {
|
||||
Set-CimInstance -InputObject $descriptionObject -Property @{"Description"="$description"} -WhatIf:$module.CheckMode
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
#Change owner
|
||||
if ($owner -or $owner -eq "") {
|
||||
$curentOwner=(Get-ItemProperty -LiteralPath $regPath -Name RegisteredOwner).RegisteredOwner
|
||||
if ($curentOwner -cne $owner) {
|
||||
Set-ItemProperty -LiteralPath $regPath -Name "RegisteredOwner" -Value $owner -WhatIf:$module.CheckMode
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
#Change organization
|
||||
if ($organization -or $organization -eq "") {
|
||||
$curentOrganization=(Get-ItemProperty -LiteralPath $regPath -Name RegisteredOrganization).RegisteredOrganization
|
||||
if ($curentOrganization -cne $organization) {
|
||||
Set-ItemProperty -LiteralPath $regPath -Name "RegisteredOrganization" -Value $organization -WhatIf:$module.CheckMode
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
$module.ExitJson()
|
@ -1,75 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, RusoSova
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_computer_description
|
||||
short_description: Set windows description, owner and organization
|
||||
description:
|
||||
- This module sets Windows description that is shown under My Computer properties. Module also sets
|
||||
Windows license owner and organization. License information can be viewed by running winver commad.
|
||||
options:
|
||||
description:
|
||||
description:
|
||||
- String value to apply to Windows descripton. Specify value of "" to clear the value.
|
||||
required: false
|
||||
type: str
|
||||
organization:
|
||||
description:
|
||||
- String value of organization that the Windows is licensed to. Specify value of "" to clear the value.
|
||||
required: false
|
||||
type: str
|
||||
owner:
|
||||
description:
|
||||
- String value of the persona that the Windows is licensed to. Specify value of "" to clear the value.
|
||||
required: false
|
||||
type: str
|
||||
version_added: '2.10'
|
||||
author:
|
||||
- RusoSova (@RusoSova)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Set Windows description, owner and organization
|
||||
win_computer_description:
|
||||
description: Best Box
|
||||
owner: RusoSova
|
||||
organization: MyOrg
|
||||
register: result
|
||||
|
||||
- name: Set Windows description only
|
||||
win_computer_description:
|
||||
description: This is my Windows machine
|
||||
register: result
|
||||
|
||||
- name: Set organization and clear owner field
|
||||
win_computer_description:
|
||||
owner: ''
|
||||
organization: Black Mesa
|
||||
|
||||
- name: Clear organization, description and owner
|
||||
win_computer_description:
|
||||
organization: ""
|
||||
owner: ""
|
||||
description: ""
|
||||
register: result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
#
|
||||
'''
|
@ -1,714 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#Requires -Module Ansible.ModuleUtils.AddType
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
alias = @{ type = "str" }
|
||||
attributes = @{
|
||||
type = "list"
|
||||
elements = "dict"
|
||||
options = @{
|
||||
name = @{ type = "str"; required = $true }
|
||||
data = @{ type = "str" }
|
||||
data_format = @{ type = "str"; default = "text"; choices = @("base64", "text") }
|
||||
}
|
||||
}
|
||||
comment = @{ type = "str" }
|
||||
name = @{ type = "str"; required = $true }
|
||||
persistence = @{ type = "str"; default = "local"; choices = @("enterprise", "local") }
|
||||
secret = @{ type = "str"; no_log = $true }
|
||||
secret_format = @{ type = "str"; default = "text"; choices = @("base64", "text") }
|
||||
state = @{ type = "str"; default = "present"; choices = @("absent", "present") }
|
||||
type = @{
|
||||
type = "str"
|
||||
required = $true
|
||||
choices = @("domain_password", "domain_certificate", "generic_password", "generic_certificate")
|
||||
}
|
||||
update_secret = @{ type = "str"; default = "always"; choices = @("always", "on_create") }
|
||||
username = @{ type = "str" }
|
||||
}
|
||||
required_if = @(
|
||||
,@("state", "present", @("username"))
|
||||
)
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$alias = $module.Params.alias
|
||||
$attributes = $module.Params.attributes
|
||||
$comment = $module.Params.comment
|
||||
$name = $module.Params.name
|
||||
$persistence = $module.Params.persistence
|
||||
$secret = $module.Params.secret
|
||||
$secret_format = $module.Params.secret_format
|
||||
$state = $module.Params.state
|
||||
$type = $module.Params.type
|
||||
$update_secret = $module.Params.update_secret
|
||||
$username = $module.Params.username
|
||||
|
||||
$module.Diff.before = ""
|
||||
$module.Diff.after = ""
|
||||
|
||||
Add-CSharpType -AnsibleModule $module -References @'
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ansible.CredentialManager
|
||||
{
|
||||
internal class NativeHelpers
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public class CREDENTIAL
|
||||
{
|
||||
public CredentialFlags Flags;
|
||||
public CredentialType Type;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string TargetName;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string Comment;
|
||||
public FILETIME LastWritten;
|
||||
public UInt32 CredentialBlobSize;
|
||||
public IntPtr CredentialBlob;
|
||||
public CredentialPersist Persist;
|
||||
public UInt32 AttributeCount;
|
||||
public IntPtr Attributes;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string TargetAlias;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string UserName;
|
||||
|
||||
public static explicit operator Credential(CREDENTIAL v)
|
||||
{
|
||||
byte[] secret = new byte[(int)v.CredentialBlobSize];
|
||||
if (v.CredentialBlob != IntPtr.Zero)
|
||||
Marshal.Copy(v.CredentialBlob, secret, 0, secret.Length);
|
||||
|
||||
List<CredentialAttribute> attributes = new List<CredentialAttribute>();
|
||||
if (v.AttributeCount > 0)
|
||||
{
|
||||
CREDENTIAL_ATTRIBUTE[] rawAttributes = new CREDENTIAL_ATTRIBUTE[v.AttributeCount];
|
||||
Credential.PtrToStructureArray(rawAttributes, v.Attributes);
|
||||
attributes = rawAttributes.Select(x => (CredentialAttribute)x).ToList();
|
||||
}
|
||||
|
||||
string userName = v.UserName;
|
||||
if (v.Type == CredentialType.DomainCertificate || v.Type == CredentialType.GenericCertificate)
|
||||
userName = Credential.UnmarshalCertificateCredential(userName);
|
||||
|
||||
return new Credential
|
||||
{
|
||||
Type = v.Type,
|
||||
TargetName = v.TargetName,
|
||||
Comment = v.Comment,
|
||||
LastWritten = (DateTimeOffset)v.LastWritten,
|
||||
Secret = secret,
|
||||
Persist = v.Persist,
|
||||
Attributes = attributes,
|
||||
TargetAlias = v.TargetAlias,
|
||||
UserName = userName,
|
||||
Loaded = true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct CREDENTIAL_ATTRIBUTE
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string Keyword;
|
||||
public UInt32 Flags; // Set to 0 and is reserved
|
||||
public UInt32 ValueSize;
|
||||
public IntPtr Value;
|
||||
|
||||
public static explicit operator CredentialAttribute(CREDENTIAL_ATTRIBUTE v)
|
||||
{
|
||||
byte[] value = new byte[v.ValueSize];
|
||||
Marshal.Copy(v.Value, value, 0, (int)v.ValueSize);
|
||||
|
||||
return new CredentialAttribute
|
||||
{
|
||||
Keyword = v.Keyword,
|
||||
Flags = v.Flags,
|
||||
Value = value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct FILETIME
|
||||
{
|
||||
internal UInt32 dwLowDateTime;
|
||||
internal UInt32 dwHighDateTime;
|
||||
|
||||
public static implicit operator long(FILETIME v) { return ((long)v.dwHighDateTime << 32) + v.dwLowDateTime; }
|
||||
public static explicit operator DateTimeOffset(FILETIME v) { return DateTimeOffset.FromFileTime(v); }
|
||||
public static explicit operator FILETIME(DateTimeOffset v)
|
||||
{
|
||||
return new FILETIME()
|
||||
{
|
||||
dwLowDateTime = (UInt32)v.ToFileTime(),
|
||||
dwHighDateTime = ((UInt32)v.ToFileTime() >> 32),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum CredentialCreateFlags : uint
|
||||
{
|
||||
PreserveCredentialBlob = 1,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum CredentialFlags
|
||||
{
|
||||
None = 0,
|
||||
PromptNow = 2,
|
||||
UsernameTarget = 4,
|
||||
}
|
||||
|
||||
public enum CredMarshalType : uint
|
||||
{
|
||||
CertCredential = 1,
|
||||
UsernameTargetCredential,
|
||||
BinaryBlobCredential,
|
||||
UsernameForPackedCredential,
|
||||
BinaryBlobForSystem,
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool CredDeleteW(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string TargetName,
|
||||
CredentialType Type,
|
||||
UInt32 Flags);
|
||||
|
||||
[DllImport("advapi32.dll")]
|
||||
public static extern void CredFree(
|
||||
IntPtr Buffer);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool CredMarshalCredentialW(
|
||||
NativeHelpers.CredMarshalType CredType,
|
||||
SafeMemoryBuffer Credential,
|
||||
out SafeCredentialBuffer MarshaledCredential);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool CredReadW(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string TargetName,
|
||||
CredentialType Type,
|
||||
UInt32 Flags,
|
||||
out SafeCredentialBuffer Credential);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool CredUnmarshalCredentialW(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string MarshaledCredential,
|
||||
out NativeHelpers.CredMarshalType CredType,
|
||||
out SafeCredentialBuffer Credential);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool CredWriteW(
|
||||
NativeHelpers.CREDENTIAL Credential,
|
||||
NativeHelpers.CredentialCreateFlags Flags);
|
||||
}
|
||||
|
||||
internal class SafeCredentialBuffer : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
public SafeCredentialBuffer() : base(true) { }
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
NativeMethods.CredFree(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
public SafeMemoryBuffer() : base(true) { }
|
||||
public SafeMemoryBuffer(int cb) : base(true)
|
||||
{
|
||||
base.SetHandle(Marshal.AllocHGlobal(cb));
|
||||
}
|
||||
public SafeMemoryBuffer(IntPtr handle) : base(true)
|
||||
{
|
||||
base.SetHandle(handle);
|
||||
}
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
Marshal.FreeHGlobal(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class Win32Exception : System.ComponentModel.Win32Exception
|
||||
{
|
||||
private string _exception_msg;
|
||||
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
|
||||
public Win32Exception(int errorCode, string message) : base(errorCode)
|
||||
{
|
||||
_exception_msg = String.Format("{0} - {1} (Win32 Error Code {2}: 0x{3})", message, base.Message, errorCode, errorCode.ToString("X8"));
|
||||
}
|
||||
public override string Message { get { return _exception_msg; } }
|
||||
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
|
||||
}
|
||||
|
||||
public enum CredentialPersist
|
||||
{
|
||||
Session = 1,
|
||||
LocalMachine = 2,
|
||||
Enterprise = 3,
|
||||
}
|
||||
|
||||
public enum CredentialType
|
||||
{
|
||||
Generic = 1,
|
||||
DomainPassword = 2,
|
||||
DomainCertificate = 3,
|
||||
DomainVisiblePassword = 4,
|
||||
GenericCertificate = 5,
|
||||
DomainExtended = 6,
|
||||
Maximum = 7,
|
||||
MaximumEx = 1007,
|
||||
}
|
||||
|
||||
public class CredentialAttribute
|
||||
{
|
||||
public string Keyword;
|
||||
public UInt32 Flags;
|
||||
public byte[] Value;
|
||||
}
|
||||
|
||||
public class Credential
|
||||
{
|
||||
public CredentialType Type;
|
||||
public string TargetName;
|
||||
public string Comment;
|
||||
public DateTimeOffset LastWritten;
|
||||
public byte[] Secret;
|
||||
public CredentialPersist Persist;
|
||||
public List<CredentialAttribute> Attributes = new List<CredentialAttribute>();
|
||||
public string TargetAlias;
|
||||
public string UserName;
|
||||
|
||||
// Used to track whether the credential has been loaded into the store or not
|
||||
public bool Loaded { get; internal set; }
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
if (!Loaded)
|
||||
return;
|
||||
|
||||
if (!NativeMethods.CredDeleteW(TargetName, Type, 0))
|
||||
throw new Win32Exception(String.Format("CredDeleteW({0}) failed", TargetName));
|
||||
Loaded = false;
|
||||
}
|
||||
|
||||
public void Write(bool preserveExisting)
|
||||
{
|
||||
string userName = UserName;
|
||||
// Convert the certificate thumbprint to the string expected
|
||||
if (Type == CredentialType.DomainCertificate || Type == CredentialType.GenericCertificate)
|
||||
userName = Credential.MarshalCertificateCredential(userName);
|
||||
|
||||
NativeHelpers.CREDENTIAL credential = new NativeHelpers.CREDENTIAL
|
||||
{
|
||||
Flags = NativeHelpers.CredentialFlags.None,
|
||||
Type = Type,
|
||||
TargetName = TargetName,
|
||||
Comment = Comment,
|
||||
LastWritten = new NativeHelpers.FILETIME(),
|
||||
CredentialBlobSize = (UInt32)(Secret == null ? 0 : Secret.Length),
|
||||
CredentialBlob = IntPtr.Zero, // Must be allocated and freed outside of this to ensure no memory leaks
|
||||
Persist = Persist,
|
||||
AttributeCount = (UInt32)(Attributes.Count),
|
||||
Attributes = IntPtr.Zero, // Attributes must be allocated and freed outside of this to ensure no memory leaks
|
||||
TargetAlias = TargetAlias,
|
||||
UserName = userName,
|
||||
};
|
||||
|
||||
using (SafeMemoryBuffer credentialBlob = new SafeMemoryBuffer((int)credential.CredentialBlobSize))
|
||||
{
|
||||
if (Secret != null)
|
||||
Marshal.Copy(Secret, 0, credentialBlob.DangerousGetHandle(), Secret.Length);
|
||||
credential.CredentialBlob = credentialBlob.DangerousGetHandle();
|
||||
|
||||
// Store the CREDENTIAL_ATTRIBUTE value in a safe memory buffer and make sure we dispose in all cases
|
||||
List<SafeMemoryBuffer> attributeBuffers = new List<SafeMemoryBuffer>();
|
||||
try
|
||||
{
|
||||
int attributeLength = Attributes.Sum(a => Marshal.SizeOf(typeof(NativeHelpers.CREDENTIAL_ATTRIBUTE)));
|
||||
byte[] attributeBytes = new byte[attributeLength];
|
||||
int offset = 0;
|
||||
foreach (CredentialAttribute attribute in Attributes)
|
||||
{
|
||||
SafeMemoryBuffer attributeBuffer = new SafeMemoryBuffer(attribute.Value.Length);
|
||||
attributeBuffers.Add(attributeBuffer);
|
||||
if (attribute.Value != null)
|
||||
Marshal.Copy(attribute.Value, 0, attributeBuffer.DangerousGetHandle(), attribute.Value.Length);
|
||||
|
||||
NativeHelpers.CREDENTIAL_ATTRIBUTE credentialAttribute = new NativeHelpers.CREDENTIAL_ATTRIBUTE
|
||||
{
|
||||
Keyword = attribute.Keyword,
|
||||
Flags = attribute.Flags,
|
||||
ValueSize = (UInt32)(attribute.Value == null ? 0 : attribute.Value.Length),
|
||||
Value = attributeBuffer.DangerousGetHandle(),
|
||||
};
|
||||
int attributeStructLength = Marshal.SizeOf(typeof(NativeHelpers.CREDENTIAL_ATTRIBUTE));
|
||||
|
||||
byte[] attrBytes = new byte[attributeStructLength];
|
||||
using (SafeMemoryBuffer tempBuffer = new SafeMemoryBuffer(attributeStructLength))
|
||||
{
|
||||
Marshal.StructureToPtr(credentialAttribute, tempBuffer.DangerousGetHandle(), false);
|
||||
Marshal.Copy(tempBuffer.DangerousGetHandle(), attrBytes, 0, attributeStructLength);
|
||||
}
|
||||
Buffer.BlockCopy(attrBytes, 0, attributeBytes, offset, attributeStructLength);
|
||||
offset += attributeStructLength;
|
||||
}
|
||||
|
||||
using (SafeMemoryBuffer attributes = new SafeMemoryBuffer(attributeBytes.Length))
|
||||
{
|
||||
if (attributeBytes.Length != 0)
|
||||
{
|
||||
Marshal.Copy(attributeBytes, 0, attributes.DangerousGetHandle(), attributeBytes.Length);
|
||||
credential.Attributes = attributes.DangerousGetHandle();
|
||||
}
|
||||
|
||||
NativeHelpers.CredentialCreateFlags createFlags = 0;
|
||||
if (preserveExisting)
|
||||
createFlags |= NativeHelpers.CredentialCreateFlags.PreserveCredentialBlob;
|
||||
|
||||
if (!NativeMethods.CredWriteW(credential, createFlags))
|
||||
throw new Win32Exception(String.Format("CredWriteW({0}) failed", TargetName));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (SafeMemoryBuffer attributeBuffer in attributeBuffers)
|
||||
attributeBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
Loaded = true;
|
||||
}
|
||||
|
||||
public static Credential GetCredential(string target, CredentialType type)
|
||||
{
|
||||
SafeCredentialBuffer buffer;
|
||||
if (!NativeMethods.CredReadW(target, type, 0, out buffer))
|
||||
{
|
||||
int lastErr = Marshal.GetLastWin32Error();
|
||||
|
||||
// Not running with Become so cannot manage the user's credentials
|
||||
if (lastErr == 0x00000520) // ERROR_NO_SUCH_LOGON_SESSION
|
||||
throw new InvalidOperationException("Failed to access the user's credential store, run the module with become");
|
||||
else if (lastErr == 0x00000490) // ERROR_NOT_FOUND
|
||||
return null;
|
||||
throw new Win32Exception(lastErr, "CredEnumerateW() failed");
|
||||
}
|
||||
|
||||
using (buffer)
|
||||
{
|
||||
NativeHelpers.CREDENTIAL credential = (NativeHelpers.CREDENTIAL)Marshal.PtrToStructure(
|
||||
buffer.DangerousGetHandle(), typeof(NativeHelpers.CREDENTIAL));
|
||||
return (Credential)credential;
|
||||
}
|
||||
}
|
||||
|
||||
public static string MarshalCertificateCredential(string thumbprint)
|
||||
{
|
||||
// CredWriteW requires the UserName field to be the value of CredMarshalCredentialW() when writting a
|
||||
// certificate auth. This converts the UserName property to the format required.
|
||||
|
||||
// While CERT_CREDENTIAL_INFO is the correct structure, we manually marshal the data in order to
|
||||
// support different cert hash lengths in the future.
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_cert_credential_info
|
||||
int hexLength = thumbprint.Length;
|
||||
byte[] credInfo = new byte[sizeof(UInt32) + (hexLength / 2)];
|
||||
|
||||
// First field is cbSize which is a UInt32 value denoting the size of the total structure
|
||||
Array.Copy(BitConverter.GetBytes((UInt32)credInfo.Length), credInfo, sizeof(UInt32));
|
||||
|
||||
// Now copy the byte representation of the thumbprint to the rest of the struct bytes
|
||||
for (int i = 0; i < hexLength; i += 2)
|
||||
credInfo[sizeof(UInt32) + (i / 2)] = Convert.ToByte(thumbprint.Substring(i, 2), 16);
|
||||
|
||||
IntPtr pCredInfo = Marshal.AllocHGlobal(credInfo.Length);
|
||||
Marshal.Copy(credInfo, 0, pCredInfo, credInfo.Length);
|
||||
SafeMemoryBuffer pCredential = new SafeMemoryBuffer(pCredInfo);
|
||||
|
||||
NativeHelpers.CredMarshalType marshalType = NativeHelpers.CredMarshalType.CertCredential;
|
||||
using (pCredential)
|
||||
{
|
||||
SafeCredentialBuffer marshaledCredential;
|
||||
if (!NativeMethods.CredMarshalCredentialW(marshalType, pCredential, out marshaledCredential))
|
||||
throw new Win32Exception("CredMarshalCredentialW() failed");
|
||||
using (marshaledCredential)
|
||||
return Marshal.PtrToStringUni(marshaledCredential.DangerousGetHandle());
|
||||
}
|
||||
}
|
||||
|
||||
public static string UnmarshalCertificateCredential(string value)
|
||||
{
|
||||
NativeHelpers.CredMarshalType credType;
|
||||
SafeCredentialBuffer pCredInfo;
|
||||
if (!NativeMethods.CredUnmarshalCredentialW(value, out credType, out pCredInfo))
|
||||
throw new Win32Exception("CredUnmarshalCredentialW() failed");
|
||||
|
||||
using (pCredInfo)
|
||||
{
|
||||
if (credType != NativeHelpers.CredMarshalType.CertCredential)
|
||||
throw new InvalidOperationException(String.Format("Expected unmarshalled cred type of CertCredential, received {0}", credType));
|
||||
|
||||
byte[] structSizeBytes = new byte[sizeof(UInt32)];
|
||||
Marshal.Copy(pCredInfo.DangerousGetHandle(), structSizeBytes, 0, sizeof(UInt32));
|
||||
UInt32 structSize = BitConverter.ToUInt32(structSizeBytes, 0);
|
||||
|
||||
byte[] certInfoBytes = new byte[structSize];
|
||||
Marshal.Copy(pCredInfo.DangerousGetHandle(), certInfoBytes, 0, certInfoBytes.Length);
|
||||
|
||||
StringBuilder hex = new StringBuilder((certInfoBytes.Length - sizeof(UInt32)) * 2);
|
||||
for (int i = 4; i < certInfoBytes.Length; i++)
|
||||
hex.AppendFormat("{0:x2}", certInfoBytes[i]);
|
||||
|
||||
return hex.ToString().ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void PtrToStructureArray<T>(T[] array, IntPtr ptr)
|
||||
{
|
||||
IntPtr ptrOffset = ptr;
|
||||
for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T))))
|
||||
array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
Function ConvertTo-CredentialAttribute {
|
||||
param($Attributes)
|
||||
|
||||
$converted_attributes = [System.Collections.Generic.List`1[Ansible.CredentialManager.CredentialAttribute]]@()
|
||||
foreach ($attribute in $Attributes) {
|
||||
$new_attribute = New-Object -TypeName Ansible.CredentialManager.CredentialAttribute
|
||||
$new_attribute.Keyword = $attribute.name
|
||||
|
||||
if ($null -ne $attribute.data) {
|
||||
if ($attribute.data_format -eq "base64") {
|
||||
$new_attribute.Value = [System.Convert]::FromBase64String($attribute.data)
|
||||
} else {
|
||||
$new_attribute.Value = [System.Text.Encoding]::UTF8.GetBytes($attribute.data)
|
||||
}
|
||||
}
|
||||
$converted_attributes.Add($new_attribute) > $null
|
||||
}
|
||||
|
||||
return ,$converted_attributes
|
||||
}
|
||||
|
||||
Function Get-DiffInfo {
|
||||
param($AnsibleCredential)
|
||||
|
||||
$diff = @{
|
||||
alias = $AnsibleCredential.TargetAlias
|
||||
attributes = [System.Collections.ArrayList]@()
|
||||
comment = $AnsibleCredential.Comment
|
||||
name = $AnsibleCredential.TargetName
|
||||
persistence = $AnsibleCredential.Persist.ToString()
|
||||
type = $AnsibleCredential.Type.ToString()
|
||||
username = $AnsibleCredential.UserName
|
||||
}
|
||||
|
||||
foreach ($attribute in $AnsibleCredential.Attributes) {
|
||||
$attribute_info = @{
|
||||
name = $attribute.Keyword
|
||||
data = $null
|
||||
}
|
||||
if ($null -ne $attribute.Value) {
|
||||
$attribute_info.data = [System.Convert]::ToBase64String($attribute.Value)
|
||||
}
|
||||
$diff.attributes.Add($attribute_info) > $null
|
||||
}
|
||||
|
||||
return ,$diff
|
||||
}
|
||||
|
||||
# If the username is a certificate thumbprint, verify it's a valid cert in the CurrentUser/Personal store
|
||||
if ($null -ne $username -and $type -in @("domain_certificate", "generic_certificate")) {
|
||||
# Ensure the thumbprint is upper case with no spaces or hyphens
|
||||
$username = $username.ToUpperInvariant().Replace(" ", "").Replace("-", "")
|
||||
|
||||
$certificate = Get-Item -Path Cert:\CurrentUser\My\$username -ErrorAction SilentlyContinue
|
||||
if ($null -eq $certificate) {
|
||||
$module.FailJson("Failed to find certificate with the thumbprint $username in the CurrentUser\My store")
|
||||
}
|
||||
}
|
||||
|
||||
# Convert the input secret to a byte array
|
||||
if ($null -ne $secret) {
|
||||
if ($secret_format -eq "base64") {
|
||||
$secret = [System.Convert]::FromBase64String($secret)
|
||||
} else {
|
||||
$secret = [System.Text.Encoding]::Unicode.GetBytes($secret)
|
||||
}
|
||||
}
|
||||
|
||||
$persistence = switch ($persistence) {
|
||||
"local" { [Ansible.CredentialManager.CredentialPersist]::LocalMachine }
|
||||
"enterprise" { [Ansible.CredentialManager.CredentialPersist]::Enterprise }
|
||||
}
|
||||
|
||||
$type = switch ($type) {
|
||||
"domain_password" { [Ansible.CredentialManager.CredentialType]::DomainPassword }
|
||||
"domain_certificate" { [Ansible.CredentialManager.CredentialType]::DomainCertificate }
|
||||
"generic_password" { [Ansible.CredentialManager.CredentialType]::Generic }
|
||||
"generic_certificate" { [Ansible.CredentialManager.CredentialType]::GenericCertificate }
|
||||
}
|
||||
|
||||
$existing_credential = [Ansible.CredentialManager.Credential]::GetCredential($name, $type)
|
||||
if ($null -ne $existing_credential) {
|
||||
$module.Diff.before = Get-DiffInfo -AnsibleCredential $existing_credential
|
||||
}
|
||||
|
||||
if ($state -eq "absent") {
|
||||
if ($null -ne $existing_credential) {
|
||||
if (-not $module.CheckMode) {
|
||||
$existing_credential.Delete()
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
} else {
|
||||
if ($null -eq $existing_credential) {
|
||||
$new_credential = New-Object -TypeName Ansible.CredentialManager.Credential
|
||||
$new_credential.Type = $type
|
||||
$new_credential.TargetName = $name
|
||||
$new_credential.Comment = if ($comment) { $comment } else { [NullString]::Value }
|
||||
$new_credential.Secret = $secret
|
||||
$new_credential.Persist = $persistence
|
||||
$new_credential.TargetAlias = if ($alias) { $alias } else { [NullString]::Value }
|
||||
$new_credential.UserName = $username
|
||||
|
||||
if ($null -ne $attributes) {
|
||||
$new_credential.Attributes = ConvertTo-CredentialAttribute -Attributes $attributes
|
||||
}
|
||||
|
||||
if (-not $module.CheckMode) {
|
||||
$new_credential.Write($false)
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
} else {
|
||||
$changed = $false
|
||||
$preserve_blob = $false
|
||||
|
||||
# make sure we do case comparison for the comment
|
||||
if ($existing_credential.Comment -cne $comment) {
|
||||
$existing_credential.Comment = $comment
|
||||
$changed = $true
|
||||
}
|
||||
|
||||
if ($existing_credential.Persist -ne $persistence) {
|
||||
$existing_credential.Persist = $persistence
|
||||
$changed = $true
|
||||
}
|
||||
|
||||
if ($existing_credential.TargetAlias -ne $alias) {
|
||||
$existing_credential.TargetAlias = $alias
|
||||
$changed = $true
|
||||
}
|
||||
|
||||
if ($existing_credential.UserName -ne $username) {
|
||||
$existing_credential.UserName = $username
|
||||
$changed = $true
|
||||
}
|
||||
|
||||
if ($null -ne $attributes) {
|
||||
$attribute_changed = $false
|
||||
|
||||
$new_attributes = ConvertTo-CredentialAttribute -Attributes $attributes
|
||||
if ($new_attributes.Count -ne $existing_credential.Attributes.Count) {
|
||||
$attribute_changed = $true
|
||||
} else {
|
||||
for ($i = 0; $i -lt $new_attributes.Count; $i++) {
|
||||
$new_keyword = $new_attributes[$i].Keyword
|
||||
$new_value = $new_attributes[$i].Value
|
||||
if ($null -eq $new_value) {
|
||||
$new_value = ""
|
||||
} else {
|
||||
$new_value = [System.Convert]::ToBase64String($new_value)
|
||||
}
|
||||
|
||||
$existing_keyword = $existing_credential.Attributes[$i].Keyword
|
||||
$existing_value = $existing_credential.Attributes[$i].Value
|
||||
if ($null -eq $existing_value) {
|
||||
$existing_value = ""
|
||||
} else {
|
||||
$existing_value = [System.Convert]::ToBase64String($existing_value)
|
||||
}
|
||||
|
||||
if (($new_keyword -cne $existing_keyword) -or ($new_value -ne $existing_value)) {
|
||||
$attribute_changed = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($attribute_changed) {
|
||||
$existing_credential.Attributes = $new_attributes
|
||||
$changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -eq $secret) {
|
||||
# If we haven't explicitly set a secret, tell Windows to preserve the existing blob
|
||||
$preserve_blob = $true
|
||||
$existing_credential.Secret = $null
|
||||
} elseif ($update_secret -eq "always") {
|
||||
# We should only set the password if we can't read the existing one or it doesn't match our secret
|
||||
if ($existing_credential.Secret.Length -eq 0) {
|
||||
# We cannot read the secret so don't know if its the configured secret
|
||||
$existing_credential.Secret = $secret
|
||||
$changed = $true
|
||||
} else {
|
||||
# We can read the secret so compare with our input
|
||||
$input_secret_b64 = [System.Convert]::ToBase64String($secret)
|
||||
$actual_secret_b64 = [System.Convert]::ToBase64String($existing_credential.Secret)
|
||||
if ($input_secret_b64 -ne $actual_secret_b64) {
|
||||
$existing_credential.Secret = $secret
|
||||
$changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($changed -and -not $module.CheckMode) {
|
||||
$existing_credential.Write($preserve_blob)
|
||||
}
|
||||
$module.Result.changed = $changed
|
||||
}
|
||||
|
||||
if ($module.CheckMode) {
|
||||
# We cannot reliably get the credential in check mode, set it based on the input
|
||||
$module.Diff.after = @{
|
||||
alias = $alias
|
||||
attributes = $attributes
|
||||
comment = $comment
|
||||
name = $name
|
||||
persistence = $persistence.ToString()
|
||||
type = $type.ToString()
|
||||
username = $username
|
||||
}
|
||||
} else {
|
||||
# Get a new copy of the credential and use that to set the after diff
|
||||
$new_credential = [Ansible.CredentialManager.Credential]::GetCredential($name, $type)
|
||||
$module.Diff.after = Get-DiffInfo -AnsibleCredential $new_credential
|
||||
}
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
@ -1,209 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_credential
|
||||
version_added: '2.8'
|
||||
short_description: Manages Windows Credentials in the Credential Manager
|
||||
description:
|
||||
- Used to create and remove Windows Credentials in the Credential Manager.
|
||||
- This module can manage both standard username/password credentials as well as
|
||||
certificate credentials.
|
||||
options:
|
||||
alias:
|
||||
description:
|
||||
- Adds an alias for the credential.
|
||||
- Typically this is the NetBIOS name of a host if I(name) is set to the DNS
|
||||
name.
|
||||
type: str
|
||||
attributes:
|
||||
description:
|
||||
- A list of dicts that set application specific attributes for a
|
||||
credential.
|
||||
- When set, existing attributes will be compared to the list as a whole,
|
||||
any differences means all attributes will be replaced.
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The key for the attribute.
|
||||
- This is not a unique identifier as multiple attributes can have the
|
||||
same key.
|
||||
type: str
|
||||
required: true
|
||||
data:
|
||||
description:
|
||||
- The value for the attribute.
|
||||
type: str
|
||||
data_format:
|
||||
description:
|
||||
- Controls the input type for I(data).
|
||||
- If C(text), I(data) is a text string that is UTF-16LE encoded to
|
||||
bytes.
|
||||
- If C(base64), I(data) is a base64 string that is base64 decoded to
|
||||
bytes.
|
||||
type: str
|
||||
choices: [ base64, text ]
|
||||
default: text
|
||||
comment:
|
||||
description:
|
||||
- A user defined comment for the credential.
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- The target that identifies the server or servers that the credential is
|
||||
to be used for.
|
||||
- If the value can be a NetBIOS name, DNS server name, DNS host name suffix
|
||||
with a wildcard character (C(*)), a NetBIOS of DNS domain name that
|
||||
contains a wildcard character sequence, or an asterisk.
|
||||
- See C(TargetName) in U(https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentiala)
|
||||
for more details on what this value can be.
|
||||
- This is used with I(type) to produce a unique credential.
|
||||
type: str
|
||||
required: true
|
||||
persistence:
|
||||
description:
|
||||
- Defines the persistence of the credential.
|
||||
- If C(local), the credential will persist for all logons of the same user
|
||||
on the same host.
|
||||
- C(enterprise) is the same as C(local) but the credential is visible to
|
||||
the same domain user when running on other hosts and not just localhost.
|
||||
type: str
|
||||
choices: [ enterprise, local ]
|
||||
default: local
|
||||
secret:
|
||||
description:
|
||||
- The secret for the credential.
|
||||
- When omitted, then no secret is used for the credential if a new
|
||||
credentials is created.
|
||||
- When I(type) is a password type, this is the password for I(username).
|
||||
- When I(type) is a certificate type, this is the pin for the certificate.
|
||||
type: str
|
||||
secret_format:
|
||||
description:
|
||||
- Controls the input type for I(secret).
|
||||
- If C(text), I(secret) is a text string that is UTF-16LE encoded to bytes.
|
||||
- If C(base64), I(secret) is a base64 string that is base64 decoded to
|
||||
bytes.
|
||||
type: str
|
||||
choices: [ base64, text ]
|
||||
default: text
|
||||
state:
|
||||
description:
|
||||
- When C(absent), the credential specified by I(name) and I(type) is
|
||||
removed.
|
||||
- When C(present), the credential specified by I(name) and I(type) is
|
||||
removed.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
type:
|
||||
description:
|
||||
- The type of credential to store.
|
||||
- This is used with I(name) to produce a unique credential.
|
||||
- When the type is a C(domain) type, the credential is used by Microsoft
|
||||
authentication packages like Negotiate.
|
||||
- When the type is a C(generic) type, the credential is not used by any
|
||||
particular authentication package.
|
||||
- It is recommended to use a C(domain) type as only authentication
|
||||
providers can access the secret.
|
||||
type: str
|
||||
required: true
|
||||
choices: [ domain_certificate, domain_password, generic_certificate, generic_password ]
|
||||
update_secret:
|
||||
description:
|
||||
- When C(always), the secret will always be updated if they differ.
|
||||
- When C(on_create), the secret will only be checked/updated when it is
|
||||
first created.
|
||||
- If the secret cannot be retrieved and this is set to C(always), the
|
||||
module will always result in a change.
|
||||
type: str
|
||||
choices: [ always, on_create ]
|
||||
default: always
|
||||
username:
|
||||
description:
|
||||
- When I(type) is a password type, then this is the username to store for
|
||||
the credential.
|
||||
- When I(type) is a credential type, then this is the thumbprint as a hex
|
||||
string of the certificate to use.
|
||||
- When C(type=domain_password), this should be in the form of a Netlogon
|
||||
(DOMAIN\Username) or a UPN (username@DOMAIN).
|
||||
- If using a certificate thumbprint, the certificate must exist in the
|
||||
C(CurrentUser\My) certificate store for the executing user.
|
||||
type: str
|
||||
notes:
|
||||
- This module requires to be run with C(become) so it can access the
|
||||
user's credential store.
|
||||
- There can only be one credential per host and type. if a second credential is
|
||||
defined that uses the same host and type, then the original credential is
|
||||
overwritten.
|
||||
seealso:
|
||||
- module: win_user_right
|
||||
- module: win_whoami
|
||||
author:
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create a local only credential
|
||||
win_credential:
|
||||
name: server.domain.com
|
||||
type: domain_password
|
||||
username: DOMAIN\username
|
||||
secret: Password01
|
||||
state: present
|
||||
|
||||
- name: Remove a credential
|
||||
win_credential:
|
||||
name: server.domain.com
|
||||
type: domain_password
|
||||
state: absent
|
||||
|
||||
- name: Create a credential with full values
|
||||
win_credential:
|
||||
name: server.domain.com
|
||||
type: domain_password
|
||||
alias: server
|
||||
username: username@DOMAIN.COM
|
||||
secret: Password01
|
||||
comment: Credential for server.domain.com
|
||||
persistence: enterprise
|
||||
attributes:
|
||||
- name: Source
|
||||
data: Ansible
|
||||
- name: Unique Identifier
|
||||
data: Y3VzdG9tIGF0dHJpYnV0ZQ==
|
||||
data_format: base64
|
||||
|
||||
- name: Create a certificate credential
|
||||
win_credential:
|
||||
name: '*.domain.com'
|
||||
type: domain_certificate
|
||||
username: 0074CC4F200D27DC3877C24A92BA8EA21E6C7AF4
|
||||
state: present
|
||||
|
||||
- name: Create a generic credential
|
||||
win_credential:
|
||||
name: smbhost
|
||||
type: generic_password
|
||||
username: smbuser
|
||||
secret: smbuser
|
||||
state: present
|
||||
|
||||
- name: Remove a generic credential
|
||||
win_credential:
|
||||
name: smbhost
|
||||
type: generic_password
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
#
|
||||
'''
|
@ -1,129 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: 2019, rnsc(@rnsc) <github@rnsc.be>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#AnsibleRequires -OSVersion 6.3
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
drive_letter = @{ type = "str"; required = $true }
|
||||
state = @{ type = "str"; choices = "absent", "present"; default = "present"; }
|
||||
settings = @{
|
||||
type = "dict"
|
||||
required = $false
|
||||
options = @{
|
||||
minimum_file_size = @{ type = "int"; default = 32768 }
|
||||
minimum_file_age_days = @{ type = "int"; default = 2 }
|
||||
no_compress = @{ type = "bool"; required = $false; default = $false }
|
||||
optimize_in_use_files = @{ type = "bool"; required = $false; default = $false }
|
||||
verify = @{ type = "bool"; required = $false; default = $false }
|
||||
}
|
||||
}
|
||||
}
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$drive_letter = $module.Params.drive_letter
|
||||
$state = $module.Params.state
|
||||
$settings = $module.Params.settings
|
||||
|
||||
$module.Result.changed = $false
|
||||
$module.Result.reboot_required = $false
|
||||
$module.Result.msg = ""
|
||||
|
||||
function Set-DataDeduplication($volume, $state, $settings, $dedup_job) {
|
||||
|
||||
$current_state = 'absent'
|
||||
|
||||
try {
|
||||
$dedup_info = Get-DedupVolume -Volume "$($volume.DriveLetter):"
|
||||
} catch {
|
||||
$dedup_info = $null
|
||||
}
|
||||
|
||||
if ($dedup_info.Enabled) {
|
||||
$current_state = 'present'
|
||||
}
|
||||
|
||||
if ( $state -ne $current_state ) {
|
||||
if( -not $module.CheckMode) {
|
||||
if($state -eq 'present') {
|
||||
# Enable-DedupVolume -Volume <String>
|
||||
Enable-DedupVolume -Volume "$($volume.DriveLetter):"
|
||||
} elseif ($state -eq 'absent') {
|
||||
Disable-DedupVolume -Volume "$($volume.DriveLetter):"
|
||||
}
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
|
||||
if ($state -eq 'present') {
|
||||
if ($null -ne $settings) {
|
||||
Set-DataDedupJobSettings -volume $volume -settings $settings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Set-DataDedupJobSettings ($volume, $settings) {
|
||||
|
||||
try {
|
||||
$dedup_info = Get-DedupVolume -Volume "$($volume.DriveLetter):"
|
||||
} catch {
|
||||
$dedup_info = $null
|
||||
}
|
||||
|
||||
ForEach ($key in $settings.keys) {
|
||||
|
||||
# See Microsoft documentation:
|
||||
# https://docs.microsoft.com/en-us/powershell/module/deduplication/set-dedupvolume?view=win10-ps
|
||||
|
||||
$update_key = $key
|
||||
$update_value = $settings.$($key)
|
||||
# Transform Ansible style options to Powershell params
|
||||
$update_key = $update_key -replace('_', '')
|
||||
|
||||
if ($update_key -eq "MinimumFileSize" -and $update_value -lt 32768) {
|
||||
$update_value = 32768
|
||||
}
|
||||
|
||||
$current_value = ($dedup_info | Select-Object -ExpandProperty $update_key)
|
||||
|
||||
if ($update_value -ne $current_value) {
|
||||
$command_param = @{
|
||||
$($update_key) = $update_value
|
||||
}
|
||||
|
||||
# Set-DedupVolume -Volume <String>`
|
||||
# -NoCompress <bool> `
|
||||
# -MinimumFileAgeDays <UInt32> `
|
||||
# -MinimumFileSize <UInt32> (minimum 32768)
|
||||
if( -not $module.CheckMode ) {
|
||||
Set-DedupVolume -Volume "$($volume.DriveLetter):" @command_param
|
||||
}
|
||||
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Install required feature
|
||||
$feature_name = "FS-Data-Deduplication"
|
||||
if( -not $module.CheckMode) {
|
||||
$feature = Install-WindowsFeature -Name $feature_name
|
||||
|
||||
if ($feature.RestartNeeded -eq 'Yes') {
|
||||
$module.Result.reboot_required = $true
|
||||
$module.FailJson("$feature_name was installed but requires Windows to be rebooted to work.")
|
||||
}
|
||||
}
|
||||
|
||||
$volume = Get-Volume -DriveLetter $drive_letter
|
||||
|
||||
Set-DataDeduplication -volume $volume -state $state -settings $settings -dedup_job $dedup_job
|
||||
|
||||
$module.ExitJson()
|
@ -1,87 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: 2019, rnsc(@rnsc) <github@rnsc.be>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_data_deduplication
|
||||
version_added: "2.10"
|
||||
short_description: Module to enable Data Deduplication on a volume.
|
||||
description:
|
||||
- This module can be used to enable Data Deduplication on a Windows volume.
|
||||
- The module will install the FS-Data-Deduplication feature (a reboot will be necessary).
|
||||
options:
|
||||
drive_letter:
|
||||
description:
|
||||
- Windows drive letter on which to enable data deduplication.
|
||||
required: yes
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Wether to enable or disable data deduplication on the selected volume.
|
||||
default: present
|
||||
type: str
|
||||
choices: [ present, absent ]
|
||||
settings:
|
||||
description:
|
||||
- Dictionary of settings to pass to the Set-DedupVolume powershell command.
|
||||
type: dict
|
||||
suboptions:
|
||||
minimum_file_size:
|
||||
description:
|
||||
- Minimum file size you want to target for deduplication.
|
||||
- It will default to 32768 if not defined or if the value is less than 32768.
|
||||
type: int
|
||||
default: 32768
|
||||
minimum_file_age_days:
|
||||
description:
|
||||
- Minimum file age you want to target for deduplication.
|
||||
type: int
|
||||
default: 2
|
||||
no_compress:
|
||||
description:
|
||||
- Wether you want to enabled filesystem compression or not.
|
||||
type: bool
|
||||
default: no
|
||||
optimize_in_use_files:
|
||||
description:
|
||||
- Indicates that the server attempts to optimize currently open files.
|
||||
type: bool
|
||||
default: no
|
||||
verify:
|
||||
description:
|
||||
- Indicates whether the deduplication engine performs a byte-for-byte verification for each duplicate chunk
|
||||
that optimization creates, rather than relying on a cryptographically strong hash.
|
||||
- This option is not recommend.
|
||||
- Setting this parameter to True can degrade optimization performance.
|
||||
type: bool
|
||||
default: no
|
||||
author:
|
||||
- rnsc (@rnsc)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Enable Data Deduplication on D
|
||||
win_data_deduplication:
|
||||
drive_letter: 'D'
|
||||
state: present
|
||||
|
||||
- name: Enable Data Deduplication on D
|
||||
win_data_deduplication:
|
||||
drive_letter: 'D'
|
||||
state: present
|
||||
settings:
|
||||
no_compress: true
|
||||
minimum_file_age_days: 1
|
||||
minimum_file_size: 0
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
#
|
||||
'''
|
@ -1,97 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#Requires -Module Ansible.ModuleUtils.ArgvParser
|
||||
#Requires -Module Ansible.ModuleUtils.CommandUtil
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
include_volumes = @{ type='list' }
|
||||
exclude_volumes = @{ type='list' }
|
||||
freespace_consolidation = @{ type='bool'; default=$false }
|
||||
priority = @{ type='str'; default='low'; choices=@( 'low', 'normal') }
|
||||
parallel = @{ type='bool'; default=$false }
|
||||
}
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$include_volumes = $module.Params.include_volumes
|
||||
$exclude_volumes = $module.Params.exclude_volumes
|
||||
$freespace_consolidation = $module.Params.freespace_consolidation
|
||||
$priority = $module.Params.priority
|
||||
$parallel = $module.Params.parallel
|
||||
|
||||
$module.Result.changed = $false
|
||||
|
||||
$executable = "defrag.exe"
|
||||
|
||||
if (-not (Get-Command -Name $executable -ErrorAction SilentlyContinue)) {
|
||||
$module.FailJson("Command '$executable' not found in $env:PATH.")
|
||||
}
|
||||
|
||||
$arguments = @()
|
||||
|
||||
if ($include_volumes) {
|
||||
foreach ($volume in $include_volumes) {
|
||||
if ($volume.Length -eq 1) {
|
||||
$arguments += "$($volume):"
|
||||
} else {
|
||||
$arguments += $volume
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$arguments += "/C"
|
||||
}
|
||||
|
||||
if ($exclude_volumes) {
|
||||
$arguments += "/E"
|
||||
foreach ($volume in $exclude_volumes) {
|
||||
if ($volume.Length -eq 1) {
|
||||
$arguments += "$($volume):"
|
||||
} else {
|
||||
$arguments += $volume
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($module.CheckMode) {
|
||||
$arguments += "/A"
|
||||
} elseif ($freespace_consolidation) {
|
||||
$arguments += "/X"
|
||||
}
|
||||
|
||||
if ($priority -eq "normal") {
|
||||
$arguments += "/H"
|
||||
}
|
||||
|
||||
if ($parallel) {
|
||||
$arguments += "/M"
|
||||
}
|
||||
|
||||
$arguments += "/V"
|
||||
|
||||
$argument_string = Argv-ToString -arguments $arguments
|
||||
|
||||
$start_datetime = [DateTime]::UtcNow
|
||||
$module.Result.cmd = "$executable $argument_string"
|
||||
|
||||
$command_result = Run-Command -command "$executable $argument_string"
|
||||
|
||||
$end_datetime = [DateTime]::UtcNow
|
||||
|
||||
$module.Result.stdout = $command_result.stdout
|
||||
$module.Result.stderr = $command_result.stderr
|
||||
$module.Result.rc = $command_result.rc
|
||||
|
||||
$module.Result.start = $start_datetime.ToString("yyyy-MM-dd hh:mm:ss.ffffff")
|
||||
$module.Result.end = $end_datetime.ToString("yyyy-MM-dd hh:mm:ss.ffffff")
|
||||
$module.Result.delta = $($end_datetime - $start_datetime).ToString("h\:mm\:ss\.ffffff")
|
||||
|
||||
$module.Result.changed = $true
|
||||
|
||||
$module.ExitJson()
|
@ -1,101 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: 2017, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_defrag
|
||||
version_added: '2.4'
|
||||
short_description: Consolidate fragmented files on local volumes
|
||||
description:
|
||||
- Locates and consolidates fragmented files on local volumes to improve system performance.
|
||||
- 'More information regarding C(win_defrag) is available from: U(https://technet.microsoft.com/en-us/library/cc731650(v=ws.11).aspx)'
|
||||
requirements:
|
||||
- defrag.exe
|
||||
options:
|
||||
include_volumes:
|
||||
description:
|
||||
- A list of drive letters or mount point paths of the volumes to be defragmented.
|
||||
- If this parameter is omitted, all volumes (not excluded) will be fragmented.
|
||||
type: list
|
||||
exclude_volumes:
|
||||
description:
|
||||
- A list of drive letters or mount point paths to exclude from defragmentation.
|
||||
type: list
|
||||
freespace_consolidation:
|
||||
description:
|
||||
- Perform free space consolidation on the specified volumes.
|
||||
type: bool
|
||||
default: no
|
||||
priority:
|
||||
description:
|
||||
- Run the operation at low or normal priority.
|
||||
type: str
|
||||
choices: [ low, normal ]
|
||||
default: low
|
||||
parallel:
|
||||
description:
|
||||
- Run the operation on each volume in parallel in the background.
|
||||
type: bool
|
||||
default: no
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Defragment all local volumes (in parallel)
|
||||
win_defrag:
|
||||
parallel: yes
|
||||
|
||||
- name: 'Defragment all local volumes, except C: and D:'
|
||||
win_defrag:
|
||||
exclude_volumes: [ C, D ]
|
||||
|
||||
- name: 'Defragment volume D: with normal priority'
|
||||
win_defrag:
|
||||
include_volumes: D
|
||||
priority: normal
|
||||
|
||||
- name: Consolidate free space (useful when reducing volumes)
|
||||
win_defrag:
|
||||
freespace_consolidation: yes
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
cmd:
|
||||
description: The complete command line used by the module.
|
||||
returned: always
|
||||
type: str
|
||||
sample: defrag.exe /C /V
|
||||
rc:
|
||||
description: The return code for the command.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 0
|
||||
stdout:
|
||||
description: The standard output from the command.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Success.
|
||||
stderr:
|
||||
description: The error output from the command.
|
||||
returned: always
|
||||
type: str
|
||||
sample:
|
||||
msg:
|
||||
description: Possible error message on failure.
|
||||
returned: failed
|
||||
type: str
|
||||
sample: Command 'defrag.exe' not found in $env:PATH.
|
||||
changed:
|
||||
description: Whether or not any changes were made.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
'''
|
@ -1,251 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Marc Tschapek <marc.tschapek@itelligence.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#AnsibleRequires -OSVersion 6.2
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
Set-StrictMode -Version 2.0
|
||||
|
||||
# Functions
|
||||
function Test-Admin {
|
||||
$CurrentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
|
||||
$IsAdmin = $CurrentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
|
||||
|
||||
return $IsAdmin
|
||||
}
|
||||
|
||||
# Check admin rights
|
||||
if (-not (Test-Admin)) {
|
||||
Fail-Json -obj @{} -message "Module was not started with elevated rights"
|
||||
}
|
||||
|
||||
# Create a new result object
|
||||
$result = @{
|
||||
changed = $false
|
||||
ansible_facts = @{
|
||||
ansible_disks = @()
|
||||
}
|
||||
}
|
||||
|
||||
# Search disks
|
||||
try {
|
||||
$disks = Get-Disk
|
||||
} catch {
|
||||
Fail-Json -obj $result -message "Failed to search the disks on the target: $($_.Exception.Message)"
|
||||
}
|
||||
foreach ($disk in $disks) {
|
||||
$disk_info = @{}
|
||||
$pdisk = Get-PhysicalDisk -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.DeviceId -eq $disk.Number
|
||||
}
|
||||
if ($pdisk) {
|
||||
$disk_info["physical_disk"] += @{
|
||||
size = $pdisk.Size
|
||||
allocated_size = $pdisk.AllocatedSize
|
||||
device_id = $pdisk.DeviceId
|
||||
friendly_name = $pdisk.FriendlyName
|
||||
operational_status = $pdisk.OperationalStatus
|
||||
health_status = $pdisk.HealthStatus
|
||||
bus_type = $pdisk.BusType
|
||||
usage_type = $pdisk.Usage
|
||||
supported_usages = $pdisk.SupportedUsages
|
||||
spindle_speed = $pdisk.SpindleSpeed
|
||||
firmware_version = $pdisk.FirmwareVersion
|
||||
physical_location = $pdisk.PhysicalLocation
|
||||
manufacturer = $pdisk.Manufacturer
|
||||
model = $pdisk.Model
|
||||
can_pool = $pdisk.CanPool
|
||||
indication_enabled = $pdisk.IsIndicationEnabled
|
||||
partial = $pdisk.IsPartial
|
||||
serial_number = $pdisk.SerialNumber
|
||||
object_id = $pdisk.ObjectId
|
||||
unique_id = $pdisk.UniqueId
|
||||
}
|
||||
if ([single]"$([System.Environment]::OSVersion.Version.Major).$([System.Environment]::OSVersion.Version.Minor)" -ge 6.3) {
|
||||
$disk_info.physical_disk.media_type = $pdisk.MediaType
|
||||
}
|
||||
if (-not $pdisk.CanPool) {
|
||||
$disk_info.physical_disk.cannot_pool_reason = $pdisk.CannotPoolReason
|
||||
}
|
||||
$vdisk = Get-VirtualDisk -PhysicalDisk $pdisk -ErrorAction SilentlyContinue
|
||||
if ($vdisk) {
|
||||
$disk_info["virtual_disk"] += @{
|
||||
size = $vdisk.Size
|
||||
allocated_size = $vdisk.AllocatedSize
|
||||
footprint_on_pool = $vdisk.FootprintOnPool
|
||||
name = $vdisk.name
|
||||
friendly_name = $vdisk.FriendlyName
|
||||
operational_status = $vdisk.OperationalStatus
|
||||
health_status = $vdisk.HealthStatus
|
||||
provisioning_type = $vdisk.ProvisioningType
|
||||
allocation_unit_size = $vdisk.AllocationUnitSize
|
||||
media_type = $vdisk.MediaType
|
||||
parity_layout = $vdisk.ParityLayout
|
||||
access = $vdisk.Access
|
||||
detached_reason = $vdisk.DetachedReason
|
||||
write_cache_size = $vdisk.WriteCacheSize
|
||||
fault_domain_awareness = $vdisk.FaultDomainAwareness
|
||||
inter_leave = $vdisk.InterLeave
|
||||
deduplication_enabled = $vdisk.IsDeduplicationEnabled
|
||||
enclosure_aware = $vdisk.IsEnclosureAware
|
||||
manual_attach = $vdisk.IsManualAttach
|
||||
snapshot = $vdisk.IsSnapshot
|
||||
tiered = $vdisk.IsTiered
|
||||
physical_sector_size = $vdisk.PhysicalSectorSize
|
||||
logical_sector_size = $vdisk.LogicalSectorSize
|
||||
available_copies = $vdisk.NumberOfAvailableCopies
|
||||
columns = $vdisk.NumberOfColumns
|
||||
groups = $vdisk.NumberOfGroups
|
||||
physical_disk_redundancy = $vdisk.PhysicalDiskRedundancy
|
||||
read_cache_size = $vdisk.ReadCacheSize
|
||||
request_no_spof = $vdisk.RequestNoSinglePointOfFailure
|
||||
resiliency_setting_name = $vdisk.ResiliencySettingName
|
||||
object_id = $vdisk.ObjectId
|
||||
unique_id_format = $vdisk.UniqueIdFormat
|
||||
unique_id = $vdisk.UniqueId
|
||||
}
|
||||
}
|
||||
}
|
||||
$win32_disk_drive = Get-CimInstance -ClassName Win32_DiskDrive -ErrorAction SilentlyContinue | Where-Object {
|
||||
if ($_.SerialNumber) {
|
||||
$_.SerialNumber -eq $disk.SerialNumber
|
||||
} elseif ($disk.UniqueIdFormat -eq 'Vendor Specific') {
|
||||
$_.PNPDeviceID -eq $disk.UniqueId.split(':')[0]
|
||||
}
|
||||
}
|
||||
if ($win32_disk_drive) {
|
||||
$disk_info["win32_disk_drive"] += @{
|
||||
availability=$win32_disk_drive.Availability
|
||||
bytes_per_sector=$win32_disk_drive.BytesPerSector
|
||||
capabilities=$win32_disk_drive.Capabilities
|
||||
capability_descriptions=$win32_disk_drive.CapabilityDescriptions
|
||||
caption=$win32_disk_drive.Caption
|
||||
compression_method=$win32_disk_drive.CompressionMethod
|
||||
config_manager_error_code=$win32_disk_drive.ConfigManagerErrorCode
|
||||
config_manager_user_config=$win32_disk_drive.ConfigManagerUserConfig
|
||||
creation_class_name=$win32_disk_drive.CreationClassName
|
||||
default_block_size=$win32_disk_drive.DefaultBlockSize
|
||||
description=$win32_disk_drive.Description
|
||||
device_id=$win32_disk_drive.DeviceID
|
||||
error_cleared=$win32_disk_drive.ErrorCleared
|
||||
error_description=$win32_disk_drive.ErrorDescription
|
||||
error_methodology=$win32_disk_drive.ErrorMethodology
|
||||
firmware_revision=$win32_disk_drive.FirmwareRevision
|
||||
index=$win32_disk_drive.Index
|
||||
install_date=$win32_disk_drive.InstallDate
|
||||
interface_type=$win32_disk_drive.InterfaceType
|
||||
last_error_code=$win32_disk_drive.LastErrorCode
|
||||
manufacturer=$win32_disk_drive.Manufacturer
|
||||
max_block_size=$win32_disk_drive.MaxBlockSize
|
||||
max_media_size=$win32_disk_drive.MaxMediaSize
|
||||
media_loaded=$win32_disk_drive.MediaLoaded
|
||||
media_type=$win32_disk_drive.MediaType
|
||||
min_block_size=$win32_disk_drive.MinBlockSize
|
||||
model=$win32_disk_drive.Model
|
||||
name=$win32_disk_drive.Name
|
||||
needs_cleaning=$win32_disk_drive.NeedsCleaning
|
||||
number_of_media_supported=$win32_disk_drive.NumberOfMediaSupported
|
||||
partitions=$win32_disk_drive.Partitions
|
||||
pnp_device_id=$win32_disk_drive.PNPDeviceID
|
||||
power_management_capabilities=$win32_disk_drive.PowerManagementCapabilities
|
||||
power_management_supported=$win32_disk_drive.PowerManagementSupported
|
||||
scsi_bus=$win32_disk_drive.SCSIBus
|
||||
scsi_logical_unit=$win32_disk_drive.SCSILogicalUnit
|
||||
scsi_port=$win32_disk_drive.SCSIPort
|
||||
scsi_target_id=$win32_disk_drive.SCSITargetId
|
||||
sectors_per_track=$win32_disk_drive.SectorsPerTrack
|
||||
serial_number=$win32_disk_drive.SerialNumber
|
||||
signature=$win32_disk_drive.Signature
|
||||
size=$win32_disk_drive.Size
|
||||
status=$win32_disk_drive.status
|
||||
status_info=$win32_disk_drive.StatusInfo
|
||||
system_creation_class_name=$win32_disk_drive.SystemCreationClassName
|
||||
system_name=$win32_disk_drive.SystemName
|
||||
total_cylinders=$win32_disk_drive.TotalCylinders
|
||||
total_heads=$win32_disk_drive.TotalHeads
|
||||
total_sectors=$win32_disk_drive.TotalSectors
|
||||
total_tracks=$win32_disk_drive.TotalTracks
|
||||
tracks_per_cylinder=$win32_disk_drive.TracksPerCylinder
|
||||
}
|
||||
}
|
||||
$disk_info.number = $disk.Number
|
||||
$disk_info.size = $disk.Size
|
||||
$disk_info.bus_type = $disk.BusType
|
||||
$disk_info.friendly_name = $disk.FriendlyName
|
||||
$disk_info.partition_style = $disk.PartitionStyle
|
||||
$disk_info.partition_count = $disk.NumberOfPartitions
|
||||
$disk_info.operational_status = $disk.OperationalStatus
|
||||
$disk_info.sector_size = $disk.PhysicalSectorSize
|
||||
$disk_info.read_only = $disk.IsReadOnly
|
||||
$disk_info.bootable = $disk.IsBoot
|
||||
$disk_info.system_disk = $disk.IsSystem
|
||||
$disk_info.clustered = $disk.IsClustered
|
||||
$disk_info.manufacturer = $disk.Manufacturer
|
||||
$disk_info.model = $disk.Model
|
||||
$disk_info.firmware_version = $disk.FirmwareVersion
|
||||
$disk_info.location = $disk.Location
|
||||
$disk_info.serial_number = $disk.SerialNumber
|
||||
$disk_info.unique_id = $disk.UniqueId
|
||||
$disk_info.guid = $disk.Guid
|
||||
$disk_info.path = $disk.Path
|
||||
$parts = Get-Partition -DiskNumber $($disk.Number) -ErrorAction SilentlyContinue
|
||||
if ($parts) {
|
||||
$disk_info["partitions"] += @()
|
||||
foreach ($part in $parts) {
|
||||
$partition_info = @{
|
||||
number = $part.PartitionNumber
|
||||
size = $part.Size
|
||||
type = $part.Type
|
||||
drive_letter = $part.DriveLetter
|
||||
transition_state = $part.TransitionState
|
||||
offset = $part.Offset
|
||||
hidden = $part.IsHidden
|
||||
shadow_copy = $part.IsShadowCopy
|
||||
guid = $part.Guid
|
||||
access_paths = $part.AccessPaths
|
||||
}
|
||||
if ($disks.PartitionStyle -eq "GPT") {
|
||||
$partition_info.gpt_type = $part.GptType
|
||||
$partition_info.no_default_driveletter = $part.NoDefaultDriveLetter
|
||||
} elseif ($disks.PartitionStyle -eq "MBR") {
|
||||
$partition_info.mbr_type = $part.MbrType
|
||||
$partition_info.active = $part.IsActive
|
||||
}
|
||||
$vols = Get-Volume -Partition $part -ErrorAction SilentlyContinue
|
||||
if ($vols) {
|
||||
$partition_info["volumes"] += @()
|
||||
foreach ($vol in $vols) {
|
||||
$volume_info = @{
|
||||
size = $vol.Size
|
||||
size_remaining = $vol.SizeRemaining
|
||||
type = $vol.FileSystem
|
||||
label = $vol.FileSystemLabel
|
||||
health_status = $vol.HealthStatus
|
||||
drive_type = $vol.DriveType
|
||||
object_id = $vol.ObjectId
|
||||
path = $vol.Path
|
||||
}
|
||||
if ([System.Environment]::OSVersion.Version.Major -ge 10) {
|
||||
$volume_info.allocation_unit_size = $vol.AllocationUnitSize
|
||||
} else {
|
||||
$volPath = ($vol.Path.TrimStart("\\?\")).TrimEnd("\")
|
||||
$BlockSize = (Get-CimInstance -Query "SELECT BlockSize FROM Win32_Volume WHERE DeviceID like '%$volPath%'" -ErrorAction SilentlyContinue | Select-Object BlockSize).BlockSize
|
||||
$volume_info.allocation_unit_size = $BlockSize
|
||||
}
|
||||
$partition_info.volumes += $volume_info
|
||||
}
|
||||
}
|
||||
$disk_info.partitions += $partition_info
|
||||
}
|
||||
}
|
||||
$result.ansible_facts.ansible_disks += $disk_info
|
||||
}
|
||||
|
||||
# Sort by disk number property
|
||||
$result.ansible_facts.ansible_disks = @() + ($result.ansible_facts.ansible_disks | Sort-Object -Property {$_.Number})
|
||||
|
||||
# Return result
|
||||
Exit-Json -obj $result
|
@ -1,891 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Marc Tschapek <marc.tschapek@itelligence.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_disk_facts
|
||||
version_added: '2.5'
|
||||
short_description: Show the attached disks and disk information of the target host
|
||||
description:
|
||||
- With the module you can retrieve and output detailed information about the attached disks of the target and
|
||||
its volumes and partitions if existent.
|
||||
requirements:
|
||||
- Windows 8.1 / Windows 2012 (NT 6.2)
|
||||
notes:
|
||||
- In order to understand all the returned properties and values please visit the following site and open the respective MSFT class
|
||||
U(https://msdn.microsoft.com/en-us/library/windows/desktop/hh830612.aspx)
|
||||
author:
|
||||
- Marc Tschapek (@marqelme)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Get disk facts
|
||||
win_disk_facts:
|
||||
|
||||
- name: Output first disk size
|
||||
debug:
|
||||
var: ansible_facts.disks[0].size
|
||||
|
||||
- name: Convert first system disk into various formats
|
||||
debug:
|
||||
msg: '{{ disksize_gib }} vs {{ disksize_gib_human }}'
|
||||
vars:
|
||||
# Get first system disk
|
||||
disk: '{{ ansible_facts.disks|selectattr("system_disk")|first }}'
|
||||
|
||||
# Show disk size in Gibibytes
|
||||
disksize_gib_human: '{{ disk.size|filesizeformat(true) }}' # returns "223.6 GiB" (human readable)
|
||||
disksize_gib: '{{ (disk.size/1024|pow(3))|round|int }} GiB' # returns "224 GiB" (value in GiB)
|
||||
|
||||
# Show disk size in Gigabytes
|
||||
disksize_gb_human: '{{ disk.size|filesizeformat }}' # returns "240.1 GB" (human readable)
|
||||
disksize_gb: '{{ (disk.size/1000|pow(3))|round|int }} GB' # returns "240 GB" (value in GB)
|
||||
|
||||
- name: Output second disk serial number
|
||||
debug:
|
||||
var: ansible_facts.disks[1].serial_number
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
ansible_facts:
|
||||
description: Dictionary containing all the detailed information about the disks of the target.
|
||||
returned: always
|
||||
type: complex
|
||||
contains:
|
||||
ansible_disks:
|
||||
description: Detailed information about one particular disk.
|
||||
returned: if disks were found
|
||||
type: list
|
||||
contains:
|
||||
number:
|
||||
description: Disk number of the particular disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 0
|
||||
size:
|
||||
description: Size in bytes of the particular disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 227727638528
|
||||
bus_type:
|
||||
description: Bus type of the particular disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "SCSI"
|
||||
friendly_name:
|
||||
description: Friendly name of the particular disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Red Hat VirtIO SCSI Disk Device"
|
||||
partition_style:
|
||||
description: Partition style of the particular disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "MBR"
|
||||
partition_count:
|
||||
description: Number of partitions on the particular disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 4
|
||||
operational_status:
|
||||
description: Operational status of the particular disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Online"
|
||||
sector_size:
|
||||
description: Sector size in bytes of the particular disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 4096
|
||||
read_only:
|
||||
description: Read only status of the particular disk.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
bootable:
|
||||
description: Information whether the particular disk is a bootable disk.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
system_disk:
|
||||
description: Information whether the particular disk is a system disk.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
clustered:
|
||||
description: Information whether the particular disk is clustered (part of a failover cluster).
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
manufacturer:
|
||||
description: Manufacturer of the particular disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Red Hat"
|
||||
model:
|
||||
description: Model specification of the particular disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "VirtIO"
|
||||
firmware_version:
|
||||
description: Firmware version of the particular disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "0001"
|
||||
location:
|
||||
description: Location of the particular disk on the target.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "PCIROOT(0)#PCI(0400)#SCSI(P00T00L00)"
|
||||
serial_number:
|
||||
description: Serial number of the particular disk on the target.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "b62beac80c3645e5877f"
|
||||
unique_id:
|
||||
description: Unique ID of the particular disk on the target.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "3141463431303031"
|
||||
guid:
|
||||
description: GUID of the particular disk on the target.
|
||||
returned: if existent
|
||||
type: str
|
||||
sample: "{efa5f928-57b9-47fc-ae3e-902e85fbe77f}"
|
||||
path:
|
||||
description: Path of the particular disk on the target.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "\\\\?\\scsi#disk&ven_red_hat&prod_virtio#4&23208fd0&1&000000#{<id>}"
|
||||
partitions:
|
||||
description: Detailed information about one particular partition on the specified disk.
|
||||
returned: if existent
|
||||
type: list
|
||||
contains:
|
||||
number:
|
||||
description: Number of the particular partition.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 1
|
||||
size:
|
||||
description:
|
||||
- Size in bytes of the particular partition.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 838860800
|
||||
type:
|
||||
description: Type of the particular partition.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "IFS"
|
||||
gpt_type:
|
||||
description: gpt type of the particular partition.
|
||||
returned: if partition_style property of the particular disk has value "GPT"
|
||||
type: str
|
||||
sample: "{e3c9e316-0b5c-4db8-817d-f92df00215ae}"
|
||||
no_default_driveletter:
|
||||
description: Information whether the particular partition has a default drive letter or not.
|
||||
returned: if partition_style property of the particular disk has value "GPT"
|
||||
type: bool
|
||||
sample: true
|
||||
mbr_type:
|
||||
description: mbr type of the particular partition.
|
||||
returned: if partition_style property of the particular disk has value "MBR"
|
||||
type: int
|
||||
sample: 7
|
||||
active:
|
||||
description: Information whether the particular partition is an active partition or not.
|
||||
returned: if partition_style property of the particular disk has value "MBR"
|
||||
type: bool
|
||||
sample: true
|
||||
drive_letter:
|
||||
description: Drive letter of the particular partition.
|
||||
returned: if existent
|
||||
type: str
|
||||
sample: "C"
|
||||
transition_state:
|
||||
description: Transition state of the particular partition.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 1
|
||||
offset:
|
||||
description: Offset of the particular partition.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 368050176
|
||||
hidden:
|
||||
description: Information whether the particular partition is hidden or not.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
shadow_copy:
|
||||
description: Information whether the particular partition is a shadow copy of another partition.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
guid:
|
||||
description: GUID of the particular partition.
|
||||
returned: if existent
|
||||
type: str
|
||||
sample: "{302e475c-6e64-4674-a8e2-2f1c7018bf97}"
|
||||
access_paths:
|
||||
description: Access paths of the particular partition.
|
||||
returned: if existent
|
||||
type: str
|
||||
sample: "\\\\?\\Volume{85bdc4a8-f8eb-11e6-80fa-806e6f6e6963}\\"
|
||||
volumes:
|
||||
description: Detailed information about one particular volume on the specified partition.
|
||||
returned: if existent
|
||||
type: list
|
||||
contains:
|
||||
size:
|
||||
description:
|
||||
- Size in bytes of the particular volume.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 838856704
|
||||
size_remaining:
|
||||
description:
|
||||
- Remaining size in bytes of the particular volume.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 395620352
|
||||
type:
|
||||
description: File system type of the particular volume.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "NTFS"
|
||||
label:
|
||||
description: File system label of the particular volume.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "System Reserved"
|
||||
health_status:
|
||||
description: Health status of the particular volume.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Healthy"
|
||||
drive_type:
|
||||
description: Drive type of the particular volume.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Fixed"
|
||||
allocation_unit_size:
|
||||
description: Allocation unit size in bytes of the particular volume.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 4096
|
||||
object_id:
|
||||
description: Object ID of the particular volume.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "\\\\?\\Volume{85bdc4a9-f8eb-11e6-80fa-806e6f6e6963}\\"
|
||||
path:
|
||||
description: Path of the particular volume.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "\\\\?\\Volume{85bdc4a9-f8eb-11e6-80fa-806e6f6e6963}\\"
|
||||
physical_disk:
|
||||
description: Detailed information about physical disk properties of the particular disk.
|
||||
returned: if existent
|
||||
type: complex
|
||||
contains:
|
||||
media_type:
|
||||
description: Media type of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "UnSpecified"
|
||||
size:
|
||||
description:
|
||||
- Size in bytes of the particular physical disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 240057409536
|
||||
allocated_size:
|
||||
description:
|
||||
- Allocated size in bytes of the particular physical disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 240057409536
|
||||
device_id:
|
||||
description: Device ID of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "0"
|
||||
friendly_name:
|
||||
description: Friendly name of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "PhysicalDisk0"
|
||||
operational_status:
|
||||
description: Operational status of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "OK"
|
||||
health_status:
|
||||
description: Health status of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Healthy"
|
||||
bus_type:
|
||||
description: Bus type of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "SCSI"
|
||||
usage_type:
|
||||
description: Usage type of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Auto-Select"
|
||||
supported_usages:
|
||||
description: Supported usage types of the particular physical disk.
|
||||
returned: always
|
||||
type: complex
|
||||
contains:
|
||||
Count:
|
||||
description: Count of supported usage types.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 5
|
||||
value:
|
||||
description: List of supported usage types.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Auto-Select, Hot Spare"
|
||||
spindle_speed:
|
||||
description: Spindle speed in rpm of the particular physical disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 4294967295
|
||||
physical_location:
|
||||
description: Physical location of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Integrated : Adapter 3 : Port 0 : Target 0 : LUN 0"
|
||||
manufacturer:
|
||||
description: Manufacturer of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "SUSE"
|
||||
model:
|
||||
description: Model of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Xen Block"
|
||||
can_pool:
|
||||
description: Information whether the particular physical disk can be added to a storage pool.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
cannot_pool_reason:
|
||||
description: Information why the particular physical disk can not be added to a storage pool.
|
||||
returned: if can_pool property has value false
|
||||
type: str
|
||||
sample: "Insufficient Capacity"
|
||||
indication_enabled:
|
||||
description: Information whether indication is enabled for the particular physical disk.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
partial:
|
||||
description: Information whether the particular physical disk is partial.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
serial_number:
|
||||
description: Serial number of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "b62beac80c3645e5877f"
|
||||
object_id:
|
||||
description: Object ID of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: '{1}\\\\HOST\\root/Microsoft/Windows/Storage/Providers_v2\\SPACES_PhysicalDisk.ObjectId=\"{<object_id>}:PD:{<pd>}\"'
|
||||
unique_id:
|
||||
description: Unique ID of the particular physical disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "3141463431303031"
|
||||
virtual_disk:
|
||||
description: Detailed information about virtual disk properties of the particular disk.
|
||||
returned: if existent
|
||||
type: complex
|
||||
contains:
|
||||
size:
|
||||
description:
|
||||
- Size in bytes of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 240057409536
|
||||
allocated_size:
|
||||
description:
|
||||
- Allocated size in bytes of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 240057409536
|
||||
footprint_on_pool:
|
||||
description:
|
||||
- Footprint on pool in bytes of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 240057409536
|
||||
name:
|
||||
description: Name of the particular virtual disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "vDisk1"
|
||||
friendly_name:
|
||||
description: Friendly name of the particular virtual disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Prod2 Virtual Disk"
|
||||
operational_status:
|
||||
description: Operational status of the particular virtual disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "OK"
|
||||
health_status:
|
||||
description: Health status of the particular virtual disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Healthy"
|
||||
provisioning_type:
|
||||
description: Provisioning type of the particular virtual disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Thin"
|
||||
allocation_unit_size:
|
||||
description: Allocation unit size in bytes of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 4096
|
||||
media_type:
|
||||
description: Media type of the particular virtual disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Unspecified"
|
||||
parity_layout:
|
||||
description: Parity layout of the particular virtual disk.
|
||||
returned: if existent
|
||||
type: int
|
||||
sample: 1
|
||||
access:
|
||||
description: Access of the particular virtual disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Read/Write"
|
||||
detached_reason:
|
||||
description: Detached reason of the particular virtual disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "None"
|
||||
write_cache_size:
|
||||
description: Write cache size in byte of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 100
|
||||
fault_domain_awareness:
|
||||
description: Fault domain awareness of the particular virtual disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "PhysicalDisk"
|
||||
inter_leave:
|
||||
description:
|
||||
- Inter leave in bytes of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 102400
|
||||
deduplication_enabled:
|
||||
description: Information whether deduplication is enabled for the particular virtual disk.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
enclosure_aware:
|
||||
description: Information whether the particular virtual disk is enclosure aware.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
manual_attach:
|
||||
description: Information whether the particular virtual disk is manual attached.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
snapshot:
|
||||
description: Information whether the particular virtual disk is a snapshot.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
tiered:
|
||||
description: Information whether the particular virtual disk is tiered.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
physical_sector_size:
|
||||
description: Physical sector size in bytes of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 4096
|
||||
logical_sector_size:
|
||||
description: Logical sector size in byte of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 512
|
||||
available_copies:
|
||||
description: Number of the available copies of the particular virtual disk.
|
||||
returned: if existent
|
||||
type: int
|
||||
sample: 1
|
||||
columns:
|
||||
description: Number of the columns of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 2
|
||||
groups:
|
||||
description: Number of the groups of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 1
|
||||
physical_disk_redundancy:
|
||||
description: Type of the physical disk redundancy of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 1
|
||||
read_cache_size:
|
||||
description: Read cache size in byte of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 0
|
||||
request_no_spof:
|
||||
description: Information whether the particular virtual disk requests no single point of failure.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
resiliency_setting_name:
|
||||
description: Type of the physical disk redundancy of the particular virtual disk.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 1
|
||||
object_id:
|
||||
description: Object ID of the particular virtual disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: '{1}\\\\HOST\\root/Microsoft/Windows/Storage/Providers_v2\\SPACES_VirtualDisk.ObjectId=\"{<object_id>}:VD:{<vd>}\"'
|
||||
unique_id:
|
||||
description: Unique ID of the particular virtual disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "260542E4C6B01D47A8FA7630FD90FFDE"
|
||||
unique_id_format:
|
||||
description: Unique ID format of the particular virtual disk.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Vendor Specific"
|
||||
win32_disk_drive:
|
||||
description: Representation of the Win32_DiskDrive class.
|
||||
returned: if existent
|
||||
type: complex
|
||||
contains:
|
||||
availability:
|
||||
description: Availability and status of the device.
|
||||
returned: always
|
||||
type: int
|
||||
bytes_per_sector:
|
||||
description: Number of bytes in each sector for the physical disk drive.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 512
|
||||
capabilities:
|
||||
description:
|
||||
- Array of capabilities of the media access device.
|
||||
- For example, the device may support random access (3), removable media (7), and automatic cleaning (9).
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- 3
|
||||
- 4
|
||||
capability_descriptions:
|
||||
description:
|
||||
- List of more detailed explanations for any of the access device features indicated in the Capabilities array.
|
||||
- Note, each entry of this array is related to the entry in the Capabilities array that is located at the same index.
|
||||
returned: always
|
||||
type: list
|
||||
sample:
|
||||
- Random Access
|
||||
- Supports Writing
|
||||
caption:
|
||||
description: Short description of the object.
|
||||
returned: always
|
||||
type: str
|
||||
sample: VMware Virtual disk SCSI Disk Device
|
||||
compression_method:
|
||||
description: Algorithm or tool used by the device to support compression.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Compressed
|
||||
config_manager_error_code:
|
||||
description: Windows Configuration Manager error code.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 0
|
||||
config_manager_user_config:
|
||||
description: If True, the device is using a user-defined configuration.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
creation_class_name:
|
||||
description:
|
||||
- Name of the first concrete class to appear in the inheritance chain used in the creation of an instance.
|
||||
- When used with the other key properties of the class, the property allows all instances of this class
|
||||
- and its subclasses to be uniquely identified.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Win32_DiskDrive
|
||||
default_block_size:
|
||||
description: Default block size, in bytes, for this device.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 512
|
||||
description:
|
||||
description: Description of the object.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Disk drive
|
||||
device_id:
|
||||
description: Unique identifier of the disk drive with other devices on the system.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "\\\\.\\PHYSICALDRIVE0"
|
||||
error_cleared:
|
||||
description: If True, the error reported in LastErrorCode is now cleared.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
error_description:
|
||||
description:
|
||||
- More information about the error recorded in LastErrorCode,
|
||||
- and information on any corrective actions that may be taken.
|
||||
returned: always
|
||||
type: str
|
||||
error_methodology:
|
||||
description: Type of error detection and correction supported by this device.
|
||||
returned: always
|
||||
type: str
|
||||
firmware_revision:
|
||||
description: Revision for the disk drive firmware that is assigned by the manufacturer.
|
||||
returned: always
|
||||
type: str
|
||||
sample: 1.0
|
||||
index:
|
||||
description:
|
||||
- Physical drive number of the given drive.
|
||||
- This property is filled by the STORAGE_DEVICE_NUMBER structure returned from the IOCTL_STORAGE_GET_DEVICE_NUMBER control code
|
||||
- A value of 0xffffffff indicates that the given drive does not map to a physical drive.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 0
|
||||
install_date:
|
||||
description: Date and time the object was installed. This property does not need a value to indicate that the object is installed.
|
||||
returned: always
|
||||
type: str
|
||||
interface_type:
|
||||
description: Interface type of physical disk drive.
|
||||
returned: always
|
||||
type: str
|
||||
sample: SCSI
|
||||
last_error_code:
|
||||
description: Last error code reported by the logical device.
|
||||
returned: always
|
||||
type: int
|
||||
manufacturer:
|
||||
description: Name of the disk drive manufacturer.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Seagate
|
||||
max_block_size:
|
||||
description: Maximum block size, in bytes, for media accessed by this device.
|
||||
returned: always
|
||||
type: int
|
||||
max_media_size:
|
||||
description: Maximum media size, in kilobytes, of media supported by this device.
|
||||
returned: always
|
||||
type: int
|
||||
media_loaded:
|
||||
description:
|
||||
- If True, the media for a disk drive is loaded, which means that the device has a readable file system and is accessible.
|
||||
- For fixed disk drives, this property will always be TRUE.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
media_type:
|
||||
description: Type of media used or accessed by this device.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Fixed hard disk media
|
||||
min_block_size:
|
||||
description: Minimum block size, in bytes, for media accessed by this device.
|
||||
returned: always
|
||||
type: int
|
||||
model:
|
||||
description: Manufacturer's model number of the disk drive.
|
||||
returned: always
|
||||
type: str
|
||||
sample: ST32171W
|
||||
name:
|
||||
description: Label by which the object is known. When subclassed, the property can be overridden to be a key property.
|
||||
returned: always
|
||||
type: str
|
||||
sample: \\\\.\\PHYSICALDRIVE0
|
||||
needs_cleaning:
|
||||
description:
|
||||
- If True, the media access device needs cleaning.
|
||||
- Whether manual or automatic cleaning is possible is indicated in the Capabilities property.
|
||||
returned: always
|
||||
type: bool
|
||||
number_of_media_supported:
|
||||
description:
|
||||
- Maximum number of media which can be supported or inserted
|
||||
- (when the media access device supports multiple individual media).
|
||||
returned: always
|
||||
type: int
|
||||
partitions:
|
||||
description: Number of partitions on this physical disk drive that are recognized by the operating system.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 3
|
||||
pnp_device_id:
|
||||
description: Windows Plug and Play device identifier of the logical device.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "SCSI\\DISK&VEN_VMWARE&PROD_VIRTUAL_DISK\\5&1982005&0&000000"
|
||||
power_management_capabilities:
|
||||
description: Array of the specific power-related capabilities of a logical device.
|
||||
returned: always
|
||||
type: list
|
||||
power_management_supported:
|
||||
description:
|
||||
- If True, the device can be power-managed (can be put into suspend mode, and so on).
|
||||
- The property does not indicate that power management features are currently enabled,
|
||||
- only that the logical device is capable of power management.
|
||||
returned: always
|
||||
type: bool
|
||||
scsi_bus:
|
||||
description: SCSI bus number of the disk drive.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 0
|
||||
scsi_logical_unit:
|
||||
description: SCSI logical unit number (LUN) of the disk drive.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 0
|
||||
scsi_port:
|
||||
description: SCSI port number of the disk drive.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 0
|
||||
scsi_target_id:
|
||||
description: SCSI identifier number of the disk drive.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 0
|
||||
sectors_per_track:
|
||||
description: Number of sectors in each track for this physical disk drive.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 63
|
||||
serial_number:
|
||||
description: Number allocated by the manufacturer to identify the physical media.
|
||||
returned: always
|
||||
type: str
|
||||
sample: 6000c298f34101b38cb2b2508926b9de
|
||||
signature:
|
||||
description: Disk identification. This property can be used to identify a shared resource.
|
||||
returned: always
|
||||
type: int
|
||||
size:
|
||||
description:
|
||||
- Size of the disk drive. It is calculated by multiplying the total number of cylinders, tracks in each cylinder,
|
||||
- sectors in each track, and bytes in each sector.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 53686402560
|
||||
status:
|
||||
description:
|
||||
- Current status of the object. Various operational and nonoperational statuses can be defined.
|
||||
- 'Operational statuses include: "OK", "Degraded", and "Pred Fail"'
|
||||
- (an element, such as a SMART-enabled hard disk drive, may be functioning properly but predicting a failure in the near future).
|
||||
- 'Nonoperational statuses include: "Error", "Starting", "Stopping", and "Service".'
|
||||
- '"Service", could apply during mirror-resilvering of a disk, reload of a user permissions list, or other administrative work.'
|
||||
- Not all such work is online, yet the managed element is neither "OK" nor in one of the other states.
|
||||
returned: always
|
||||
type: str
|
||||
sample: OK
|
||||
status_info:
|
||||
description:
|
||||
- State of the logical device. If this property does not apply to the logical device, the value 5 (Not Applicable) should be used.
|
||||
returned: always
|
||||
type: int
|
||||
system_creation_class_name:
|
||||
description: Value of the scoping computer's CreationClassName property.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Win32_ComputerSystem
|
||||
system_name:
|
||||
description: Name of the scoping system.
|
||||
returned: always
|
||||
type: str
|
||||
sample: WILMAR-TEST-123
|
||||
total_cylinders:
|
||||
description:
|
||||
- Total number of cylinders on the physical disk drive.
|
||||
- 'Note: the value for this property is obtained through extended functions of BIOS interrupt 13h.'
|
||||
- The value may be inaccurate if the drive uses a translation scheme to support high-capacity disk sizes.
|
||||
- Consult the manufacturer for accurate drive specifications.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 6527
|
||||
total_heads:
|
||||
description:
|
||||
- Total number of heads on the disk drive.
|
||||
- 'Note: the value for this property is obtained through extended functions of BIOS interrupt 13h.'
|
||||
- The value may be inaccurate if the drive uses a translation scheme to support high-capacity disk sizes.
|
||||
- Consult the manufacturer for accurate drive specifications.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 255
|
||||
total_sectors:
|
||||
description:
|
||||
- Total number of sectors on the physical disk drive.
|
||||
- 'Note: the value for this property is obtained through extended functions of BIOS interrupt 13h.'
|
||||
- The value may be inaccurate if the drive uses a translation scheme to support high-capacity disk sizes.
|
||||
- Consult the manufacturer for accurate drive specifications.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 104856255
|
||||
total_tracks:
|
||||
description:
|
||||
- Total number of tracks on the physical disk drive.
|
||||
- 'Note: the value for this property is obtained through extended functions of BIOS interrupt 13h.'
|
||||
- The value may be inaccurate if the drive uses a translation scheme to support high-capacity disk sizes.
|
||||
- Consult the manufacturer for accurate drive specifications.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 1664385
|
||||
tracks_per_cylinder:
|
||||
description:
|
||||
- Number of tracks in each cylinder on the physical disk drive.
|
||||
- 'Note: the value for this property is obtained through extended functions of BIOS interrupt 13h.'
|
||||
- The value may be inaccurate if the drive uses a translation scheme to support high-capacity disk sizes.
|
||||
- Consult the manufacturer for accurate drive specifications.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 255
|
||||
'''
|
@ -1,78 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Red Hat, Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
Set-StrictMode -Version 2
|
||||
|
||||
If(-not (Get-Command Get-DiskImage -ErrorAction SilentlyContinue)) {
|
||||
Fail-Json -message "win_disk_image requires Windows 8+ or Windows Server 2012+"
|
||||
}
|
||||
|
||||
$parsed_args = Parse-Args $args -supports_check_mode $true
|
||||
|
||||
$result = @{changed=$false}
|
||||
|
||||
$image_path = Get-AnsibleParam $parsed_args "image_path" -failifempty $result
|
||||
$state = Get-AnsibleParam $parsed_args "state" -default "present" -validateset "present","absent"
|
||||
$check_mode = Get-AnsibleParam $parsed_args "_ansible_check_mode" -default $false
|
||||
|
||||
$di = Get-DiskImage $image_path
|
||||
|
||||
If($state -eq "present") {
|
||||
If(-not $di.Attached) {
|
||||
$result.changed = $true
|
||||
|
||||
If(-not $check_mode) {
|
||||
$di = Mount-DiskImage $image_path -PassThru
|
||||
|
||||
# the actual mount is async, so the CIMInstance result may not immediately contain the data we need
|
||||
$retry_count = 0
|
||||
While(-not $di.Attached -and $retry_count -lt 5) {
|
||||
Start-Sleep -Seconds 1 > $null
|
||||
$di = $di | Get-DiskImage
|
||||
$retry_count++
|
||||
}
|
||||
|
||||
If(-not $di.Attached) {
|
||||
Fail-Json $result -message "Timed out waiting for disk to attach"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# FUTURE: detect/handle "ejected" ISOs
|
||||
# FUTURE: support explicit drive letter and NTFS in-volume mountpoints.
|
||||
# VHDs don't always auto-assign, and other system settings can prevent automatic assignment
|
||||
|
||||
If($di.Attached) { # only try to get the mount_path if the disk is attached (
|
||||
If($di.StorageType -eq 1) { # ISO, we can get the mountpoint directly from Get-Volume
|
||||
$drive_letters = ($di | Get-Volume).DriveLetter
|
||||
}
|
||||
ElseIf($di.StorageType -in @(2,3)) { # VHD/VHDX, need Get-Disk + Get-Partition to discover mountpoint
|
||||
$drive_letters = ($di | Get-Disk | Get-Partition).DriveLetter
|
||||
}
|
||||
# remove any null entries (no drive letter)
|
||||
$drive_letters = $drive_letters | Where-Object { $_ }
|
||||
|
||||
If(-not $drive_letters) {
|
||||
Fail-Json -message "Unable to retrieve drive letter from mounted image"
|
||||
}
|
||||
|
||||
# mount_path is deprecated and will be removed in 2.11, use mount_paths which contains all the partitions instead
|
||||
$result.mount_path = $drive_letters[0] + ":\"
|
||||
$result.mount_paths = @($drive_letters | ForEach-Object { "$($_):\" })
|
||||
}
|
||||
}
|
||||
ElseIf($state -eq "absent") {
|
||||
If($di.Attached) {
|
||||
$result.changed = $true
|
||||
If(-not $check_mode) {
|
||||
Dismount-DiskImage $image_path > $null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Exit-Json $result
|
@ -1,67 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Red Hat, Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'core'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
module: win_disk_image
|
||||
short_description: Manage ISO/VHD/VHDX mounts on Windows hosts
|
||||
version_added: '2.3'
|
||||
description:
|
||||
- Manages mount behavior for a specified ISO, VHD, or VHDX image on a Windows host. When C(state) is C(present),
|
||||
the image will be mounted under a system-assigned drive letter, which will be returned in the C(mount_path) value
|
||||
of the module result.
|
||||
- Requires Windows 8+ or Windows Server 2012+.
|
||||
options:
|
||||
image_path:
|
||||
description:
|
||||
- Path to an ISO, VHD, or VHDX image on the target Windows host (the file cannot reside on a network share)
|
||||
type: str
|
||||
required: yes
|
||||
state:
|
||||
description:
|
||||
- Whether the image should be present as a drive-letter mount or not.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
author:
|
||||
- Matt Davis (@nitzmahone)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Run installer from mounted ISO, then unmount
|
||||
- name: Ensure an ISO is mounted
|
||||
win_disk_image:
|
||||
image_path: C:\install.iso
|
||||
state: present
|
||||
register: disk_image_out
|
||||
|
||||
- name: Run installer from mounted ISO
|
||||
win_package:
|
||||
path: '{{ disk_image_out.mount_paths[0] }}setup\setup.exe'
|
||||
product_id: 35a4e767-0161-46b0-979f-e61f282fee21
|
||||
state: present
|
||||
|
||||
- name: Unmount ISO
|
||||
win_disk_image:
|
||||
image_path: C:\install.iso
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
mount_path:
|
||||
description: Filesystem path where the target image is mounted, this has been deprecated in favour of C(mount_paths).
|
||||
returned: when C(state) is C(present)
|
||||
type: str
|
||||
sample: F:\
|
||||
mount_paths:
|
||||
description: A list of filesystem paths mounted from the target image.
|
||||
returned: when C(state) is C(present)
|
||||
type: list
|
||||
sample: [ 'E:\', 'F:\' ]
|
||||
'''
|
@ -1,149 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2019, Hitachi ID Systems, Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
name = @{ type = "str"; required = $true }
|
||||
state = @{ type = "str"; choices = "absent", "present"; default = "present" }
|
||||
ttl = @{ type = "int"; default = "3600" }
|
||||
type = @{ type = "str"; choices = "A","AAAA","CNAME","PTR"; required = $true }
|
||||
value = @{ type = "list"; elements = "str"; default = @() ; aliases=@( 'values' )}
|
||||
zone = @{ type = "str"; required = $true }
|
||||
computer_name = @{ type = "str" }
|
||||
}
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$name = $module.Params.name
|
||||
$state = $module.Params.state
|
||||
$ttl = $module.Params.ttl
|
||||
$type = $module.Params.type
|
||||
$values = $module.Params.value
|
||||
$zone = $module.Params.zone
|
||||
$dns_computer_name = $module.Params.computer_name
|
||||
|
||||
|
||||
$extra_args = @{}
|
||||
if ($null -ne $dns_computer_name) {
|
||||
$extra_args.ComputerName = $dns_computer_name
|
||||
}
|
||||
|
||||
if ($state -eq 'present') {
|
||||
if ($values.Count -eq 0) {
|
||||
$module.FailJson("Parameter 'values' must be non-empty when state='present'")
|
||||
}
|
||||
} else {
|
||||
if ($values.Count -ne 0) {
|
||||
$module.FailJson("Parameter 'values' must be undefined or empty when state='absent'")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# TODO: add warning for forest minTTL override -- see https://docs.microsoft.com/en-us/windows/desktop/ad/configuration-of-ttl-limits
|
||||
if ($ttl -lt 1 -or $ttl -gt 31557600) {
|
||||
$module.FailJson("Parameter 'ttl' must be between 1 and 31557600")
|
||||
}
|
||||
$ttl = New-TimeSpan -Seconds $ttl
|
||||
|
||||
|
||||
if (($type -eq 'CNAME' -or $type -eq 'PTR') -and $null -ne $values -and $values.Count -gt 0 -and $zone[-1] -ne '.') {
|
||||
# CNAMEs and PTRs should be '.'-terminated, or record matching will fail
|
||||
$values = $values | ForEach-Object {
|
||||
if ($_ -Like "*.") { $_ } else { "$_." }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$record_argument_name = @{
|
||||
A = "IPv4Address";
|
||||
AAAA = "IPv6Address";
|
||||
CNAME = "HostNameAlias";
|
||||
# MX = "MailExchange";
|
||||
# NS = "NameServer";
|
||||
PTR = "PtrDomainName";
|
||||
# TXT = "DescriptiveText"
|
||||
}[$type]
|
||||
|
||||
|
||||
$changes = @{
|
||||
before = "";
|
||||
after = ""
|
||||
}
|
||||
|
||||
|
||||
$records = Get-DnsServerResourceRecord -ZoneName $zone -Name $name -RRType $type -Node -ErrorAction:Ignore @extra_args | Sort-Object
|
||||
if ($null -ne $records) {
|
||||
# We use [Hashtable]$required_values below as a set rather than a map.
|
||||
# It provides quick lookup to test existing DNS record against. By removing
|
||||
# items as each is processed, whatever remains at the end is missing
|
||||
# content (that needs to be added).
|
||||
$required_values = @{}
|
||||
foreach ($value in $values) {
|
||||
$required_values[$value.ToString()] = $null
|
||||
}
|
||||
|
||||
foreach ($record in $records) {
|
||||
$record_value = $record.RecordData.$record_argument_name.ToString()
|
||||
|
||||
if ($required_values.ContainsKey($record_value)) {
|
||||
# This record matches one of the values; but does it match the TTL?
|
||||
if ($record.TimeToLive -ne $ttl) {
|
||||
$new_record = $record.Clone()
|
||||
$new_record.TimeToLive = $ttl
|
||||
Set-DnsServerResourceRecord -ZoneName $zone -OldInputObject $record -NewInputObject $new_record -WhatIf:$module.CheckMode @extra_args
|
||||
|
||||
$changes.before += "[$zone] $($record.HostName) $($record.TimeToLive.TotalSeconds) IN $type $record_value`n"
|
||||
$changes.after += "[$zone] $($record.HostName) $($ttl.TotalSeconds) IN $type $record_value`n"
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
|
||||
# Cross this one off the list, so we don't try adding it later
|
||||
$required_values.Remove($record_value)
|
||||
} else {
|
||||
# This record doesn't match any of the values, and must be removed
|
||||
$record | Remove-DnsServerResourceRecord -ZoneName $zone -Force -WhatIf:$module.CheckMode @extra_args
|
||||
|
||||
$changes.before += "[$zone] $($record.HostName) $($record.TimeToLive.TotalSeconds) IN $type $record_value`n"
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Whatever is left in $required_values needs to be added
|
||||
$values = $required_values.Keys
|
||||
}
|
||||
|
||||
|
||||
if ($null -ne $values -and $values.Count -gt 0) {
|
||||
foreach ($value in $values) {
|
||||
$splat_args = @{ $type = $true; $record_argument_name = $value }
|
||||
$module.Result.debug_splat_args = $splat_args
|
||||
try {
|
||||
Add-DnsServerResourceRecord -ZoneName $zone -Name $name -AllowUpdateAny -TimeToLive $ttl @splat_args -WhatIf:$module.CheckMode @extra_args
|
||||
} catch {
|
||||
$module.FailJson("Error adding DNS $type resource $name in zone $zone with value $value", $_)
|
||||
}
|
||||
$changes.after += "[$zone] $name $($ttl.TotalSeconds) IN $type $value`n"
|
||||
}
|
||||
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
|
||||
if ($module.CheckMode) {
|
||||
# Simulated changes
|
||||
$module.Diff.before = $changes.before
|
||||
$module.Diff.after = $changes.after
|
||||
} else {
|
||||
# Real changes
|
||||
$records_end = Get-DnsServerResourceRecord -ZoneName $zone -Name $name -RRType $type -Node -ErrorAction:Ignore @extra_args | Sort-Object
|
||||
|
||||
$module.Diff.before = @($records | ForEach-Object { "[$zone] $($_.HostName) $($_.TimeToLive.TotalSeconds) IN $type $($_.RecordData.$record_argument_name.ToString())`n" }) -join ''
|
||||
$module.Diff.after = @($records_end | ForEach-Object { "[$zone] $($_.HostName) $($_.TimeToLive.TotalSeconds) IN $type $($_.RecordData.$record_argument_name.ToString())`n" }) -join ''
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
@ -1,131 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Hitachi ID Systems, Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# This is a windows documentation stub. The actual code lives in the .ps1
|
||||
# file of the same name.
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_dns_record
|
||||
version_added: "2.8"
|
||||
short_description: Manage Windows Server DNS records
|
||||
description:
|
||||
- Manage DNS records within an existing Windows Server DNS zone.
|
||||
author: John Nelson (@johnboy2)
|
||||
requirements:
|
||||
- This module requires Windows 8, Server 2012, or newer.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the record.
|
||||
required: yes
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Whether the record should exist or not.
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
type: str
|
||||
ttl:
|
||||
description:
|
||||
- The "time to live" of the record, in seconds.
|
||||
- Ignored when C(state=absent).
|
||||
- Valid range is 1 - 31557600.
|
||||
- Note that an Active Directory forest can specify a minimum TTL, and will
|
||||
dynamically "round up" other values to that minimum.
|
||||
default: 3600
|
||||
type: int
|
||||
type:
|
||||
description:
|
||||
- The type of DNS record to manage.
|
||||
choices: [ A, AAAA, CNAME, PTR ]
|
||||
required: yes
|
||||
type: str
|
||||
value:
|
||||
description:
|
||||
- The value(s) to specify. Required when C(state=present).
|
||||
- When C(type=PTR) only the partial part of the IP should be given.
|
||||
aliases: [ values ]
|
||||
type: list
|
||||
zone:
|
||||
description:
|
||||
- The name of the zone to manage (eg C(example.com)).
|
||||
- The zone must already exist.
|
||||
required: yes
|
||||
type: str
|
||||
computer_name:
|
||||
description:
|
||||
- Specifies a DNS server.
|
||||
- You can specify an IP address or any value that resolves to an IP
|
||||
address, such as a fully qualified domain name (FQDN), host name, or
|
||||
NETBIOS name.
|
||||
type: str
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Demonstrate creating a matching A and PTR record.
|
||||
|
||||
- name: Create database server record
|
||||
win_dns_record:
|
||||
name: "cgyl1404p.amer.example.com"
|
||||
type: "A"
|
||||
value: "10.1.1.1"
|
||||
zone: "amer.example.com"
|
||||
|
||||
- name: Create matching PTR record
|
||||
win_dns_record:
|
||||
name: "1.1.1"
|
||||
type: "PTR"
|
||||
value: "db1"
|
||||
zone: "10.in-addr.arpa"
|
||||
|
||||
# Demonstrate replacing an A record with a CNAME
|
||||
|
||||
- name: Remove static record
|
||||
win_dns_record:
|
||||
name: "db1"
|
||||
type: "A"
|
||||
state: absent
|
||||
zone: "amer.example.com"
|
||||
|
||||
- name: Create database server alias
|
||||
win_dns_record:
|
||||
name: "db1"
|
||||
type: "CNAME"
|
||||
value: "cgyl1404p.amer.example.com"
|
||||
zone: "amer.example.com"
|
||||
|
||||
# Demonstrate creating multiple A records for the same name
|
||||
|
||||
- name: Create multiple A record values for www
|
||||
win_dns_record:
|
||||
name: "www"
|
||||
type: "A"
|
||||
values:
|
||||
- 10.0.42.5
|
||||
- 10.0.42.6
|
||||
- 10.0.42.7
|
||||
zone: "example.com"
|
||||
|
||||
# Demonstrates a partial update (replace some existing values with new ones)
|
||||
# for a pre-existing name
|
||||
|
||||
- name: Update www host with new addresses
|
||||
win_dns_record:
|
||||
name: "www"
|
||||
type: "A"
|
||||
values:
|
||||
- 10.0.42.5 # this old value was kept (others removed)
|
||||
- 10.0.42.12 # this new value was added
|
||||
zone: "example.com"
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
@ -1,208 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, AMTEGA - Xunta de Galicia
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Preparing result
|
||||
$result = @{}
|
||||
$result.changed = $false
|
||||
|
||||
# Parameter ingestion
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
$diff_support = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false
|
||||
|
||||
$name = Get-AnsibleParam -obj $params -name "name" -failifempty $true -resultobj $result
|
||||
$sam_account_name = Get-AnsibleParam -obj $params -name "sam_account_name" -default "$name$"
|
||||
If (-not $sam_account_name.EndsWith("$")) {
|
||||
Fail-Json -obj $result -message "sam_account_name must end in $"
|
||||
}
|
||||
$enabled = Get-AnsibleParam -obj $params -name "enabled" -type "bool" -default $true
|
||||
$description = Get-AnsibleParam -obj $params -name "description" -default $null
|
||||
$domain_username = Get-AnsibleParam -obj $params -name "domain_username" -type "str"
|
||||
$domain_password = Get-AnsibleParam -obj $params -name "domain_password" -type "str" -failifempty ($null -ne $domain_username)
|
||||
$domain_server = Get-AnsibleParam -obj $params -name "domain_server" -type "str"
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -ValidateSet "present","absent" -default "present"
|
||||
|
||||
$extra_args = @{}
|
||||
if ($null -ne $domain_username) {
|
||||
$domain_password = ConvertTo-SecureString $domain_password -AsPlainText -Force
|
||||
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $domain_username, $domain_password
|
||||
$extra_args.Credential = $credential
|
||||
}
|
||||
if ($null -ne $domain_server) {
|
||||
$extra_args.Server = $domain_server
|
||||
}
|
||||
|
||||
If ($state -eq "present") {
|
||||
$dns_hostname = Get-AnsibleParam -obj $params -name "dns_hostname" -failifempty $true -resultobj $result
|
||||
$ou = Get-AnsibleParam -obj $params -name "ou" -failifempty $true -resultobj $result
|
||||
$distinguished_name = "CN=$name,$ou"
|
||||
|
||||
$desired_state = [ordered]@{
|
||||
name = $name
|
||||
sam_account_name = $sam_account_name
|
||||
dns_hostname = $dns_hostname
|
||||
ou = $ou
|
||||
distinguished_name = $distinguished_name
|
||||
description = $description
|
||||
enabled = $enabled
|
||||
state = $state
|
||||
}
|
||||
} Else {
|
||||
$desired_state = [ordered]@{
|
||||
name = $name
|
||||
sam_account_name = $sam_account_name
|
||||
state = $state
|
||||
}
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
Function Get-InitialState($desired_state) {
|
||||
# Test computer exists
|
||||
$computer = Try {
|
||||
Get-ADComputer `
|
||||
-Identity $desired_state.sam_account_name `
|
||||
-Properties DistinguishedName,DNSHostName,Enabled,Name,SamAccountName,Description,ObjectClass `
|
||||
@extra_args
|
||||
} Catch { $null }
|
||||
If ($computer) {
|
||||
$initial_state = [ordered]@{
|
||||
name = $computer.Name
|
||||
sam_account_name = $computer.SamAccountName
|
||||
dns_hostname = $computer.DNSHostName
|
||||
# Get OU from regexp that removes all characters to the first ","
|
||||
ou = $computer.DistinguishedName -creplace "^[^,]*,",""
|
||||
distinguished_name = $computer.DistinguishedName
|
||||
description = $computer.Description
|
||||
enabled = $computer.Enabled
|
||||
state = "present"
|
||||
}
|
||||
} Else {
|
||||
$initial_state = [ordered]@{
|
||||
name = $desired_state.name
|
||||
sam_account_name = $desired_state.sam_account_name
|
||||
state = "absent"
|
||||
}
|
||||
}
|
||||
|
||||
return $initial_state
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
Function Set-ConstructedState($initial_state, $desired_state) {
|
||||
Try {
|
||||
Set-ADComputer `
|
||||
-Identity $desired_state.name `
|
||||
-SamAccountName $desired_state.name `
|
||||
-DNSHostName $desired_state.dns_hostname `
|
||||
-Enabled $desired_state.enabled `
|
||||
-Description $desired_state.description `
|
||||
-WhatIf:$check_mode `
|
||||
@extra_args
|
||||
} Catch {
|
||||
Fail-Json -obj $result -message "Failed to set the AD object $($desired_state.name): $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
If ($initial_state.distinguished_name -cne $desired_state.distinguished_name) {
|
||||
# Move computer to OU
|
||||
Try {
|
||||
Get-ADComputer -Identity $desired_state.sam_account_name @extra_args |
|
||||
Move-ADObject `
|
||||
-TargetPath $desired_state.ou `
|
||||
-Confirm:$False `
|
||||
-WhatIf:$check_mode `
|
||||
@extra_args
|
||||
} Catch {
|
||||
Fail-Json -obj $result -message "Failed to move the AD object $($initial_state.distinguished_name) to $($desired_state.distinguished_name): $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
Function Add-ConstructedState($desired_state) {
|
||||
Try {
|
||||
New-ADComputer `
|
||||
-Name $desired_state.name `
|
||||
-SamAccountName $desired_state.sam_account_name `
|
||||
-DNSHostName $desired_state.dns_hostname `
|
||||
-Path $desired_state.ou `
|
||||
-Enabled $desired_state.enabled `
|
||||
-Description $desired_state.description `
|
||||
-WhatIf:$check_mode `
|
||||
@extra_args
|
||||
} Catch {
|
||||
Fail-Json -obj $result -message "Failed to create the AD object $($desired_state.name): $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
Function Remove-ConstructedState($initial_state) {
|
||||
Try {
|
||||
Get-ADComputer -Identity $initial_state.sam_account_name @extra_args |
|
||||
Remove-ADObject `
|
||||
-Recursive `
|
||||
-Confirm:$False `
|
||||
-WhatIf:$check_mode `
|
||||
@extra_args
|
||||
} Catch {
|
||||
Fail-Json -obj $result -message "Failed to remove the AD object $($desired_state.name): $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
Function are_hashtables_equal($x, $y) {
|
||||
# Compare not nested HashTables
|
||||
Foreach ($key in $x.Keys) {
|
||||
If (($y.Keys -notcontains $key) -or ($x[$key] -cne $y[$key])) {
|
||||
Return $false
|
||||
}
|
||||
}
|
||||
foreach ($key in $y.Keys) {
|
||||
if (($x.Keys -notcontains $key) -or ($x[$key] -cne $y[$key])) {
|
||||
Return $false
|
||||
}
|
||||
}
|
||||
Return $true
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
$initial_state = Get-InitialState($desired_state)
|
||||
|
||||
If ($desired_state.state -eq "present") {
|
||||
If ($initial_state.state -eq "present") {
|
||||
$in_desired_state = are_hashtables_equal $initial_state $desired_state
|
||||
|
||||
If (-not $in_desired_state) {
|
||||
Set-ConstructedState $initial_state $desired_state
|
||||
}
|
||||
} Else { # $desired_state.state = "Present" & $initial_state.state = "Absent"
|
||||
Add-ConstructedState($desired_state)
|
||||
}
|
||||
} Else { # $desired_state.state = "Absent"
|
||||
If ($initial_state.state -eq "present") {
|
||||
Remove-ConstructedState($initial_state)
|
||||
}
|
||||
}
|
||||
|
||||
If ($diff_support) {
|
||||
$diff = @{
|
||||
before = $initial_state
|
||||
after = $desired_state
|
||||
}
|
||||
$result.diff = $diff
|
||||
}
|
||||
|
||||
Exit-Json -obj $result
|
@ -1,123 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, AMTEGA - Xunta de Galicia
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_domain_computer
|
||||
short_description: Manage computers in Active Directory
|
||||
description:
|
||||
- Create, read, update and delete computers in Active Directory using a
|
||||
windows bridge computer to launch New-ADComputer, Get-ADComputer,
|
||||
Set-ADComputer, Remove-ADComputer and Move-ADObject powershell commands.
|
||||
version_added: '2.6'
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Specifies the name of the object.
|
||||
- This parameter sets the Name property of the Active Directory object.
|
||||
- The LDAP display name (ldapDisplayName) of this property is name.
|
||||
type: str
|
||||
required: true
|
||||
sam_account_name:
|
||||
description:
|
||||
- Specifies the Security Account Manager (SAM) account name of the
|
||||
computer.
|
||||
- It maximum is 256 characters, 15 is advised for older
|
||||
operating systems compatibility.
|
||||
- The LDAP display name (ldapDisplayName) for this property is sAMAccountName.
|
||||
- If ommitted the value is the same as C(name).
|
||||
- Note that all computer SAMAccountNames need to end with a $.
|
||||
type: str
|
||||
enabled:
|
||||
description:
|
||||
- Specifies if an account is enabled.
|
||||
- An enabled account requires a password.
|
||||
- This parameter sets the Enabled property for an account object.
|
||||
- This parameter also sets the ADS_UF_ACCOUNTDISABLE flag of the
|
||||
Active Directory User Account Control (UAC) attribute.
|
||||
type: bool
|
||||
default: yes
|
||||
ou:
|
||||
description:
|
||||
- Specifies the X.500 path of the Organizational Unit (OU) or container
|
||||
where the new object is created. Required when I(state=present).
|
||||
type: str
|
||||
description:
|
||||
description:
|
||||
- Specifies a description of the object.
|
||||
- This parameter sets the value of the Description property for the object.
|
||||
- The LDAP display name (ldapDisplayName) for this property is description.
|
||||
type: str
|
||||
default: ''
|
||||
dns_hostname:
|
||||
description:
|
||||
- Specifies the fully qualified domain name (FQDN) of the computer.
|
||||
- This parameter sets the DNSHostName property for a computer object.
|
||||
- The LDAP display name for this property is dNSHostName.
|
||||
- Required when I(state=present).
|
||||
type: str
|
||||
domain_username:
|
||||
description:
|
||||
- The username to use when interacting with AD.
|
||||
- If this is not set then the user Ansible used to log in with will be
|
||||
used instead when using CredSSP or Kerberos with credential delegation.
|
||||
type: str
|
||||
version_added: '2.8'
|
||||
domain_password:
|
||||
description:
|
||||
- The password for I(username).
|
||||
type: str
|
||||
version_added: '2.8'
|
||||
domain_server:
|
||||
description:
|
||||
- Specifies the Active Directory Domain Services instance to connect to.
|
||||
- Can be in the form of an FQDN or NetBIOS name.
|
||||
- If not specified then the value is based on the domain of the computer
|
||||
running PowerShell.
|
||||
type: str
|
||||
version_added: '2.8'
|
||||
state:
|
||||
description:
|
||||
- Specified whether the computer should be C(present) or C(absent) in
|
||||
Active Directory.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: win_domain
|
||||
- module: win_domain_controller
|
||||
- module: win_domain_group
|
||||
- module: win_domain_membership
|
||||
- module: win_domain_user
|
||||
author:
|
||||
- Daniel Sánchez Fábregas (@Daniel-Sanchez-Fabregas)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add linux computer to Active Directory OU using a windows machine
|
||||
win_domain_computer:
|
||||
name: one_linux_server.my_org.local
|
||||
sam_account_name: linux_server$
|
||||
dns_hostname: one_linux_server.my_org.local
|
||||
ou: "OU=servers,DC=my_org,DC=local"
|
||||
description: Example of linux server
|
||||
enabled: yes
|
||||
state: present
|
||||
delegate_to: my_windows_bridge.my_org.local
|
||||
|
||||
- name: Remove linux computer from Active Directory using a windows machine
|
||||
win_domain_computer:
|
||||
name: one_linux_server.my_org.local
|
||||
state: absent
|
||||
delegate_to: my_windows_bridge.my_org.local
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
@ -1,344 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Jordan Borean <jborean93@gmail.com>, and others
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$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
|
||||
$display_name = Get-AnsibleParam -obj $params -name "display_name" -type "str"
|
||||
$domain_username = Get-AnsibleParam -obj $params -name "domain_username" -type "str"
|
||||
$domain_password = Get-AnsibleParam -obj $params -name "domain_password" -type "str" -failifempty ($null -ne $domain_username)
|
||||
$description = Get-AnsibleParam -obj $params -name "description" -type "str"
|
||||
$category = Get-AnsibleParam -obj $params -name "category" -type "str" -validateset "distribution","security"
|
||||
$scope = Get-AnsibleParam -obj $params -name "scope" -type "str" -validateset "domainlocal","global","universal"
|
||||
$managed_by = Get-AnsibleParam -obj $params -name "managed_by" -type "str"
|
||||
$attributes = Get-AnsibleParam -obj $params -name "attributes"
|
||||
$organizational_unit = Get-AnsibleParam -obj $params -name "organizational_unit" -type "str" -aliases "ou","path"
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent"
|
||||
$protect = Get-AnsibleParam -obj $params -name "protect" -type "bool"
|
||||
$ignore_protection = Get-AnsibleParam -obj $params -name "ignore_protection" -type "bool" -default $false
|
||||
$domain_server = Get-AnsibleParam -obj $params -name "domain_server" -type "str"
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
created = $false
|
||||
}
|
||||
|
||||
if ($diff_mode) {
|
||||
$result.diff = @{}
|
||||
}
|
||||
|
||||
if (-not (Get-Module -Name ActiveDirectory -ListAvailable)) {
|
||||
Fail-Json $result "win_domain_group requires the ActiveDirectory PS module to be installed"
|
||||
}
|
||||
Import-Module ActiveDirectory
|
||||
|
||||
$extra_args = @{}
|
||||
if ($null -ne $domain_username) {
|
||||
$domain_password = ConvertTo-SecureString $domain_password -AsPlainText -Force
|
||||
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $domain_username, $domain_password
|
||||
$extra_args.Credential = $credential
|
||||
}
|
||||
if ($null -ne $domain_server) {
|
||||
$extra_args.Server = $domain_server
|
||||
}
|
||||
|
||||
try {
|
||||
$group = Get-ADGroup -Identity $name -Properties * @extra_args
|
||||
} catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
|
||||
$group = $null
|
||||
} catch {
|
||||
Fail-Json $result "failed to retrieve initial details for group $($name): $($_.Exception.Message)"
|
||||
}
|
||||
if ($state -eq "absent") {
|
||||
if ($null -ne $group) {
|
||||
if ($group.ProtectedFromAccidentalDeletion -eq $true -and $ignore_protection -eq $true) {
|
||||
$group = $group | Set-ADObject -ProtectedFromAccidentalDeletion $false -WhatIf:$check_mode -PassThru @extra_args
|
||||
} elseif ($group.ProtectedFromAccidentalDeletion -eq $true -and $ignore_protection -eq $false) {
|
||||
Fail-Json $result "cannot delete group $name when ProtectedFromAccidentalDeletion is turned on, run this module with ignore_protection=true to override this"
|
||||
}
|
||||
|
||||
try {
|
||||
$group | Remove-ADGroup -Confirm:$false -WhatIf:$check_mode @extra_args
|
||||
} catch {
|
||||
Fail-Json $result "failed to remove group $($name): $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$result.changed = $true
|
||||
if ($diff_mode) {
|
||||
$result.diff.prepared = "-[$name]"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# validate that path is an actual path
|
||||
if ($null -ne $organizational_unit) {
|
||||
try {
|
||||
Get-ADObject -Identity $organizational_unit @extra_args | Out-Null
|
||||
} catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
|
||||
Fail-Json $result "the group path $organizational_unit does not exist, please specify a valid LDAP path"
|
||||
}
|
||||
}
|
||||
|
||||
$diff_text = $null
|
||||
if ($null -ne $group) {
|
||||
# will be overridden later if no change actually occurs
|
||||
$diff_text += "[$name]`n"
|
||||
|
||||
# change the path of the group
|
||||
if ($null -ne $organizational_unit) {
|
||||
$group_cn = $group.CN
|
||||
$existing_path = $group.DistinguishedName -replace "^CN=$group_cn,",''
|
||||
if ($existing_path -ne $organizational_unit) {
|
||||
$protection_disabled = $false
|
||||
if ($group.ProtectedFromAccidentalDeletion -eq $true -and $ignore_protection -eq $true) {
|
||||
$group | Set-ADObject -ProtectedFromAccidentalDeletion $false -WhatIf:$check_mode -PassThru @extra_args | Out-Null
|
||||
$protection_disabled = $true
|
||||
} elseif ($group.ProtectedFromAccidentalDeletion -eq $true -and $ignore_protection -eq $false) {
|
||||
Fail-Json $result "cannot move group $name when ProtectedFromAccidentalDeletion is turned on, run this module with ignore_protection=true to override this"
|
||||
}
|
||||
|
||||
try {
|
||||
$group = $group | Move-ADObject -Targetpath $organizational_unit -WhatIf:$check_mode -PassThru @extra_args
|
||||
} catch {
|
||||
Fail-Json $result "failed to move group from $existing_path to $($organizational_unit): $($_.Exception.Message)"
|
||||
} finally {
|
||||
if ($protection_disabled -eq $true) {
|
||||
$group | Set-ADObject -ProtectedFromAccidentalDeletion $true -WhatIf:$check_mode -PassThru @extra_args | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
$result.changed = $true
|
||||
$diff_text += "-DistinguishedName = CN=$group_cn,$existing_path`n+DistinguishedName = CN=$group_cn,$organizational_unit`n"
|
||||
|
||||
if ($protection_disabled -eq $true) {
|
||||
$group | Set-ADObject -ProtectedFromAccidentalDeletion $true -WhatIf:$check_mode @extra_args | Out-Null
|
||||
}
|
||||
# get the group again once we have moved it
|
||||
$group = Get-ADGroup -Identity $name -Properties * @extra_args
|
||||
}
|
||||
}
|
||||
|
||||
# change attributes of group
|
||||
$extra_scope_change = $null
|
||||
$run_change = $false
|
||||
$set_args = $extra_args.Clone()
|
||||
|
||||
if ($null -ne $scope) {
|
||||
if ($group.GroupScope -ne $scope) {
|
||||
# you cannot from from Global to DomainLocal and vice-versa, we
|
||||
# need to change it to Universal and then finally to the target
|
||||
# scope
|
||||
if ($group.GroupScope -eq "global" -and $scope -eq "domainlocal") {
|
||||
$set_args.GroupScope = "Universal"
|
||||
$extra_scope_change = $scope
|
||||
} elseif ($group.GroupScope -eq "domainlocal" -and $scope -eq "global") {
|
||||
$set_args.GroupScope = "Universal"
|
||||
$extra_scope_change = $scope
|
||||
} else {
|
||||
$set_args.GroupScope = $scope
|
||||
}
|
||||
$run_change = $true
|
||||
$diff_text += "-GroupScope = $($group.GroupScope)`n+GroupScope = $scope`n"
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -ne $description -and $group.Description -cne $description) {
|
||||
$set_args.Description = $description
|
||||
$run_change = $true
|
||||
$diff_text += "-Description = $($group.Description)`n+Description = $description`n"
|
||||
}
|
||||
|
||||
if ($null -ne $display_name -and $group.DisplayName -cne $display_name) {
|
||||
$set_args.DisplayName = $display_name
|
||||
$run_change = $true
|
||||
$diff_text += "-DisplayName = $($group.DisplayName)`n+DisplayName = $display_name`n"
|
||||
}
|
||||
|
||||
if ($null -ne $category -and $group.GroupCategory -ne $category) {
|
||||
$set_args.GroupCategory = $category
|
||||
$run_change = $true
|
||||
$diff_text += "-GroupCategory = $($group.GroupCategory)`n+GroupCategory = $category`n"
|
||||
}
|
||||
|
||||
if ($null -ne $managed_by) {
|
||||
if ($null -eq $group.ManagedBy) {
|
||||
$set_args.ManagedBy = $managed_by
|
||||
$run_change = $true
|
||||
$diff_text += "+ManagedBy = $managed_by`n"
|
||||
} else {
|
||||
try {
|
||||
$managed_by_object = Get-ADGroup -Identity $managed_by @extra_args
|
||||
} catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
|
||||
try {
|
||||
$managed_by_object = Get-ADUser -Identity $managed_by @extra_args
|
||||
} catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
|
||||
Fail-Json $result "failed to find managed_by user or group $managed_by to be used for comparison"
|
||||
}
|
||||
}
|
||||
|
||||
if ($group.ManagedBy -ne $managed_by_object.DistinguishedName) {
|
||||
$set_args.ManagedBy = $managed_by
|
||||
$run_change = $true
|
||||
$diff_text += "-ManagedBy = $($group.ManagedBy)`n+ManagedBy = $($managed_by_object.DistinguishedName)`n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -ne $attributes) {
|
||||
$add_attributes = @{}
|
||||
$replace_attributes = @{}
|
||||
foreach ($attribute in $attributes.GetEnumerator()) {
|
||||
$attribute_name = $attribute.Name
|
||||
$attribute_value = $attribute.Value
|
||||
|
||||
$valid_property = [bool]($group.PSobject.Properties.name -eq $attribute_name)
|
||||
if ($valid_property) {
|
||||
$existing_value = $group.$attribute_name
|
||||
if ($existing_value -cne $attribute_value) {
|
||||
$replace_attributes.$attribute_name = $attribute_value
|
||||
$diff_text += "-$attribute_name = $existing_value`n+$attribute_name = $attribute_value`n"
|
||||
}
|
||||
} else {
|
||||
$add_attributes.$attribute_name = $attribute_value
|
||||
$diff_text += "+$attribute_name = $attribute_value`n"
|
||||
}
|
||||
}
|
||||
if ($add_attributes.Count -gt 0) {
|
||||
$set_args.Add = $add_attributes
|
||||
$run_change = $true
|
||||
}
|
||||
if ($replace_attributes.Count -gt 0) {
|
||||
$set_args.Replace = $replace_attributes
|
||||
$run_change = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($run_change) {
|
||||
try {
|
||||
$group = $group | Set-ADGroup -WhatIf:$check_mode -PassThru @set_args
|
||||
} catch {
|
||||
Fail-Json $result "failed to change group $($name): $($_.Exception.Message)"
|
||||
}
|
||||
$result.changed = $true
|
||||
|
||||
if ($null -ne $extra_scope_change) {
|
||||
try {
|
||||
$group = $group | Set-ADGroup -GroupScope $extra_scope_change -WhatIf:$check_mode -PassThru @extra_args
|
||||
} catch {
|
||||
Fail-Json $result "failed to change scope of group $name to $($scope): $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# make sure our diff text is null if no change occurred
|
||||
if ($result.changed -eq $false) {
|
||||
$diff_text = $null
|
||||
}
|
||||
} else {
|
||||
# validate if scope is set
|
||||
if ($null -eq $scope) {
|
||||
Fail-Json $result "scope must be set when state=present and the group doesn't exist"
|
||||
}
|
||||
|
||||
$diff_text += "+[$name]`n+Scope = $scope`n"
|
||||
$add_args = $extra_args.Clone()
|
||||
$add_args.Name = $name
|
||||
$add_args.GroupScope = $scope
|
||||
|
||||
if ($null -ne $description) {
|
||||
$add_args.Description = $description
|
||||
$diff_text += "+Description = $description`n"
|
||||
}
|
||||
|
||||
if ($null -ne $display_name) {
|
||||
$add_args.DisplayName = $display_name
|
||||
$diff_text += "+DisplayName = $display_name`n"
|
||||
}
|
||||
|
||||
if ($null -ne $category) {
|
||||
$add_args.GroupCategory = $category
|
||||
$diff_text += "+GroupCategory = $category`n"
|
||||
}
|
||||
|
||||
if ($null -ne $managed_by) {
|
||||
$add_args.ManagedBy = $managed_by
|
||||
$diff_text += "+ManagedBy = $managed_by`n"
|
||||
}
|
||||
|
||||
if ($null -ne $attributes) {
|
||||
$add_args.OtherAttributes = $attributes
|
||||
foreach ($attribute in $attributes.GetEnumerator()) {
|
||||
$diff_text += "+$($attribute.Name) = $($attribute.Value)`n"
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -ne $organizational_unit) {
|
||||
$add_args.Path = $organizational_unit
|
||||
$diff_text += "+Path = $organizational_unit`n"
|
||||
}
|
||||
|
||||
try {
|
||||
$group = New-AdGroup -WhatIf:$check_mode -PassThru @add_args
|
||||
} catch {
|
||||
Fail-Json $result "failed to create group $($name): $($_.Exception.Message)"
|
||||
}
|
||||
$result.changed = $true
|
||||
$result.created = $true
|
||||
}
|
||||
|
||||
# set the protection value
|
||||
if ($null -ne $protect) {
|
||||
if (-not $check_mode) {
|
||||
$group = Get-ADGroup -Identity $name -Properties * @extra_args
|
||||
}
|
||||
$existing_protection_value = $group.ProtectedFromAccidentalDeletion
|
||||
if ($null -eq $existing_protection_value) {
|
||||
$existing_protection_value = $false
|
||||
}
|
||||
if ($existing_protection_value -ne $protect) {
|
||||
$diff_text += @"
|
||||
-ProtectedFromAccidentalDeletion = $existing_protection_value
|
||||
+ProtectedFromAccidentalDeletion = $protect
|
||||
"@
|
||||
|
||||
$group | Set-ADObject -ProtectedFromAccidentalDeletion $protect -WhatIf:$check_mode -PassThru @extra_args
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($diff_mode -and $null -ne $diff_text) {
|
||||
$result.diff.prepared = $diff_text
|
||||
}
|
||||
|
||||
if (-not $check_mode) {
|
||||
$group = Get-ADGroup -Identity $name -Properties * @extra_args
|
||||
$result.sid = $group.SID.Value
|
||||
$result.description = $group.Description
|
||||
$result.distinguished_name = $group.DistinguishedName
|
||||
$result.display_name = $group.DisplayName
|
||||
$result.name = $group.Name
|
||||
$result.canonical_name = $group.CanonicalName
|
||||
$result.guid = $group.ObjectGUID
|
||||
$result.protected_from_accidental_deletion = $group.ProtectedFromAccidentalDeletion
|
||||
$result.managed_by = $group.ManagedBy
|
||||
$result.group_scope = ($group.GroupScope).ToString()
|
||||
$result.category = ($group.GroupCategory).ToString()
|
||||
|
||||
if ($null -ne $attributes) {
|
||||
$result.attributes = @{}
|
||||
foreach ($attribute in $attributes.GetEnumerator()) {
|
||||
$attribute_name = $attribute.Name
|
||||
$result.attributes.$attribute_name = $group.$attribute_name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Exit-Json $result
|
@ -1,242 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_domain_group
|
||||
version_added: '2.4'
|
||||
short_description: Creates, modifies or removes domain groups
|
||||
description:
|
||||
- Creates, modifies or removes groups in Active Directory.
|
||||
- For local groups, use the M(win_group) module instead.
|
||||
options:
|
||||
attributes:
|
||||
description:
|
||||
- A dict of custom LDAP attributes to set on the group.
|
||||
- This can be used to set custom attributes that are not exposed as module
|
||||
parameters, e.g. C(mail).
|
||||
- See the examples on how to format this parameter.
|
||||
type: dict
|
||||
category:
|
||||
description:
|
||||
- The category of the group, this is the value to assign to the LDAP
|
||||
C(groupType) attribute.
|
||||
- If a new group is created then C(security) will be used by default.
|
||||
type: str
|
||||
choices: [ distribution, security ]
|
||||
description:
|
||||
description:
|
||||
- The value to be assigned to the LDAP C(description) attribute.
|
||||
type: str
|
||||
display_name:
|
||||
description:
|
||||
- The value to assign to the LDAP C(displayName) attribute.
|
||||
type: str
|
||||
domain_username:
|
||||
description:
|
||||
- The username to use when interacting with AD.
|
||||
- If this is not set then the user Ansible used to log in with will be
|
||||
used instead.
|
||||
type: str
|
||||
domain_password:
|
||||
description:
|
||||
- The password for C(username).
|
||||
type: str
|
||||
domain_server:
|
||||
description:
|
||||
- Specifies the Active Directory Domain Services instance to connect to.
|
||||
- Can be in the form of an FQDN or NetBIOS name.
|
||||
- If not specified then the value is based on the domain of the computer
|
||||
running PowerShell.
|
||||
type: str
|
||||
version_added: '2.5'
|
||||
ignore_protection:
|
||||
description:
|
||||
- Will ignore the C(ProtectedFromAccidentalDeletion) flag when deleting or
|
||||
moving a group.
|
||||
- The module will fail if one of these actions need to occur and this value
|
||||
is set to C(no).
|
||||
type: bool
|
||||
default: no
|
||||
managed_by:
|
||||
description:
|
||||
- The value to be assigned to the LDAP C(managedBy) attribute.
|
||||
- This value can be in the forms C(Distinguished Name), C(objectGUID),
|
||||
C(objectSid) or C(sAMAccountName), see examples for more details.
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- The name of the group to create, modify or remove.
|
||||
- This value can be in the forms C(Distinguished Name), C(objectGUID),
|
||||
C(objectSid) or C(sAMAccountName), see examples for more details.
|
||||
type: str
|
||||
required: yes
|
||||
organizational_unit:
|
||||
description:
|
||||
- The full LDAP path to create or move the group to.
|
||||
- This should be the path to the parent object to create or move the group to.
|
||||
- See examples for details of how this path is formed.
|
||||
type: str
|
||||
aliases: [ ou, path ]
|
||||
protect:
|
||||
description:
|
||||
- Will set the C(ProtectedFromAccidentalDeletion) flag based on this value.
|
||||
- This flag stops a user from deleting or moving a group to a different
|
||||
path.
|
||||
type: bool
|
||||
scope:
|
||||
description:
|
||||
- The scope of the group.
|
||||
- If C(state=present) and the group doesn't exist then this must be set.
|
||||
type: str
|
||||
choices: [domainlocal, global, universal]
|
||||
state:
|
||||
description:
|
||||
- If C(state=present) this module will ensure the group is created and is
|
||||
configured accordingly.
|
||||
- If C(state=absent) this module will delete the group if it exists
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
notes:
|
||||
- This must be run on a host that has the ActiveDirectory powershell module installed.
|
||||
seealso:
|
||||
- module: win_domain
|
||||
- module: win_domain_controller
|
||||
- module: win_domain_computer
|
||||
- module: win_domain_membership
|
||||
- module: win_domain_user
|
||||
- module: win_group
|
||||
- module: win_group_membership
|
||||
author:
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Ensure the group Cow exists using sAMAccountName
|
||||
win_domain_group:
|
||||
name: Cow
|
||||
scope: global
|
||||
path: OU=groups,DC=ansible,DC=local
|
||||
|
||||
- name: Ensure the group Cow doesn't exist using the Distinguished Name
|
||||
win_domain_group:
|
||||
name: CN=Cow,OU=groups,DC=ansible,DC=local
|
||||
state: absent
|
||||
|
||||
- name: Delete group ignoring the protection flag
|
||||
win_domain_group:
|
||||
name: Cow
|
||||
state: absent
|
||||
ignore_protection: yes
|
||||
|
||||
- name: Create group with delete protection enabled and custom attributes
|
||||
win_domain_group:
|
||||
name: Ansible Users
|
||||
scope: domainlocal
|
||||
category: security
|
||||
attributes:
|
||||
mail: helpdesk@ansible.com
|
||||
wWWHomePage: www.ansible.com
|
||||
ignore_protection: yes
|
||||
|
||||
- name: Change the OU of a group using the SID and ignore the protection flag
|
||||
win_domain_group:
|
||||
name: S-1-5-21-2171456218-3732823212-122182344-1189
|
||||
scope: global
|
||||
organizational_unit: OU=groups,DC=ansible,DC=local
|
||||
ignore_protection: yes
|
||||
|
||||
- name: Add managed_by user
|
||||
win_domain_group:
|
||||
name: Group Name Here
|
||||
managed_by: Domain Admins
|
||||
|
||||
- name: Add group and specify the AD domain services to use for the create
|
||||
win_domain_group:
|
||||
name: Test Group
|
||||
domain_username: user@CORP.ANSIBLE.COM
|
||||
domain_password: Password01!
|
||||
domain_server: corp-DC12.corp.ansible.com
|
||||
scope: domainlocal
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
attributes:
|
||||
description: Custom attributes that were set by the module. This does not
|
||||
show all the custom attributes rather just the ones that were set by the
|
||||
module.
|
||||
returned: group exists and attributes are set on the module invocation
|
||||
type: dict
|
||||
sample:
|
||||
mail: 'helpdesk@ansible.com'
|
||||
wWWHomePage: 'www.ansible.com'
|
||||
canonical_name:
|
||||
description: The canonical name of the group.
|
||||
returned: group exists
|
||||
type: str
|
||||
sample: ansible.local/groups/Cow
|
||||
category:
|
||||
description: The Group type value of the group, i.e. Security or Distribution.
|
||||
returned: group exists
|
||||
type: str
|
||||
sample: Security
|
||||
description:
|
||||
description: The Description of the group.
|
||||
returned: group exists
|
||||
type: str
|
||||
sample: Group Description
|
||||
display_name:
|
||||
description: The Display name of the group.
|
||||
returned: group exists
|
||||
type: str
|
||||
sample: Users who connect through RDP
|
||||
distinguished_name:
|
||||
description: The full Distinguished Name of the group.
|
||||
returned: group exists
|
||||
type: str
|
||||
sample: CN=Cow,OU=groups,DC=ansible,DC=local
|
||||
group_scope:
|
||||
description: The Group scope value of the group.
|
||||
returned: group exists
|
||||
type: str
|
||||
sample: Universal
|
||||
guid:
|
||||
description: The guid of the group.
|
||||
returned: group exists
|
||||
type: str
|
||||
sample: 512a9adb-3fc0-4a26-9df0-e6ea1740cf45
|
||||
managed_by:
|
||||
description: The full Distinguished Name of the AD object that is set on the
|
||||
managedBy attribute.
|
||||
returned: group exists
|
||||
type: str
|
||||
sample: CN=Domain Admins,CN=Users,DC=ansible,DC=local
|
||||
name:
|
||||
description: The name of the group.
|
||||
returned: group exists
|
||||
type: str
|
||||
sample: Cow
|
||||
protected_from_accidental_deletion:
|
||||
description: Whether the group is protected from accidental deletion.
|
||||
returned: group exists
|
||||
type: bool
|
||||
sample: true
|
||||
sid:
|
||||
description: The Security ID of the group.
|
||||
returned: group exists
|
||||
type: str
|
||||
sample: S-1-5-21-2171456218-3732823212-122182344-1189
|
||||
created:
|
||||
description: Whether a group was created
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
'''
|
@ -1,131 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2019, Marius Rieder <marius.rieder@scs.ch>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
try {
|
||||
Import-Module ActiveDirectory
|
||||
}
|
||||
catch {
|
||||
Fail-Json -obj @{} -message "win_domain_group_membership requires the ActiveDirectory PS module to be installed"
|
||||
}
|
||||
|
||||
$params = Parse-Args $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
|
||||
|
||||
# Module control parameters
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent","pure"
|
||||
$domain_username = Get-AnsibleParam -obj $params -name "domain_username" -type "str"
|
||||
$domain_password = Get-AnsibleParam -obj $params -name "domain_password" -type "str" -failifempty ($null -ne $domain_username)
|
||||
$domain_server = Get-AnsibleParam -obj $params -name "domain_server" -type "str"
|
||||
|
||||
# Group Membership parameters
|
||||
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
|
||||
$members = Get-AnsibleParam -obj $params -name "members" -type "list" -failifempty $true
|
||||
|
||||
# Filter ADObjects by ObjectClass
|
||||
$ad_object_class_filter = "(ObjectClass -eq 'user' -or ObjectClass -eq 'group' -or ObjectClass -eq 'computer' -or ObjectClass -eq 'msDS-ManagedServiceAccount')"
|
||||
|
||||
$extra_args = @{}
|
||||
if ($null -ne $domain_username) {
|
||||
$domain_password = ConvertTo-SecureString $domain_password -AsPlainText -Force
|
||||
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $domain_username, $domain_password
|
||||
$extra_args.Credential = $credential
|
||||
}
|
||||
if ($null -ne $domain_server) {
|
||||
$extra_args.Server = $domain_server
|
||||
}
|
||||
|
||||
$ADGroup = Get-ADGroup -Identity $name @extra_args
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
added = [System.Collections.Generic.List`1[String]]@()
|
||||
removed = [System.Collections.Generic.List`1[String]]@()
|
||||
}
|
||||
if ($diff_mode) {
|
||||
$result.diff = @{}
|
||||
}
|
||||
|
||||
$members_before = Get-AdGroupMember -Identity $ADGroup @extra_args
|
||||
$pure_members = [System.Collections.Generic.List`1[String]]@()
|
||||
|
||||
foreach ($member in $members) {
|
||||
$extra_member_args = $extra_args.Clone()
|
||||
if ($member -match "\\"){
|
||||
$extra_member_args.Server = $member.Split("\")[0]
|
||||
$member = $member.Split("\")[1]
|
||||
}
|
||||
$group_member = Get-ADObject -Filter "SamAccountName -eq '$member' -and $ad_object_class_filter" -Properties objectSid, sAMAccountName @extra_member_args
|
||||
if (!$group_member) {
|
||||
Fail-Json -obj $result "Could not find domain user, group, service account or computer named $member"
|
||||
}
|
||||
|
||||
if ($state -eq "pure") {
|
||||
$pure_members.Add($group_member.objectSid)
|
||||
}
|
||||
|
||||
$user_in_group = $false
|
||||
foreach ($current_member in $members_before) {
|
||||
if ($current_member.sid -eq $group_member.objectSid) {
|
||||
$user_in_group = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($state -in @("present", "pure") -and !$user_in_group) {
|
||||
Add-ADPrincipalGroupMembership -Identity $group_member -MemberOf $ADGroup -WhatIf:$check_mode @extra_member_args
|
||||
$result.added.Add($group_member.SamAccountName)
|
||||
$result.changed = $true
|
||||
} elseif ($state -eq "absent" -and $user_in_group) {
|
||||
Remove-ADPrincipalGroupMembership -Identity $group_member -MemberOf $ADGroup -WhatIf:$check_mode -Confirm:$False @extra_member_args
|
||||
$result.removed.Add($group_member.SamAccountName)
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($state -eq "pure") {
|
||||
# Perform removals for existing group members not defined in $members
|
||||
$current_members = Get-AdGroupMember -Identity $ADGroup @extra_args
|
||||
|
||||
foreach ($current_member in $current_members) {
|
||||
$user_to_remove = $true
|
||||
foreach ($pure_member in $pure_members) {
|
||||
if ($pure_member -eq $current_member.sid) {
|
||||
$user_to_remove = $false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($user_to_remove) {
|
||||
Remove-ADPrincipalGroupMembership -Identity $current_member -MemberOf $ADGroup -WhatIf:$check_mode -Confirm:$False
|
||||
$result.removed.Add($current_member.SamAccountName)
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$final_members = Get-AdGroupMember -Identity $ADGroup @extra_args
|
||||
|
||||
if ($final_members) {
|
||||
$result.members = [Array]$final_members.SamAccountName
|
||||
} else {
|
||||
$result.members = @()
|
||||
}
|
||||
|
||||
if ($diff_mode -and $result.changed) {
|
||||
$result.diff.before = $members_before.SamAccountName | Out-String
|
||||
if (!$check_mode) {
|
||||
$result.diff.after = [Array]$final_members.SamAccountName | Out-String
|
||||
} else {
|
||||
$after = [System.Collections.Generic.List`1[String]]$result.members
|
||||
$result.removed | ForEach-Object { $after.Remove($_) > $null }
|
||||
$after.AddRange($result.added)
|
||||
$result.diff.after = $after | Out-String
|
||||
}
|
||||
}
|
||||
|
||||
Exit-Json -obj $result
|
@ -1,130 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Andrew Saraceni <andrew.saraceni@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_domain_group_membership
|
||||
version_added: "2.8"
|
||||
short_description: Manage Windows domain group membership
|
||||
description:
|
||||
- Allows the addition and removal of domain users
|
||||
and domain groups from/to a domain group.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the domain group to manage membership on.
|
||||
type: str
|
||||
required: yes
|
||||
members:
|
||||
description:
|
||||
- A list of members to ensure are present/absent from the group.
|
||||
- The given names must be a SamAccountName of a user, group, service account, or computer.
|
||||
- For computers, you must add "$" after the name; for example, to add "Mycomputer" to a group, use "Mycomputer$" as the member.
|
||||
- If the member object is part of another domain in a multi-domain forest, you must add the domain and "\" in front of the name.
|
||||
type: list
|
||||
required: yes
|
||||
state:
|
||||
description:
|
||||
- Desired state of the members in the group.
|
||||
- When C(state) is C(pure), only the members specified will exist,
|
||||
and all other existing members not specified are removed.
|
||||
type: str
|
||||
choices: [ absent, present, pure ]
|
||||
default: present
|
||||
domain_username:
|
||||
description:
|
||||
- The username to use when interacting with AD.
|
||||
- If this is not set then the user Ansible used to log in with will be
|
||||
used instead when using CredSSP or Kerberos with credential delegation.
|
||||
type: str
|
||||
domain_password:
|
||||
description:
|
||||
- The password for I(username).
|
||||
type: str
|
||||
domain_server:
|
||||
description:
|
||||
- Specifies the Active Directory Domain Services instance to connect to.
|
||||
- Can be in the form of an FQDN or NetBIOS name.
|
||||
- If not specified then the value is based on the domain of the computer
|
||||
running PowerShell.
|
||||
type: str
|
||||
notes:
|
||||
- This must be run on a host that has the ActiveDirectory powershell module installed.
|
||||
seealso:
|
||||
- module: win_domain_user
|
||||
- module: win_domain_group
|
||||
author:
|
||||
- Marius Rieder (@jiuka)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a domain user/group to a domain group
|
||||
win_domain_group_membership:
|
||||
name: Foo
|
||||
members:
|
||||
- Bar
|
||||
state: present
|
||||
|
||||
- name: Remove a domain user/group from a domain group
|
||||
win_domain_group_membership:
|
||||
name: Foo
|
||||
members:
|
||||
- Bar
|
||||
state: absent
|
||||
|
||||
- name: Ensure only a domain user/group exists in a domain group
|
||||
win_domain_group_membership:
|
||||
name: Foo
|
||||
members:
|
||||
- Bar
|
||||
state: pure
|
||||
|
||||
- name: Add a computer to a domain group
|
||||
win_domain_group_membership:
|
||||
name: Foo
|
||||
members:
|
||||
- DESKTOP$
|
||||
state: present
|
||||
|
||||
- name: Add a domain user/group from another Domain in the multi-domain forest to a domain group
|
||||
win_domain_group_membership:
|
||||
domain_server: DomainAAA.cloud
|
||||
name: GroupinDomainAAA
|
||||
members:
|
||||
- DomainBBB.cloud\UserInDomainBBB
|
||||
state: Present
|
||||
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
name:
|
||||
description: The name of the target domain group.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Domain-Admins
|
||||
added:
|
||||
description: A list of members added when C(state) is C(present) or
|
||||
C(pure); this is empty if no members are added.
|
||||
returned: success and C(state) is C(present) or C(pure)
|
||||
type: list
|
||||
sample: ["UserName", "GroupName"]
|
||||
removed:
|
||||
description: A list of members removed when C(state) is C(absent) or
|
||||
C(pure); this is empty if no members are removed.
|
||||
returned: success and C(state) is C(absent) or C(pure)
|
||||
type: list
|
||||
sample: ["UserName", "GroupName"]
|
||||
members:
|
||||
description: A list of all domain group members at completion; this is empty
|
||||
if the group contains no members.
|
||||
returned: success
|
||||
type: list
|
||||
sample: ["UserName", "GroupName"]
|
||||
'''
|
@ -1,271 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#Requires -Module Ansible.ModuleUtils.AddType
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
domain_password = @{ type = 'str'; no_log = $true }
|
||||
domain_server = @{ type = 'str' }
|
||||
domain_username = @{ type = 'str' }
|
||||
filter = @{ type = 'str' }
|
||||
identity = @{ type = 'str' }
|
||||
include_deleted = @{ type = 'bool'; default = $false }
|
||||
ldap_filter = @{ type = 'str' }
|
||||
properties = @{ type = 'list'; elements = 'str' }
|
||||
search_base = @{ type = 'str' }
|
||||
search_scope = @{ type = 'str'; choices = @('base', 'one_level', 'subtree') }
|
||||
}
|
||||
supports_check_mode = $true
|
||||
mutually_exclusive = @(
|
||||
@('filter', 'identity', 'ldap_filter'),
|
||||
@('identity', 'search_base'),
|
||||
@('identity', 'search_scope')
|
||||
)
|
||||
required_one_of = @(
|
||||
,@('filter', 'identity', 'ldap_filter')
|
||||
)
|
||||
required_together = @(,@('domain_username', 'domain_password'))
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$module.Result.objects = @() # Always ensure this is returned even in a failure.
|
||||
|
||||
$domainServer = $module.Params.domain_server
|
||||
$domainPassword = $module.Params.domain_password
|
||||
$domainUsername = $module.Params.domain_username
|
||||
$filter = $module.Params.filter
|
||||
$identity = $module.Params.identity
|
||||
$includeDeleted = $module.Params.include_deleted
|
||||
$ldapFilter = $module.Params.ldap_filter
|
||||
$properties = $module.Params.properties
|
||||
$searchBase = $module.Params.search_base
|
||||
$searchScope = $module.Params.search_scope
|
||||
|
||||
$credential = $null
|
||||
if ($domainUsername) {
|
||||
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @(
|
||||
$domainUsername,
|
||||
(ConvertTo-SecureString -AsPlainText -Force -String $domainPassword)
|
||||
)
|
||||
}
|
||||
|
||||
Add-CSharpType -References @'
|
||||
using System;
|
||||
|
||||
namespace Ansible.WinDomainObjectInfo
|
||||
{
|
||||
[Flags]
|
||||
public enum UserAccountControl : int
|
||||
{
|
||||
ADS_UF_SCRIPT = 0x00000001,
|
||||
ADS_UF_ACCOUNTDISABLE = 0x00000002,
|
||||
ADS_UF_HOMEDIR_REQUIRED = 0x00000008,
|
||||
ADS_UF_LOCKOUT = 0x00000010,
|
||||
ADS_UF_PASSWD_NOTREQD = 0x00000020,
|
||||
ADS_UF_PASSWD_CANT_CHANGE = 0x00000040,
|
||||
ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0x00000080,
|
||||
ADS_UF_TEMP_DUPLICATE_ACCOUNT = 0x00000100,
|
||||
ADS_UF_NORMAL_ACCOUNT = 0x00000200,
|
||||
ADS_UF_INTERDOMAIN_TRUST_ACCOUNT = 0x00000800,
|
||||
ADS_UF_WORKSTATION_TRUST_ACCOUNT = 0x00001000,
|
||||
ADS_UF_SERVER_TRUST_ACCOUNT = 0x00002000,
|
||||
ADS_UF_DONT_EXPIRE_PASSWD = 0x00010000,
|
||||
ADS_UF_MNS_LOGON_ACCOUNT = 0x00020000,
|
||||
ADS_UF_SMARTCARD_REQUIRED = 0x00040000,
|
||||
ADS_UF_TRUSTED_FOR_DELEGATION = 0x00080000,
|
||||
ADS_UF_NOT_DELEGATED = 0x00100000,
|
||||
ADS_UF_USE_DES_KEY_ONLY = 0x00200000,
|
||||
ADS_UF_DONT_REQUIRE_PREAUTH = 0x00400000,
|
||||
ADS_UF_PASSWORD_EXPIRED = 0x00800000,
|
||||
ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x01000000,
|
||||
}
|
||||
|
||||
public enum sAMAccountType : int
|
||||
{
|
||||
SAM_DOMAIN_OBJECT = 0x00000000,
|
||||
SAM_GROUP_OBJECT = 0x10000000,
|
||||
SAM_NON_SECURITY_GROUP_OBJECT = 0x10000001,
|
||||
SAM_ALIAS_OBJECT = 0x20000000,
|
||||
SAM_NON_SECURITY_ALIAS_OBJECT = 0x20000001,
|
||||
SAM_USER_OBJECT = 0x30000000,
|
||||
SAM_NORMAL_USER_ACCOUNT = 0x30000000,
|
||||
SAM_MACHINE_ACCOUNT = 0x30000001,
|
||||
SAM_TRUST_ACCOUNT = 0x30000002,
|
||||
SAM_APP_BASIC_GROUP = 0x40000000,
|
||||
SAM_APP_QUERY_GROUP = 0x40000001,
|
||||
SAM_ACCOUNT_TYPE_MAX = 0x7fffffff,
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
Function ConvertTo-OutputValue {
|
||||
[CmdletBinding()]
|
||||
Param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[AllowNull()]
|
||||
[Object]
|
||||
$InputObject
|
||||
)
|
||||
|
||||
if ($InputObject -is [System.Security.Principal.SecurityIdentifier]) {
|
||||
# Syntax: SID - Only serialize the SID as a string and not the other metadata properties.
|
||||
$sidInfo = @{
|
||||
Sid = $InputObject.Value
|
||||
}
|
||||
|
||||
# Try and map the SID to the account name, this may fail if the SID is invalid or not mappable.
|
||||
try {
|
||||
$sidInfo.Name = $InputObject.Translate([System.Security.Principal.NTAccount]).Value
|
||||
} catch [System.Security.Principal.IdentityNotMappedException] {
|
||||
$sidInfo.Name = $null
|
||||
}
|
||||
|
||||
$sidInfo
|
||||
} elseif ($InputObject -is [Byte[]]) {
|
||||
# Syntax: Octet String - By default will serialize as a list of decimal values per byte, instead return a
|
||||
# Base64 string as Ansible can easily parse that.
|
||||
[System.Convert]::ToBase64String($InputObject)
|
||||
} elseif ($InputObject -is [DateTime]) {
|
||||
# Syntax: UTC Coded Time - .NET DateTimes serialized as in the form "Date(FILETIME)" which isn't easily
|
||||
# parsable by Ansible, instead return as an ISO 8601 string in the UTC timezone.
|
||||
[TimeZoneInfo]::ConvertTimeToUtc($InputObject).ToString("o")
|
||||
} elseif ($InputObject -is [System.Security.AccessControl.ObjectSecurity]) {
|
||||
# Complex object which isn't easily serializable. Instead we should just return the SDDL string. If a user
|
||||
# needs to parse this then they really need to reprocess the SDDL string and process their results on another
|
||||
# win_shell task.
|
||||
$InputObject.GetSecurityDescriptorSddlForm(([System.Security.AccessControl.AccessControlSections]::All))
|
||||
} else {
|
||||
# Syntax: (All Others) - The default serialization handling of other syntaxes are fine, don't do anything.
|
||||
$InputObject
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
Calling Get-ADObject that returns multiple objects with -Properties * will only return the properties that were set on
|
||||
the first found object. To counter this problem we will first call Get-ADObject to list all the objects that match the
|
||||
filter specified then get the properties on each object.
|
||||
#>
|
||||
|
||||
$commonParams = @{
|
||||
IncludeDeletedObjects = $includeDeleted
|
||||
}
|
||||
|
||||
if ($credential) {
|
||||
$commonParams.Credential = $credential
|
||||
}
|
||||
|
||||
if ($domainServer) {
|
||||
$commonParams.Server = $domainServer
|
||||
}
|
||||
|
||||
# First get the IDs for all the AD objects that match the filter specified.
|
||||
$getParams = @{
|
||||
Properties = @('DistinguishedName', 'ObjectGUID')
|
||||
}
|
||||
|
||||
if ($filter) {
|
||||
$getParams.Filter = $filter
|
||||
} elseif ($identity) {
|
||||
$getParams.Identity = $identity
|
||||
} elseif ($ldapFilter) {
|
||||
$getParams.LDAPFilter = $ldapFilter
|
||||
}
|
||||
|
||||
# Explicit check on $null as an empty string is different from not being set.
|
||||
if ($null -ne $searchBase) {
|
||||
$getParams.SearchBase = $searchbase
|
||||
}
|
||||
|
||||
if ($searchScope) {
|
||||
$getParams.SearchScope = switch($searchScope) {
|
||||
base { 'Base' }
|
||||
one_level { 'OneLevel' }
|
||||
subtree { 'Subtree' }
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
# We run this in a custom PowerShell pipeline so that users of this module can't use any of the variables defined
|
||||
# above in their filter. While the cmdlet won't execute sub expressions we don't want anyone implicitly relying on
|
||||
# a defined variable in this module in case we ever change the name or remove it.
|
||||
$ps = [PowerShell]::Create()
|
||||
$null = $ps.AddCommand('Get-ADObject').AddParameters($commonParams).AddParameters($getParams)
|
||||
$null = $ps.AddCommand('Select-Object').AddParameter('Property', @('DistinguishedName', 'ObjectGUID'))
|
||||
|
||||
$foundGuids = @($ps.Invoke())
|
||||
} catch {
|
||||
# Because we ran in a pipeline we can't catch ADIdentityNotFoundException. Instead just get the base exception and
|
||||
# do the error checking on that.
|
||||
if ($_.Exception.GetBaseException() -is [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]) {
|
||||
$foundGuids = @()
|
||||
} else {
|
||||
# The exception is from the .Invoke() call, compare on the InnerException which was what was actually raised by
|
||||
# the pipeline.
|
||||
$innerException = $_.Exception.InnerException.InnerException
|
||||
if ($innerException -is [Microsoft.ActiveDirectory.Management.ADServerDownException]) {
|
||||
# Point users in the direction of the double hop problem as that is what is typically the cause of this.
|
||||
$msg = "Failed to contact the AD server, this could be caused by the double hop problem over WinRM. "
|
||||
$msg += "Try using the module with auth as Kerberos with credential delegation or CredSSP, become, or "
|
||||
$msg += "defining the domain_username and domain_password module parameters."
|
||||
$module.FailJson($msg, $innerException)
|
||||
} else {
|
||||
throw $innerException
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$getParams = @{}
|
||||
if ($properties) {
|
||||
$getParams.Properties = $properties
|
||||
}
|
||||
$module.Result.objects = @(foreach ($adId in $foundGuids) {
|
||||
try {
|
||||
$adObject = Get-ADObject @commonParams @getParams -Identity $adId.ObjectGUID
|
||||
} catch {
|
||||
$msg = "Failed to retrieve properties for AD Object '$($adId.DistinguishedName)': $($_.Exception.Message)"
|
||||
$module.Warn($msg)
|
||||
continue
|
||||
}
|
||||
|
||||
$propertyNames = $adObject.PropertyNames
|
||||
$propertyNames += ($properties | Where-Object { $_ -ne '*' })
|
||||
|
||||
# Now process each property to an easy to represent string
|
||||
$filteredObject = [Ordered]@{}
|
||||
foreach ($name in ($propertyNames | Sort-Object)) {
|
||||
# In the case of explicit properties that were asked for but weren't set, Get-ADObject won't actually return
|
||||
# the property so this is a defensive check against that scenario.
|
||||
if (-not $adObject.PSObject.Properties.Name.Contains($name)) {
|
||||
$filteredObject.$name = $null
|
||||
continue
|
||||
}
|
||||
|
||||
$value = $adObject.$name
|
||||
if ($value -is [Microsoft.ActiveDirectory.Management.ADPropertyValueCollection]) {
|
||||
$value = foreach ($v in $value) {
|
||||
ConvertTo-OutputValue -InputObject $v
|
||||
}
|
||||
} else {
|
||||
$value = ConvertTo-OutputValue -InputObject $value
|
||||
}
|
||||
$filteredObject.$name = $value
|
||||
|
||||
# For these 2 properties, add an _AnsibleFlags attribute which contains the enum strings that are set.
|
||||
if ($name -eq 'sAMAccountType') {
|
||||
$enumValue = [Ansible.WinDomainObjectInfo.sAMAccountType]$value
|
||||
$filteredObject.'sAMAccountType_AnsibleFlags' = $enumValue.ToString() -split ', '
|
||||
} elseif ($name -eq 'userAccountControl') {
|
||||
$enumValue = [Ansible.WinDomainObjectInfo.UserAccountControl]$value
|
||||
$filteredObject.'userAccountControl_AnsibleFlags' = $enumValue.ToString() -split ', '
|
||||
}
|
||||
}
|
||||
|
||||
$filteredObject
|
||||
})
|
||||
|
||||
$module.ExitJson()
|
@ -1,162 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_domain_object_info
|
||||
version_added: '2.10'
|
||||
short_description: Gather information an Active Directory object
|
||||
description:
|
||||
- Gather information about multiple Active Directory object(s).
|
||||
options:
|
||||
domain_password:
|
||||
description:
|
||||
- The password for C(domain_username).
|
||||
type: str
|
||||
domain_server:
|
||||
description:
|
||||
- Specified the Active Directory Domain Services instance to connect to.
|
||||
- Can be in the form of an FQDN or NetBIOS name.
|
||||
- If not specified then the value is based on the default domain of the computer running PowerShell.
|
||||
type: str
|
||||
domain_username:
|
||||
description:
|
||||
- The username to use when interacting with AD.
|
||||
- If this is not set then the user that is used for authentication will be the connection user.
|
||||
- Ansible will be unable to use the connection user unless auth is Kerberos with credential delegation or CredSSP,
|
||||
or become is used on the task.
|
||||
type: str
|
||||
filter:
|
||||
description:
|
||||
- Specifies a query string using the PowerShell Expression Language syntax.
|
||||
- This follows the same rules and formatting as the C(-Filter) parameter for the PowerShell AD cmdlets exception
|
||||
there is no variable substitutions.
|
||||
- This is mutually exclusive with I(identity) and I(ldap_filter).
|
||||
type: str
|
||||
identity:
|
||||
description:
|
||||
- Specifies a single Active Directory object by its distinguished name or its object GUID.
|
||||
- This is mutually exclusive with I(filter) and I(ldap_filter).
|
||||
- This cannot be used with either the I(search_base) or I(search_scope) options.
|
||||
type: str
|
||||
include_deleted:
|
||||
description:
|
||||
- Also search for deleted Active Directory objects.
|
||||
default: no
|
||||
type: bool
|
||||
ldap_filter:
|
||||
description:
|
||||
- Like I(filter) but this is a tradiitional LDAP query string to filter the objects to return.
|
||||
- This is mutually exclusive with I(filter) and I(identity).
|
||||
type: str
|
||||
properties:
|
||||
description:
|
||||
- A list of properties to return.
|
||||
- If a property is C(*), all properties that have a set value on the AD object will be returned.
|
||||
- If a property is valid on the object but not set, it is only returned if defined explicitly in this option list.
|
||||
- The properties C(DistinguishedName), C(Name), C(ObjectClass), and C(ObjectGUID) are always returned.
|
||||
- Specifying multiple properties can have a performance impact, it is best to only return what is needed.
|
||||
- If an invalid property is specified then the module will display a warning for each object it is invalid on.
|
||||
type: list
|
||||
elements: str
|
||||
search_base:
|
||||
description:
|
||||
- Specify the Active Directory path to search for objects in.
|
||||
- This cannot be set with I(identity).
|
||||
- By default the search base is the default naming context of the target AD instance which is the DN returned by
|
||||
"(Get-ADRootDSE).defaultNamingContext".
|
||||
type: str
|
||||
search_scope:
|
||||
description:
|
||||
- Specify the scope of when searching for an object in the C(search_base).
|
||||
- C(base) will limit the search to the base object so the maximum number of objects returned is always one. This
|
||||
will not search any objects inside a container..
|
||||
- C(one_level) will search the current path and any immediate objects in that path.
|
||||
- C(subtree) will search the current path and all objects of that path recursively.
|
||||
- This cannot be set with I(identity).
|
||||
choices:
|
||||
- base
|
||||
- one_level
|
||||
- subtree
|
||||
type: str
|
||||
notes:
|
||||
- The C(sAMAccountType_AnsibleFlags) and C(userAccountControl_AnsibleFlags) return property is something set by the
|
||||
module itself as an easy way to view what those flags represent. These properties cannot be used as part of the
|
||||
I(filter) or I(ldap_filter) and are automatically added if those properties were requested.
|
||||
author:
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Get all properties for the specified account using its DistinguishedName
|
||||
win_domain_object_info:
|
||||
identity: CN=Username,CN=Users,DC=domain,DC=com
|
||||
properties: '*'
|
||||
|
||||
- name: Get the SID for all user accounts as a filter
|
||||
win_domain_object_info:
|
||||
filter: ObjectClass -eq 'user' -and objectCategory -eq 'Person'
|
||||
properties:
|
||||
- objectSid
|
||||
|
||||
- name: Get the SID for all user accounts as a LDAP filter
|
||||
win_domain_object_info:
|
||||
ldap_filter: (&(objectClass=user)(objectCategory=Person))
|
||||
properties:
|
||||
- objectSid
|
||||
|
||||
- name: Search all computer accounts in a specific path that were added after February 1st
|
||||
win_domain_object_info:
|
||||
filter: objectClass -eq 'computer' -and whenCreated -gt '20200201000000.0Z'
|
||||
properties: '*'
|
||||
search_scope: one_level
|
||||
search_base: CN=Computers,DC=domain,DC=com
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
objects:
|
||||
description:
|
||||
- A list of dictionaries that are the Active Directory objects found and the properties requested.
|
||||
- The dict's keys are the property name and the value is the value for the property.
|
||||
- All date properties are return in the ISO 8601 format in the UTC timezone.
|
||||
- All SID properties are returned as a dict with the keys C(Sid) as the SID string and C(Name) as the translated SID
|
||||
account name.
|
||||
- All byte properties are returned as a base64 string.
|
||||
- All security descriptor properties are returned as the SDDL string of that descriptor.
|
||||
- The properties C(DistinguishedName), C(Name), C(ObjectClass), and C(ObjectGUID) are always returned.
|
||||
returned: always
|
||||
type: list
|
||||
elements: dict
|
||||
sample: |
|
||||
[{
|
||||
"accountExpires": 0,
|
||||
"adminCount": 1,
|
||||
"CanonicalName": "domain.com/Users/Administrator",
|
||||
"CN": "Administrator",
|
||||
"Created": "2020-01-13T09:03:22.0000000Z",
|
||||
"Description": "Built-in account for administering computer/domain",
|
||||
"DisplayName": null,
|
||||
"DistinguishedName": "CN=Administrator,CN=Users,DC=domain,DC=com",
|
||||
"memberOf": [
|
||||
"CN=Group Policy Creator Owners,CN=Users,DC=domain,DC=com",
|
||||
"CN=Domain Admins",CN=Users,DC=domain,DC=com"
|
||||
],
|
||||
"Name": "Administrator",
|
||||
"nTSecurityDescriptor": "O:DAG:DAD:PAI(A;;LCRPLORC;;;AU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;SY)(A;;CCDCLCSWRPWPLOCRSDRCWDWO;;;BA)",
|
||||
"ObjectCategory": "CN=Person,CN=Schema,CN=Configuration,DC=domain,DC=com",
|
||||
"ObjectClass": "user",
|
||||
"ObjectGUID": "c8c6569e-4688-4f3c-8462-afc4ff60817b",
|
||||
"objectSid": {
|
||||
"Sid": "S-1-5-21-2959096244-3298113601-420842770-500",
|
||||
"Name": "DOMAIN\Administrator"
|
||||
},
|
||||
"sAMAccountName": "Administrator",
|
||||
}]
|
||||
'''
|
@ -1,384 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#AnsibleRequires -CSharpUtil Ansible.AccessToken
|
||||
|
||||
Function Test-Credential {
|
||||
param(
|
||||
[String]$Username,
|
||||
[String]$Password,
|
||||
[String]$Domain = $null
|
||||
)
|
||||
if (($Username.ToCharArray()) -contains [char]'@') {
|
||||
# UserPrincipalName
|
||||
$Domain = $null # force $Domain to be null, to prevent undefined behaviour, as a domain name is already included in the username
|
||||
} elseif (($Username.ToCharArray()) -contains [char]'\') {
|
||||
# Pre Win2k Account Name
|
||||
$Username = ($Username -split '\')[0]
|
||||
$Domain = ($Username -split '\')[1]
|
||||
} else {
|
||||
# No domain provided, so maybe local user, or domain specified separately.
|
||||
}
|
||||
|
||||
try {
|
||||
$handle = [Ansible.AccessToken.TokenUtil]::LogonUser($Username, $Domain, $Password, "Network", "Default")
|
||||
$handle.Dispose()
|
||||
return $true
|
||||
} catch [Ansible.AccessToken.Win32Exception] {
|
||||
# following errors indicate the creds are correct but the user was
|
||||
# unable to log on for other reasons, which we don't care about
|
||||
$success_codes = @(
|
||||
0x0000052F, # ERROR_ACCOUNT_RESTRICTION
|
||||
0x00000530, # ERROR_INVALID_LOGON_HOURS
|
||||
0x00000531, # ERROR_INVALID_WORKSTATION
|
||||
0x00000569 # ERROR_LOGON_TYPE_GRANTED
|
||||
)
|
||||
$failed_codes = @(
|
||||
0x0000052E, # ERROR_LOGON_FAILURE
|
||||
0x00000532 # ERROR_PASSWORD_EXPIRED
|
||||
)
|
||||
|
||||
if ($_.Exception.NativeErrorCode -in $failed_codes) {
|
||||
return $false
|
||||
} elseif ($_.Exception.NativeErrorCode -in $success_codes) {
|
||||
return $true
|
||||
} else {
|
||||
# an unknown failure, reraise exception
|
||||
throw $_
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Import-Module ActiveDirectory
|
||||
}
|
||||
catch {
|
||||
Fail-Json $result "Failed to import ActiveDirectory PowerShell module. This module should be run on a domain controller, and the ActiveDirectory module must be available."
|
||||
}
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
created = $false
|
||||
password_updated = $false
|
||||
}
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -default $false
|
||||
|
||||
# Module control parameters
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent","query"
|
||||
$update_password = Get-AnsibleParam -obj $params -name "update_password" -type "str" -default "always" -validateset "always","on_create","when_changed"
|
||||
$groups_action = Get-AnsibleParam -obj $params -name "groups_action" -type "str" -default "replace" -validateset "add","remove","replace"
|
||||
$domain_username = Get-AnsibleParam -obj $params -name "domain_username" -type "str"
|
||||
$domain_password = Get-AnsibleParam -obj $params -name "domain_password" -type "str" -failifempty ($null -ne $domain_username)
|
||||
$domain_server = Get-AnsibleParam -obj $params -name "domain_server" -type "str"
|
||||
|
||||
# User account parameters
|
||||
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
|
||||
$identity = Get-AnsibleParam -obj $params -name "identity" -type "str" -default $name
|
||||
$description = Get-AnsibleParam -obj $params -name "description" -type "str"
|
||||
$password = Get-AnsibleParam -obj $params -name "password" -type "str"
|
||||
$password_expired = Get-AnsibleParam -obj $params -name "password_expired" -type "bool"
|
||||
$password_never_expires = Get-AnsibleParam -obj $params -name "password_never_expires" -type "bool"
|
||||
$user_cannot_change_password = Get-AnsibleParam -obj $params -name "user_cannot_change_password" -type "bool"
|
||||
$account_locked = Get-AnsibleParam -obj $params -name "account_locked" -type "bool"
|
||||
$groups = Get-AnsibleParam -obj $params -name "groups" -type "list"
|
||||
$enabled = Get-AnsibleParam -obj $params -name "enabled" -type "bool" -default $true
|
||||
$path = Get-AnsibleParam -obj $params -name "path" -type "str"
|
||||
$upn = Get-AnsibleParam -obj $params -name "upn" -type "str"
|
||||
|
||||
# User informational parameters
|
||||
$user_info = @{
|
||||
GivenName = Get-AnsibleParam -obj $params -name "firstname" -type "str"
|
||||
Surname = Get-AnsibleParam -obj $params -name "surname" -type "str"
|
||||
Company = Get-AnsibleParam -obj $params -name "company" -type "str"
|
||||
EmailAddress = Get-AnsibleParam -obj $params -name "email" -type "str"
|
||||
StreetAddress = Get-AnsibleParam -obj $params -name "street" -type "str"
|
||||
City = Get-AnsibleParam -obj $params -name "city" -type "str"
|
||||
State = Get-AnsibleParam -obj $params -name "state_province" -type "str"
|
||||
PostalCode = Get-AnsibleParam -obj $params -name "postal_code" -type "str"
|
||||
Country = Get-AnsibleParam -obj $params -name "country" -type "str"
|
||||
}
|
||||
|
||||
# Additional attributes
|
||||
$attributes = Get-AnsibleParam -obj $params -name "attributes"
|
||||
|
||||
# Parameter validation
|
||||
If ($null -ne $account_locked -and $account_locked) {
|
||||
Fail-Json $result "account_locked must be set to 'no' if provided"
|
||||
}
|
||||
If (($null -ne $password_expired) -and ($null -ne $password_never_expires)) {
|
||||
Fail-Json $result "password_expired and password_never_expires are mutually exclusive but have both been set"
|
||||
}
|
||||
|
||||
$extra_args = @{}
|
||||
if ($null -ne $domain_username) {
|
||||
$domain_password = ConvertTo-SecureString $domain_password -AsPlainText -Force
|
||||
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $domain_username, $domain_password
|
||||
$extra_args.Credential = $credential
|
||||
}
|
||||
if ($null -ne $domain_server) {
|
||||
$extra_args.Server = $domain_server
|
||||
}
|
||||
|
||||
Function Get-PrincipalGroups {
|
||||
Param ($identity, $args_extra)
|
||||
try{
|
||||
$groups = Get-ADPrincipalGroupMembership -Identity $identity @args_extra -ErrorAction Stop
|
||||
} catch {
|
||||
Add-Warning -obj $result -message "Failed to enumerate user groups but continuing on.: $($_.Exception.Message)"
|
||||
return @()
|
||||
}
|
||||
|
||||
$result_groups = foreach ($group in $groups) {
|
||||
$group.DistinguishedName
|
||||
}
|
||||
return $result_groups
|
||||
}
|
||||
|
||||
try {
|
||||
$user_obj = Get-ADUser -Identity $identity -Properties * @extra_args
|
||||
$user_guid = $user_obj.ObjectGUID
|
||||
}
|
||||
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
|
||||
$user_obj = $null
|
||||
$user_guid = $null
|
||||
}
|
||||
|
||||
If ($state -eq 'present') {
|
||||
# Ensure user exists
|
||||
$new_user = $false
|
||||
|
||||
# If the account does not exist, create it
|
||||
If (-not $user_obj) {
|
||||
$create_args = @{}
|
||||
$create_args.Name = $name
|
||||
If ($null -ne $path){
|
||||
$create_args.Path = $path
|
||||
}
|
||||
If ($null -ne $upn){
|
||||
$create_args.UserPrincipalName = $upn
|
||||
$create_args.SamAccountName = $upn.Split('@')[0]
|
||||
}
|
||||
$user_obj = New-ADUser @create_args -WhatIf:$check_mode -PassThru @extra_args
|
||||
$user_guid = $user_obj.ObjectGUID
|
||||
$new_user = $true
|
||||
$result.created = $true
|
||||
$result.changed = $true
|
||||
If ($check_mode) {
|
||||
Exit-Json $result
|
||||
}
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
}
|
||||
|
||||
If ($password) {
|
||||
# Don't unnecessary check for working credentials.
|
||||
# Set the password if we need to.
|
||||
# For new_users there is also no difference between always and when_changed
|
||||
# so we don't need to differentiate between this two states.
|
||||
If ($new_user -or ($update_password -eq "always")) {
|
||||
$set_new_credentials = $true
|
||||
} elseif ($update_password -eq "when_changed") {
|
||||
$set_new_credentials = -not (Test-Credential -Username $user_obj.UserPrincipalName -Password $password)
|
||||
} else {
|
||||
$set_new_credentials = $false
|
||||
}
|
||||
If ($set_new_credentials) {
|
||||
$secure_password = ConvertTo-SecureString $password -AsPlainText -Force
|
||||
Set-ADAccountPassword -Identity $user_guid -Reset:$true -Confirm:$false -NewPassword $secure_password -WhatIf:$check_mode @extra_args
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.password_updated = $true
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Configure password policies
|
||||
If (($null -ne $password_never_expires) -and ($password_never_expires -ne $user_obj.PasswordNeverExpires)) {
|
||||
Set-ADUser -Identity $user_guid -PasswordNeverExpires $password_never_expires -WhatIf:$check_mode @extra_args
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.changed = $true
|
||||
}
|
||||
If (($null -ne $password_expired) -and ($password_expired -ne $user_obj.PasswordExpired)) {
|
||||
Set-ADUser -Identity $user_guid -ChangePasswordAtLogon $password_expired -WhatIf:$check_mode @extra_args
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.changed = $true
|
||||
}
|
||||
If (($null -ne $user_cannot_change_password) -and ($user_cannot_change_password -ne $user_obj.CannotChangePassword)) {
|
||||
Set-ADUser -Identity $user_guid -CannotChangePassword $user_cannot_change_password -WhatIf:$check_mode @extra_args
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
# Assign other account settings
|
||||
If (($null -ne $upn) -and ($upn -ne $user_obj.UserPrincipalName)) {
|
||||
Set-ADUser -Identity $user_guid -UserPrincipalName $upn -WhatIf:$check_mode @extra_args
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.changed = $true
|
||||
}
|
||||
If (($null -ne $description) -and ($description -ne $user_obj.Description)) {
|
||||
Set-ADUser -Identity $user_guid -description $description -WhatIf:$check_mode @extra_args
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.changed = $true
|
||||
}
|
||||
If ($enabled -ne $user_obj.Enabled) {
|
||||
Set-ADUser -Identity $user_guid -Enabled $enabled -WhatIf:$check_mode @extra_args
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.changed = $true
|
||||
}
|
||||
If ((-not $account_locked) -and ($user_obj.LockedOut -eq $true)) {
|
||||
Unlock-ADAccount -Identity $user_guid -WhatIf:$check_mode @extra_args
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
# Set user information
|
||||
Foreach ($key in $user_info.Keys) {
|
||||
If ($null -eq $user_info[$key]) {
|
||||
continue
|
||||
}
|
||||
$value = $user_info[$key]
|
||||
If ($value -ne $user_obj.$key) {
|
||||
$set_args = $extra_args.Clone()
|
||||
$set_args.$key = $value
|
||||
Set-ADUser -Identity $user_guid -WhatIf:$check_mode @set_args
|
||||
$result.changed = $true
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
}
|
||||
}
|
||||
|
||||
# Set additional attributes
|
||||
$set_args = $extra_args.Clone()
|
||||
$run_change = $false
|
||||
if ($null -ne $attributes) {
|
||||
$add_attributes = @{}
|
||||
$replace_attributes = @{}
|
||||
foreach ($attribute in $attributes.GetEnumerator()) {
|
||||
$attribute_name = $attribute.Name
|
||||
$attribute_value = $attribute.Value
|
||||
|
||||
$valid_property = [bool]($user_obj.PSobject.Properties.name -eq $attribute_name)
|
||||
if ($valid_property) {
|
||||
$existing_value = $user_obj.$attribute_name
|
||||
if ($existing_value -cne $attribute_value) {
|
||||
$replace_attributes.$attribute_name = $attribute_value
|
||||
}
|
||||
} else {
|
||||
$add_attributes.$attribute_name = $attribute_value
|
||||
}
|
||||
}
|
||||
if ($add_attributes.Count -gt 0) {
|
||||
$set_args.Add = $add_attributes
|
||||
$run_change = $true
|
||||
}
|
||||
if ($replace_attributes.Count -gt 0) {
|
||||
$set_args.Replace = $replace_attributes
|
||||
$run_change = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($run_change) {
|
||||
try {
|
||||
$user_obj = $user_obj | Set-ADUser -WhatIf:$check_mode -PassThru @set_args
|
||||
} catch {
|
||||
Fail-Json $result "failed to change user $($name): $($_.Exception.Message)"
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
|
||||
# Configure group assignment
|
||||
If ($null -ne $groups) {
|
||||
$group_list = $groups
|
||||
|
||||
$groups = @()
|
||||
Foreach ($group in $group_list) {
|
||||
$groups += (Get-ADGroup -Identity $group @extra_args).DistinguishedName
|
||||
}
|
||||
|
||||
$assigned_groups = Get-PrincipalGroups $user_guid $extra_args
|
||||
|
||||
switch ($groups_action) {
|
||||
"add" {
|
||||
Foreach ($group in $groups) {
|
||||
If (-not ($assigned_groups -Contains $group)) {
|
||||
Add-ADGroupMember -Identity $group -Members $user_guid -WhatIf:$check_mode @extra_args
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
"remove" {
|
||||
Foreach ($group in $groups) {
|
||||
If ($assigned_groups -Contains $group) {
|
||||
Remove-ADGroupMember -Identity $group -Members $user_guid -Confirm:$false -WhatIf:$check_mode @extra_args
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
"replace" {
|
||||
Foreach ($group in $assigned_groups) {
|
||||
If (($group -ne $user_obj.PrimaryGroup) -and -not ($groups -Contains $group)) {
|
||||
Remove-ADGroupMember -Identity $group -Members $user_guid -Confirm:$false -WhatIf:$check_mode @extra_args
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
Foreach ($group in $groups) {
|
||||
If (-not ($assigned_groups -Contains $group)) {
|
||||
Add-ADGroupMember -Identity $group -Members $user_guid -WhatIf:$check_mode @extra_args
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} ElseIf ($state -eq 'absent') {
|
||||
# Ensure user does not exist
|
||||
If ($user_obj) {
|
||||
Remove-ADUser $user_obj -Confirm:$false -WhatIf:$check_mode @extra_args
|
||||
$result.changed = $true
|
||||
If ($check_mode) {
|
||||
Exit-Json $result
|
||||
}
|
||||
$user_obj = $null
|
||||
}
|
||||
}
|
||||
|
||||
If ($user_obj) {
|
||||
$user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args
|
||||
$result.name = $user_obj.Name
|
||||
$result.firstname = $user_obj.GivenName
|
||||
$result.surname = $user_obj.Surname
|
||||
$result.enabled = $user_obj.Enabled
|
||||
$result.company = $user_obj.Company
|
||||
$result.street = $user_obj.StreetAddress
|
||||
$result.email = $user_obj.EmailAddress
|
||||
$result.city = $user_obj.City
|
||||
$result.state_province = $user_obj.State
|
||||
$result.country = $user_obj.Country
|
||||
$result.postal_code = $user_obj.PostalCode
|
||||
$result.distinguished_name = $user_obj.DistinguishedName
|
||||
$result.description = $user_obj.Description
|
||||
$result.password_expired = $user_obj.PasswordExpired
|
||||
$result.password_never_expires = $user_obj.PasswordNeverExpires
|
||||
$result.user_cannot_change_password = $user_obj.CannotChangePassword
|
||||
$result.account_locked = $user_obj.LockedOut
|
||||
$result.sid = [string]$user_obj.SID
|
||||
$result.upn = $user_obj.UserPrincipalName
|
||||
$result.groups = Get-PrincipalGroups $user_guid $extra_args
|
||||
$result.msg = "User '$name' is present"
|
||||
$result.state = "present"
|
||||
}
|
||||
Else {
|
||||
$result.name = $name
|
||||
$result.msg = "User '$name' is absent"
|
||||
$result.state = "absent"
|
||||
}
|
||||
|
||||
Exit-Json $result
|
@ -1,376 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_domain_user
|
||||
version_added: '2.4'
|
||||
short_description: Manages Windows Active Directory user accounts
|
||||
description:
|
||||
- Manages Windows Active Directory user accounts.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the user to create, remove or modify.
|
||||
type: str
|
||||
required: true
|
||||
identity:
|
||||
description:
|
||||
- Identity parameter used to find the User in the Active Directory.
|
||||
- This value can be in the forms C(Distinguished Name), C(objectGUID),
|
||||
C(objectSid) or C(sAMAccountName).
|
||||
- Default to C(name) if not set.
|
||||
type: str
|
||||
version_added: '2.10'
|
||||
state:
|
||||
description:
|
||||
- When C(present), creates or updates the user account.
|
||||
- When C(absent), removes the user account if it exists.
|
||||
- When C(query), retrieves the user account details without making any changes.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
enabled:
|
||||
description:
|
||||
- C(yes) will enable the user account.
|
||||
- C(no) will disable the account.
|
||||
type: bool
|
||||
default: yes
|
||||
account_locked:
|
||||
description:
|
||||
- C(no) will unlock the user account if locked.
|
||||
- Note that there is not a way to lock an account as an administrator.
|
||||
- Accounts are locked due to user actions; as an admin, you may only unlock a locked account.
|
||||
- If you wish to administratively disable an account, set I(enabled) to C(no).
|
||||
choices: [ no ]
|
||||
description:
|
||||
description:
|
||||
- Description of the user
|
||||
type: str
|
||||
groups:
|
||||
description:
|
||||
- Adds or removes the user from this list of groups,
|
||||
depending on the value of I(groups_action).
|
||||
- To remove all but the Principal Group, set C(groups=<principal group name>) and
|
||||
I(groups_action=replace).
|
||||
- Note that users cannot be removed from their principal group (for example, "Domain Users").
|
||||
type: list
|
||||
groups_action:
|
||||
description:
|
||||
- If C(add), the user is added to each group in I(groups) where not already a member.
|
||||
- If C(remove), the user is removed from each group in I(groups).
|
||||
- If C(replace), the user is added as a member of each group in
|
||||
I(groups) and removed from any other groups.
|
||||
type: str
|
||||
choices: [ add, remove, replace ]
|
||||
default: replace
|
||||
password:
|
||||
description:
|
||||
- Optionally set the user's password to this (plain text) value.
|
||||
- To enable an account - I(enabled) - a password must already be
|
||||
configured on the account, or you must provide a password here.
|
||||
type: str
|
||||
update_password:
|
||||
description:
|
||||
- C(always) will always update passwords.
|
||||
- C(on_create) will only set the password for newly created users.
|
||||
- C(when_changed) will only set the password when changed (added in ansible 2.9).
|
||||
type: str
|
||||
choices: [ always, on_create, when_changed ]
|
||||
default: always
|
||||
password_expired:
|
||||
description:
|
||||
- C(yes) will require the user to change their password at next login.
|
||||
- C(no) will clear the expired password flag.
|
||||
- This is mutually exclusive with I(password_never_expires).
|
||||
type: bool
|
||||
password_never_expires:
|
||||
description:
|
||||
- C(yes) will set the password to never expire.
|
||||
- C(no) will allow the password to expire.
|
||||
- This is mutually exclusive with I(password_expired).
|
||||
type: bool
|
||||
user_cannot_change_password:
|
||||
description:
|
||||
- C(yes) will prevent the user from changing their password.
|
||||
- C(no) will allow the user to change their password.
|
||||
type: bool
|
||||
firstname:
|
||||
description:
|
||||
- Configures the user's first name (given name).
|
||||
type: str
|
||||
surname:
|
||||
description:
|
||||
- Configures the user's last name (surname).
|
||||
type: str
|
||||
company:
|
||||
description:
|
||||
- Configures the user's company name.
|
||||
type: str
|
||||
upn:
|
||||
description:
|
||||
- Configures the User Principal Name (UPN) for the account.
|
||||
- This is not required, but is best practice to configure for modern
|
||||
versions of Active Directory.
|
||||
- The format is C(<username>@<domain>).
|
||||
type: str
|
||||
email:
|
||||
description:
|
||||
- Configures the user's email address.
|
||||
- This is a record in AD and does not do anything to configure any email
|
||||
servers or systems.
|
||||
type: str
|
||||
street:
|
||||
description:
|
||||
- Configures the user's street address.
|
||||
type: str
|
||||
city:
|
||||
description:
|
||||
- Configures the user's city.
|
||||
type: str
|
||||
state_province:
|
||||
description:
|
||||
- Configures the user's state or province.
|
||||
type: str
|
||||
postal_code:
|
||||
description:
|
||||
- Configures the user's postal code / zip code.
|
||||
type: str
|
||||
country:
|
||||
description:
|
||||
- Configures the user's country code.
|
||||
- Note that this is a two-character ISO 3166 code.
|
||||
type: str
|
||||
path:
|
||||
description:
|
||||
- Container or OU for the new user; if you do not specify this, the
|
||||
user will be placed in the default container for users in the domain.
|
||||
- Setting the path is only available when a new user is created;
|
||||
if you specify a path on an existing user, the user's path will not
|
||||
be updated - you must delete (e.g., C(state=absent)) the user and
|
||||
then re-add the user with the appropriate path.
|
||||
type: str
|
||||
attributes:
|
||||
description:
|
||||
- A dict of custom LDAP attributes to set on the user.
|
||||
- This can be used to set custom attributes that are not exposed as module
|
||||
parameters, e.g. C(telephoneNumber).
|
||||
- See the examples on how to format this parameter.
|
||||
type: str
|
||||
version_added: '2.5'
|
||||
domain_username:
|
||||
description:
|
||||
- The username to use when interacting with AD.
|
||||
- If this is not set then the user Ansible used to log in with will be
|
||||
used instead when using CredSSP or Kerberos with credential delegation.
|
||||
type: str
|
||||
version_added: '2.5'
|
||||
domain_password:
|
||||
description:
|
||||
- The password for I(username).
|
||||
type: str
|
||||
version_added: '2.5'
|
||||
domain_server:
|
||||
description:
|
||||
- Specifies the Active Directory Domain Services instance to connect to.
|
||||
- Can be in the form of an FQDN or NetBIOS name.
|
||||
- If not specified then the value is based on the domain of the computer
|
||||
running PowerShell.
|
||||
type: str
|
||||
version_added: '2.5'
|
||||
notes:
|
||||
- Works with Windows 2012R2 and newer.
|
||||
- If running on a server that is not a Domain Controller, credential
|
||||
delegation through CredSSP or Kerberos with delegation must be used or the
|
||||
I(domain_username), I(domain_password) must be set.
|
||||
- Note that some individuals have confirmed successful operation on Windows
|
||||
2008R2 servers with AD and AD Web Services enabled, but this has not
|
||||
received the same degree of testing as Windows 2012R2.
|
||||
seealso:
|
||||
- module: win_domain
|
||||
- module: win_domain_controller
|
||||
- module: win_domain_computer
|
||||
- module: win_domain_group
|
||||
- module: win_domain_membership
|
||||
- module: win_user
|
||||
- module: win_user_profile
|
||||
author:
|
||||
- Nick Chandler (@nwchandler)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Ensure user bob is present with address information
|
||||
win_domain_user:
|
||||
name: bob
|
||||
firstname: Bob
|
||||
surname: Smith
|
||||
company: BobCo
|
||||
password: B0bP4ssw0rd
|
||||
state: present
|
||||
groups:
|
||||
- Domain Admins
|
||||
street: 123 4th St.
|
||||
city: Sometown
|
||||
state_province: IN
|
||||
postal_code: 12345
|
||||
country: US
|
||||
attributes:
|
||||
telephoneNumber: 555-123456
|
||||
|
||||
- name: Ensure user bob is created and use custom credentials to create the user
|
||||
win_domain_user:
|
||||
name: bob
|
||||
firstname: Bob
|
||||
surname: Smith
|
||||
password: B0bP4ssw0rd
|
||||
state: present
|
||||
domain_username: DOMAIN\admin-account
|
||||
domain_password: SomePas2w0rd
|
||||
domain_server: domain@DOMAIN.COM
|
||||
|
||||
- name: Ensure user bob is present in OU ou=test,dc=domain,dc=local
|
||||
win_domain_user:
|
||||
name: bob
|
||||
password: B0bP4ssw0rd
|
||||
state: present
|
||||
path: ou=test,dc=domain,dc=local
|
||||
groups:
|
||||
- Domain Admins
|
||||
|
||||
- name: Ensure user bob is absent
|
||||
win_domain_user:
|
||||
name: bob
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
account_locked:
|
||||
description: true if the account is locked
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
changed:
|
||||
description: true if the account changed during execution
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
city:
|
||||
description: The user city
|
||||
returned: always
|
||||
type: str
|
||||
sample: Indianapolis
|
||||
company:
|
||||
description: The user company
|
||||
returned: always
|
||||
type: str
|
||||
sample: RedHat
|
||||
country:
|
||||
description: The user country
|
||||
returned: always
|
||||
type: str
|
||||
sample: US
|
||||
description:
|
||||
description: A description of the account
|
||||
returned: always
|
||||
type: str
|
||||
sample: Server Administrator
|
||||
distinguished_name:
|
||||
description: DN of the user account
|
||||
returned: always
|
||||
type: str
|
||||
sample: CN=nick,OU=test,DC=domain,DC=local
|
||||
email:
|
||||
description: The user email address
|
||||
returned: always
|
||||
type: str
|
||||
sample: nick@domain.local
|
||||
enabled:
|
||||
description: true if the account is enabled and false if disabled
|
||||
returned: always
|
||||
type: str
|
||||
sample: true
|
||||
firstname:
|
||||
description: The user first name
|
||||
returned: always
|
||||
type: str
|
||||
sample: Nick
|
||||
groups:
|
||||
description: AD Groups to which the account belongs
|
||||
returned: always
|
||||
type: list
|
||||
sample: [ "Domain Admins", "Domain Users" ]
|
||||
msg:
|
||||
description: Summary message of whether the user is present or absent
|
||||
returned: always
|
||||
type: str
|
||||
sample: User nick is present
|
||||
name:
|
||||
description: The username on the account
|
||||
returned: always
|
||||
type: str
|
||||
sample: nick
|
||||
password_expired:
|
||||
description: true if the account password has expired
|
||||
returned: always
|
||||
type: bool
|
||||
sample: false
|
||||
password_updated:
|
||||
description: true if the password changed during this execution
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
postal_code:
|
||||
description: The user postal code
|
||||
returned: always
|
||||
type: str
|
||||
sample: 46033
|
||||
sid:
|
||||
description: The SID of the account
|
||||
returned: always
|
||||
type: str
|
||||
sample: S-1-5-21-2752426336-228313920-2202711348-1175
|
||||
state:
|
||||
description: The state of the user account
|
||||
returned: always
|
||||
type: str
|
||||
sample: present
|
||||
state_province:
|
||||
description: The user state or province
|
||||
returned: always
|
||||
type: str
|
||||
sample: IN
|
||||
street:
|
||||
description: The user street address
|
||||
returned: always
|
||||
type: str
|
||||
sample: 123 4th St.
|
||||
surname:
|
||||
description: The user last name
|
||||
returned: always
|
||||
type: str
|
||||
sample: Doe
|
||||
upn:
|
||||
description: The User Principal Name of the account
|
||||
returned: always
|
||||
type: str
|
||||
sample: nick@domain.local
|
||||
user_cannot_change_password:
|
||||
description: true if the user is not allowed to change password
|
||||
returned: always
|
||||
type: str
|
||||
sample: false
|
||||
created:
|
||||
description: Whether a user was created
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
'''
|
@ -1,61 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2015, Peter Mounce <public@neverrunwithscissors.com>
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#Requires -Module Ansible.ModuleUtils.CommandUtil
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
|
||||
Function Invoke-Ngen($architecture="") {
|
||||
$cmd = "$($env:windir)\Microsoft.NET\Framework$($architecture)\v4.0.30319\ngen.exe"
|
||||
|
||||
if (Test-Path -Path $cmd) {
|
||||
$arguments = "update /force"
|
||||
if ($check_mode) {
|
||||
$ngen_result = @{
|
||||
rc = 0
|
||||
stdout = "check mode output for $cmd $arguments"
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$ngen_result = Run-Command -command "$cmd $arguments"
|
||||
} catch {
|
||||
Fail-Json -obj $result -message "failed to execute '$cmd $arguments': $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
$result."dotnet_ngen$($architecture)_update_exit_code" = $ngen_result.rc
|
||||
$result."dotnet_ngen$($architecture)_update_output" = $ngen_result.stdout
|
||||
|
||||
$arguments = "executeQueuedItems"
|
||||
if ($check_mode) {
|
||||
$executed_queued_items = @{
|
||||
rc = 0
|
||||
stdout = "check mode output for $cmd $arguments"
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$executed_queued_items = Run-Command -command "$cmd $arguments"
|
||||
} catch {
|
||||
Fail-Json -obj $result -message "failed to execute '$cmd $arguments': $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
$result."dotnet_ngen$($architecture)_eqi_exit_code" = $executed_queued_items.rc
|
||||
$result."dotnet_ngen$($architecture)_eqi_output" = $executed_queued_items.stdout
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
Invoke-Ngen
|
||||
Invoke-Ngen -architecture "64"
|
||||
|
||||
Exit-Json -obj $result
|
@ -1,88 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2015, Peter Mounce <public@neverrunwithscissors.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_dotnet_ngen
|
||||
version_added: "2.0"
|
||||
short_description: Runs ngen to recompile DLLs after .NET updates
|
||||
description:
|
||||
- After .NET framework is installed/updated, Windows will probably want to recompile things to optimise for the host.
|
||||
- This happens via scheduled task, usually at some inopportune time.
|
||||
- This module allows you to run this task on your own schedule, so you incur the CPU hit at some more convenient and controlled time.
|
||||
- U(https://docs.microsoft.com/en-us/dotnet/framework/tools/ngen-exe-native-image-generator#native-image-service)
|
||||
- U(http://blogs.msdn.com/b/dotnet/archive/2013/08/06/wondering-why-mscorsvw-exe-has-high-cpu-usage-you-can-speed-it-up.aspx)
|
||||
options: {}
|
||||
notes:
|
||||
- There are in fact two scheduled tasks for ngen but they have no triggers so aren't a problem.
|
||||
- There's no way to test if they've been completed.
|
||||
- The stdout is quite likely to be several megabytes.
|
||||
author:
|
||||
- Peter Mounce (@petemounce)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Run ngen tasks
|
||||
win_dotnet_ngen:
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
dotnet_ngen_update_exit_code:
|
||||
description: The exit code after running the 32-bit ngen.exe update /force
|
||||
command.
|
||||
returned: 32-bit ngen executable exists
|
||||
type: int
|
||||
sample: 0
|
||||
dotnet_ngen_update_output:
|
||||
description: The stdout after running the 32-bit ngen.exe update /force
|
||||
command.
|
||||
returned: 32-bit ngen executable exists
|
||||
type: str
|
||||
sample: sample output
|
||||
dotnet_ngen_eqi_exit_code:
|
||||
description: The exit code after running the 32-bit ngen.exe
|
||||
executeQueuedItems command.
|
||||
returned: 32-bit ngen executable exists
|
||||
type: int
|
||||
sample: 0
|
||||
dotnet_ngen_eqi_output:
|
||||
description: The stdout after running the 32-bit ngen.exe executeQueuedItems
|
||||
command.
|
||||
returned: 32-bit ngen executable exists
|
||||
type: str
|
||||
sample: sample output
|
||||
dotnet_ngen64_update_exit_code:
|
||||
description: The exit code after running the 64-bit ngen.exe update /force
|
||||
command.
|
||||
returned: 64-bit ngen executable exists
|
||||
type: int
|
||||
sample: 0
|
||||
dotnet_ngen64_update_output:
|
||||
description: The stdout after running the 64-bit ngen.exe update /force
|
||||
command.
|
||||
returned: 64-bit ngen executable exists
|
||||
type: str
|
||||
sample: sample output
|
||||
dotnet_ngen64_eqi_exit_code:
|
||||
description: The exit code after running the 64-bit ngen.exe
|
||||
executeQueuedItems command.
|
||||
returned: 64-bit ngen executable exists
|
||||
type: int
|
||||
sample: 0
|
||||
dotnet_ngen64_eqi_output:
|
||||
description: The stdout after running the 64-bit ngen.exe executeQueuedItems
|
||||
command.
|
||||
returned: 64-bit ngen executable exists
|
||||
type: str
|
||||
sample: sample output
|
||||
'''
|
@ -1,287 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Andrew Saraceni <andrew.saraceni@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-EventLogDetail {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get details of an event log, sources, and associated attributes.
|
||||
Used for comparison against passed-in option values to ensure idempotency.
|
||||
#>
|
||||
param(
|
||||
[String]$LogName
|
||||
)
|
||||
|
||||
$log_details = @{}
|
||||
$log_details.name = $LogName
|
||||
$log_details.exists = $false
|
||||
$log = Get-EventLog -List | Where-Object {$_.Log -eq $LogName}
|
||||
|
||||
if ($log) {
|
||||
$log_details.exists = $true
|
||||
$log_details.maximum_size_kb = $log.MaximumKilobytes
|
||||
$log_details.overflow_action = $log.OverflowAction.ToString()
|
||||
$log_details.retention_days = $log.MinimumRetentionDays
|
||||
$log_details.entries = $log.Entries.Count
|
||||
$log_details.sources = [Ordered]@{}
|
||||
|
||||
# Retrieve existing sources and category/message/parameter file locations
|
||||
# Associating file locations and sources with logs can only be done from the registry
|
||||
|
||||
$root_key = "HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\{0}" -f $LogName
|
||||
$log_root = Get-ChildItem -Path $root_key
|
||||
|
||||
foreach ($child in $log_root) {
|
||||
$source_name = $child.PSChildName
|
||||
$log_details.sources.$source_name = @{}
|
||||
$hash_cursor = $log_details.sources.$source_name
|
||||
|
||||
$source_root = "{0}\{1}" -f $root_key, $source_name
|
||||
$resource_files = Get-ItemProperty -Path $source_root
|
||||
|
||||
$hash_cursor.category_file = $resource_files.CategoryMessageFile
|
||||
$hash_cursor.message_file = $resource_files.EventMessageFile
|
||||
$hash_cursor.parameter_file = $resource_files.ParameterMessageFile
|
||||
}
|
||||
}
|
||||
|
||||
return $log_details
|
||||
}
|
||||
|
||||
function Test-SourceExistence {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get information on a source's existence.
|
||||
Examine existence regarding the parent log it belongs to and its expected state.
|
||||
#>
|
||||
param(
|
||||
[String]$LogName,
|
||||
[String]$SourceName,
|
||||
[Switch]$NoLogShouldExist
|
||||
)
|
||||
|
||||
$source_exists = [System.Diagnostics.EventLog]::SourceExists($SourceName)
|
||||
|
||||
if ($source_exists -and $NoLogShouldExist) {
|
||||
Fail-Json -obj $result -message "Source $SourceName already exists and cannot be created"
|
||||
}
|
||||
elseif ($source_exists) {
|
||||
$source_log = [System.Diagnostics.EventLog]::LogNameFromSourceName($SourceName, ".")
|
||||
if ($source_log -ne $LogName) {
|
||||
Fail-Json -obj $result -message "Source $SourceName does not belong to log $LogName and cannot be modified"
|
||||
}
|
||||
}
|
||||
|
||||
return $source_exists
|
||||
}
|
||||
|
||||
function ConvertTo-MaximumSize {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Convert a string KB/MB/GB value to common bytes and KB representations.
|
||||
.NOTES
|
||||
Size must be between 64KB and 4GB and divisible by 64KB, as per the MaximumSize parameter of Limit-EventLog.
|
||||
#>
|
||||
param(
|
||||
[String]$Size
|
||||
)
|
||||
|
||||
$parsed_size = @{
|
||||
bytes = $null
|
||||
KB = $null
|
||||
}
|
||||
|
||||
$size_regex = "^\d+(\.\d+)?(KB|MB|GB)$"
|
||||
if ($Size -notmatch $size_regex) {
|
||||
Fail-Json -obj $result -message "Maximum size $Size is not properly specified"
|
||||
}
|
||||
|
||||
$size_upper = $Size.ToUpper()
|
||||
$size_numeric = [Double]$Size.Substring(0, $Size.Length -2)
|
||||
|
||||
if ($size_upper.EndsWith("GB")) {
|
||||
$size_bytes = $size_numeric * 1GB
|
||||
}
|
||||
elseif ($size_upper.EndsWith("MB")) {
|
||||
$size_bytes = $size_numeric * 1MB
|
||||
}
|
||||
elseif ($size_upper.EndsWith("KB")) {
|
||||
$size_bytes = $size_numeric * 1KB
|
||||
}
|
||||
|
||||
if (($size_bytes -lt 64KB) -or ($size_bytes -ge 4GB)) {
|
||||
Fail-Json -obj $result -message "Maximum size must be between 64KB and 4GB"
|
||||
}
|
||||
elseif (($size_bytes % 64KB) -ne 0) {
|
||||
Fail-Json -obj $result -message "Maximum size must be divisible by 64KB"
|
||||
}
|
||||
|
||||
$parsed_size.bytes = $size_bytes
|
||||
$parsed_size.KB = $size_bytes / 1KB
|
||||
return $parsed_size
|
||||
}
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -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","clear","absent"
|
||||
$sources = Get-AnsibleParam -obj $params -name "sources" -type "list"
|
||||
$category_file = Get-AnsibleParam -obj $params -name "category_file" -type "path"
|
||||
$message_file = Get-AnsibleParam -obj $params -name "message_file" -type "path"
|
||||
$parameter_file = Get-AnsibleParam -obj $params -name "parameter_file" -type "path"
|
||||
$maximum_size = Get-AnsibleParam -obj $params -name "maximum_size" -type "str"
|
||||
$overflow_action = Get-AnsibleParam -obj $params -name "overflow_action" -type "str" -validateset "OverwriteOlder","OverwriteAsNeeded","DoNotOverwrite"
|
||||
$retention_days = Get-AnsibleParam -obj $params -name "retention_days" -type "int"
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
name = $name
|
||||
sources_changed = @()
|
||||
}
|
||||
|
||||
$log_details = Get-EventLogDetail -LogName $name
|
||||
|
||||
# Handle common error cases up front
|
||||
if ($state -eq "present" -and !$log_details.exists -and !$sources) {
|
||||
# When creating a log, one or more sources must be passed
|
||||
Fail-Json -obj $result -message "You must specify one or more sources when creating a log for the first time"
|
||||
}
|
||||
elseif ($state -eq "present" -and $log_details.exists -and $name -in $sources -and ($category_file -or $message_file -or $parameter_file)) {
|
||||
# After a default source of the same name is created, it cannot be modified without removing the log
|
||||
Fail-Json -obj $result -message "Cannot modify default source $name of log $name - you must remove the log"
|
||||
}
|
||||
elseif ($state -eq "clear" -and !$log_details.exists) {
|
||||
Fail-Json -obj $result -message "Cannot clear log $name as it does not exist"
|
||||
}
|
||||
elseif ($state -eq "absent" -and $name -in $sources) {
|
||||
# You also cannot remove a default source for the log - you must remove the log itself
|
||||
Fail-Json -obj $result -message "Cannot remove default source $name from log $name - you must remove the log"
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($state) {
|
||||
"present" {
|
||||
foreach ($source in $sources) {
|
||||
if ($log_details.exists) {
|
||||
$source_exists = Test-SourceExistence -LogName $name -SourceName $source
|
||||
}
|
||||
else {
|
||||
$source_exists = Test-SourceExistence -LogName $name -SourceName $source -NoLogShouldExist
|
||||
}
|
||||
|
||||
if ($source_exists) {
|
||||
$category_change = $category_file -and $log_details.sources.$source.category_file -ne $category_file
|
||||
$message_change = $message_file -and $log_details.sources.$source.message_file -ne $message_file
|
||||
$parameter_change = $parameter_file -and $log_details.sources.$source.parameter_file -ne $parameter_file
|
||||
# Remove source and recreate later if any of the above are true
|
||||
if ($category_change -or $message_change -or $parameter_change) {
|
||||
Remove-EventLog -Source $source -WhatIf:$check_mode
|
||||
}
|
||||
else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
$new_params = @{
|
||||
LogName = $name
|
||||
Source = $source
|
||||
}
|
||||
if ($category_file) {
|
||||
$new_params.CategoryResourceFile = $category_file
|
||||
}
|
||||
if ($message_file) {
|
||||
$new_params.MessageResourceFile = $message_file
|
||||
}
|
||||
if ($parameter_file) {
|
||||
$new_params.ParameterResourceFile = $parameter_file
|
||||
}
|
||||
|
||||
if (!$check_mode) {
|
||||
New-EventLog @new_params
|
||||
$result.sources_changed += $source
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
if ($maximum_size) {
|
||||
$converted_size = ConvertTo-MaximumSize -Size $maximum_size
|
||||
}
|
||||
|
||||
$size_change = $maximum_size -and $log_details.maximum_size_kb -ne $converted_size.KB
|
||||
$overflow_change = $overflow_action -and $log_details.overflow_action -ne $overflow_action
|
||||
$retention_change = $retention_days -and $log_details.retention_days -ne $retention_days
|
||||
|
||||
if ($size_change -or $overflow_change -or $retention_change) {
|
||||
$limit_params = @{
|
||||
LogName = $name
|
||||
WhatIf = $check_mode
|
||||
}
|
||||
if ($maximum_size) {
|
||||
$limit_params.MaximumSize = $converted_size.bytes
|
||||
}
|
||||
if ($overflow_action) {
|
||||
$limit_params.OverflowAction = $overflow_action
|
||||
}
|
||||
if ($retention_days) {
|
||||
$limit_params.RetentionDays = $retention_days
|
||||
}
|
||||
|
||||
Limit-EventLog @limit_params
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
}
|
||||
"clear" {
|
||||
if ($log_details.entries -gt 0) {
|
||||
Clear-EventLog -LogName $name -WhatIf:$check_mode
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
"absent" {
|
||||
if ($sources -and $log_details.exists) {
|
||||
# Since sources were passed, remove sources tied to event log
|
||||
foreach ($source in $sources) {
|
||||
$source_exists = Test-SourceExistence -LogName $name -SourceName $source
|
||||
if ($source_exists) {
|
||||
Remove-EventLog -Source $source -WhatIf:$check_mode
|
||||
if (!$check_mode) {
|
||||
$result.sources_changed += $source
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($log_details.exists) {
|
||||
# Only name passed, so remove event log itself (which also removes contained sources)
|
||||
Remove-EventLog -LogName $name -WhatIf:$check_mode
|
||||
if (!$check_mode) {
|
||||
$log_details.sources.GetEnumerator() | ForEach-Object { $result.sources_changed += $_.Name }
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Fail-Json -obj $result -message $_.Exception.Message
|
||||
}
|
||||
|
||||
$final_log_details = Get-EventLogDetail -LogName $name
|
||||
foreach ($final_log_detail in $final_log_details.GetEnumerator()) {
|
||||
if ($final_log_detail.Name -eq "sources") {
|
||||
$sources = @()
|
||||
$final_log_detail.Value.GetEnumerator() | ForEach-Object { $sources += $_.Name }
|
||||
$result.$($final_log_detail.Name) = [Array]$sources
|
||||
}
|
||||
else {
|
||||
$result.$($final_log_detail.Name) = $final_log_detail.Value
|
||||
}
|
||||
}
|
||||
|
||||
Exit-Json -obj $result
|
@ -1,166 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Andrew Saraceni <andrew.saraceni@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_eventlog
|
||||
version_added: "2.4"
|
||||
short_description: Manage Windows event logs
|
||||
description:
|
||||
- Allows the addition, clearing and removal of local Windows event logs,
|
||||
and the creation and removal of sources from a given event log. Also
|
||||
allows the specification of settings per log and source.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the event log to manage.
|
||||
type: str
|
||||
required: yes
|
||||
state:
|
||||
description:
|
||||
- Desired state of the log and/or sources.
|
||||
- When C(sources) is populated, state is checked for sources.
|
||||
- When C(sources) is not populated, state is checked for the specified log itself.
|
||||
- If C(state) is C(clear), event log entries are cleared for the target log.
|
||||
type: str
|
||||
choices: [ absent, clear, present ]
|
||||
default: present
|
||||
sources:
|
||||
description:
|
||||
- A list of one or more sources to ensure are present/absent in the log.
|
||||
- When C(category_file), C(message_file) and/or C(parameter_file) are specified,
|
||||
these values are applied across all sources.
|
||||
type: list
|
||||
category_file:
|
||||
description:
|
||||
- For one or more sources specified, the path to a custom category resource file.
|
||||
type: path
|
||||
message_file:
|
||||
description:
|
||||
- For one or more sources specified, the path to a custom event message resource file.
|
||||
type: path
|
||||
parameter_file:
|
||||
description:
|
||||
- For one or more sources specified, the path to a custom parameter resource file.
|
||||
type: path
|
||||
maximum_size:
|
||||
description:
|
||||
- The maximum size of the event log.
|
||||
- Value must be between 64KB and 4GB, and divisible by 64KB.
|
||||
- Size can be specified in KB, MB or GB (e.g. 128KB, 16MB, 2.5GB).
|
||||
type: str
|
||||
overflow_action:
|
||||
description:
|
||||
- The action for the log to take once it reaches its maximum size.
|
||||
- For C(DoNotOverwrite), all existing entries are kept and new entries are not retained.
|
||||
- For C(OverwriteAsNeeded), each new entry overwrites the oldest entry.
|
||||
- For C(OverwriteOlder), new log entries overwrite those older than the C(retention_days) value.
|
||||
type: str
|
||||
choices: [ DoNotOverwrite, OverwriteAsNeeded, OverwriteOlder ]
|
||||
retention_days:
|
||||
description:
|
||||
- The minimum number of days event entries must remain in the log.
|
||||
- This option is only used when C(overflow_action) is C(OverwriteOlder).
|
||||
type: int
|
||||
seealso:
|
||||
- module: win_eventlog_entry
|
||||
author:
|
||||
- Andrew Saraceni (@andrewsaraceni)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new event log with two custom sources
|
||||
win_eventlog:
|
||||
name: MyNewLog
|
||||
sources:
|
||||
- NewLogSource1
|
||||
- NewLogSource2
|
||||
state: present
|
||||
|
||||
- name: Change the category and message resource files used for NewLogSource1
|
||||
win_eventlog:
|
||||
name: MyNewLog
|
||||
sources:
|
||||
- NewLogSource1
|
||||
category_file: C:\NewApp\CustomCategories.dll
|
||||
message_file: C:\NewApp\CustomMessages.dll
|
||||
state: present
|
||||
|
||||
- name: Change the maximum size and overflow action for MyNewLog
|
||||
win_eventlog:
|
||||
name: MyNewLog
|
||||
maximum_size: 16MB
|
||||
overflow_action: DoNotOverwrite
|
||||
state: present
|
||||
|
||||
- name: Clear event entries for MyNewLog
|
||||
win_eventlog:
|
||||
name: MyNewLog
|
||||
state: clear
|
||||
|
||||
- name: Remove NewLogSource2 from MyNewLog
|
||||
win_eventlog:
|
||||
name: MyNewLog
|
||||
sources:
|
||||
- NewLogSource2
|
||||
state: absent
|
||||
|
||||
- name: Remove MyNewLog and all remaining sources
|
||||
win_eventlog:
|
||||
name: MyNewLog
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
name:
|
||||
description: The name of the event log.
|
||||
returned: always
|
||||
type: str
|
||||
sample: MyNewLog
|
||||
exists:
|
||||
description: Whether the event log exists or not.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: true
|
||||
entries:
|
||||
description: The count of entries present in the event log.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 50
|
||||
maximum_size_kb:
|
||||
description: Maximum size of the log in KB.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 512
|
||||
overflow_action:
|
||||
description: The action the log takes once it reaches its maximum size.
|
||||
returned: success
|
||||
type: str
|
||||
sample: OverwriteOlder
|
||||
retention_days:
|
||||
description: The minimum number of days entries are retained in the log.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 7
|
||||
sources:
|
||||
description: A list of the current sources for the log.
|
||||
returned: success
|
||||
type: list
|
||||
sample: ["MyNewLog", "NewLogSource1", "NewLogSource2"]
|
||||
sources_changed:
|
||||
description: A list of sources changed (e.g. re/created, removed) for the log;
|
||||
this is empty if no sources are changed.
|
||||
returned: always
|
||||
type: list
|
||||
sample: ["NewLogSource2"]
|
||||
'''
|
@ -1,106 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Andrew Saraceni <andrew.saraceni@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Test-LogExistence {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get information on a log's existence.
|
||||
#>
|
||||
param(
|
||||
[String]$LogName
|
||||
)
|
||||
|
||||
$log_exists = $false
|
||||
$log = Get-EventLog -List | Where-Object {$_.Log -eq $LogName}
|
||||
if ($log) {
|
||||
$log_exists = $true
|
||||
}
|
||||
return $log_exists
|
||||
}
|
||||
|
||||
function Test-SourceExistence {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get information on a source's existence.
|
||||
#>
|
||||
param(
|
||||
[String]$LogName,
|
||||
[String]$SourceName
|
||||
)
|
||||
|
||||
$source_exists = [System.Diagnostics.EventLog]::SourceExists($SourceName)
|
||||
|
||||
if ($source_exists) {
|
||||
$source_log = [System.Diagnostics.EventLog]::LogNameFromSourceName($SourceName, ".")
|
||||
if ($source_log -ne $LogName) {
|
||||
Fail-Json -obj $result -message "Source $SourceName does not belong to log $LogName and cannot be written to"
|
||||
}
|
||||
}
|
||||
|
||||
return $source_exists
|
||||
}
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
|
||||
$log = Get-AnsibleParam -obj $params -name "log" -type "str" -failifempty $true
|
||||
$source = Get-AnsibleParam -obj $params -name "source" -type "str" -failifempty $true
|
||||
$event_id = Get-AnsibleParam -obj $params -name "event_id" -type "int" -failifempty $true
|
||||
$message = Get-AnsibleParam -obj $params -name "message" -type "str" -failifempty $true
|
||||
$entry_type = Get-AnsibleParam -obj $params -name "entry_type" -type "str" -validateset "Error","FailureAudit","Information","SuccessAudit","Warning"
|
||||
$category = Get-AnsibleParam -obj $params -name "category" -type "int"
|
||||
$raw_data = Get-AnsibleParam -obj $params -name "raw_data" -type "str"
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
|
||||
$log_exists = Test-LogExistence -LogName $log
|
||||
if (!$log_exists) {
|
||||
Fail-Json -obj $result -message "Log $log does not exist and cannot be written to"
|
||||
}
|
||||
|
||||
$source_exists = Test-SourceExistence -LogName $log -SourceName $source
|
||||
if (!$source_exists) {
|
||||
Fail-Json -obj $result -message "Source $source does not exist"
|
||||
}
|
||||
|
||||
if ($event_id -lt 0 -or $event_id -gt 65535) {
|
||||
Fail-Json -obj $result -message "Event ID must be between 0 and 65535"
|
||||
}
|
||||
|
||||
$write_params = @{
|
||||
LogName = $log
|
||||
Source = $source
|
||||
EventId = $event_id
|
||||
Message = $message
|
||||
}
|
||||
|
||||
try {
|
||||
if ($entry_type) {
|
||||
$write_params.EntryType = $entry_type
|
||||
}
|
||||
if ($category) {
|
||||
$write_params.Category = $category
|
||||
}
|
||||
if ($raw_data) {
|
||||
$write_params.RawData = [Byte[]]($raw_data -split ",")
|
||||
}
|
||||
|
||||
if (!$check_mode) {
|
||||
Write-EventLog @write_params
|
||||
}
|
||||
$result.changed = $true
|
||||
$result.msg = "Entry added to log $log from source $source"
|
||||
}
|
||||
catch {
|
||||
Fail-Json -obj $result -message $_.Exception.Message
|
||||
}
|
||||
|
||||
Exit-Json -obj $result
|
@ -1,83 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Andrew Saraceni <andrew.saraceni@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_eventlog_entry
|
||||
version_added: "2.4"
|
||||
short_description: Write entries to Windows event logs
|
||||
description:
|
||||
- Write log entries to a given event log from a specified source.
|
||||
options:
|
||||
log:
|
||||
description:
|
||||
- Name of the event log to write an entry to.
|
||||
type: str
|
||||
required: yes
|
||||
source:
|
||||
description:
|
||||
- Name of the log source to indicate where the entry is from.
|
||||
type: str
|
||||
required: yes
|
||||
event_id:
|
||||
description:
|
||||
- The numeric event identifier for the entry.
|
||||
- Value must be between 0 and 65535.
|
||||
type: int
|
||||
required: yes
|
||||
message:
|
||||
description:
|
||||
- The message for the given log entry.
|
||||
type: str
|
||||
required: yes
|
||||
entry_type:
|
||||
description:
|
||||
- Indicates the entry being written to the log is of a specific type.
|
||||
type: str
|
||||
choices: [ Error, FailureAudit, Information, SuccessAudit, Warning ]
|
||||
category:
|
||||
description:
|
||||
- A numeric task category associated with the category message file for the log source.
|
||||
type: int
|
||||
raw_data:
|
||||
description:
|
||||
- Binary data associated with the log entry.
|
||||
- Value must be a comma-separated array of 8-bit unsigned integers (0 to 255).
|
||||
type: str
|
||||
notes:
|
||||
- This module will always report a change when writing an event entry.
|
||||
seealso:
|
||||
- module: win_eventlog
|
||||
author:
|
||||
- Andrew Saraceni (@andrewsaraceni)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Write an entry to a Windows event log
|
||||
win_eventlog_entry:
|
||||
log: MyNewLog
|
||||
source: NewLogSource1
|
||||
event_id: 1234
|
||||
message: This is a test log entry.
|
||||
|
||||
- name: Write another entry to a different Windows event log
|
||||
win_eventlog_entry:
|
||||
log: AnotherLog
|
||||
source: MyAppSource
|
||||
event_id: 5000
|
||||
message: An error has occurred.
|
||||
entry_type: Error
|
||||
category: 5
|
||||
raw_data: 10,20
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
# Default return values
|
||||
'''
|
@ -1,118 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2019, Micah Hunsberger
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
|
||||
Set-StrictMode -Version 2
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
path = @{ type = 'path'; required = $true }
|
||||
state = @{ type = 'str'; default = 'present'; choices = 'absent', 'present' }
|
||||
recurse = @{ type = 'bool'; default = $false }
|
||||
force = @{ type = 'bool'; default = $true }
|
||||
}
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$path = $module.Params.path
|
||||
$state = $module.Params.state
|
||||
$recurse = $module.Params.recurse
|
||||
$force = $module.Params.force
|
||||
|
||||
$module.Result.rc = 0
|
||||
|
||||
if(-not (Test-Path -LiteralPath $path)) {
|
||||
$module.FailJson("Path to item, $path, does not exist.")
|
||||
}
|
||||
|
||||
$item = Get-Item -LiteralPath $path -Force # Use -Force for hidden files
|
||||
if (-not $item.PSIsContainer -and $recurse) {
|
||||
$module.Warn("The recurse option has no effect when path is not a folder.")
|
||||
}
|
||||
|
||||
$cim_params = @{
|
||||
ClassName = 'Win32_LogicalDisk'
|
||||
Filter = "DeviceId='$($item.PSDrive.Name):'"
|
||||
Property = @('FileSystem', 'SupportsFileBasedCompression')
|
||||
}
|
||||
$drive_info = Get-CimInstance @cim_params
|
||||
if ($drive_info.SupportsFileBasedCompression -eq $false) {
|
||||
$module.FailJson("Path, $path, is not on a filesystemi '$($drive_info.FileSystem)' that supports file based compression.")
|
||||
}
|
||||
|
||||
function Get-ReturnCodeMessage {
|
||||
param(
|
||||
[int]$code
|
||||
)
|
||||
switch ($code) {
|
||||
0 { return "The request was successful." }
|
||||
2 { return "Access was denied." }
|
||||
8 { return "An unspecified failure occurred." }
|
||||
9 { return "The name specified was not valid." }
|
||||
10 { return "The object specified already exists." }
|
||||
11 { return "The file system is not NTFS." }
|
||||
12 { return "The platform is not Windows." }
|
||||
13 { return "The drive is not the same." }
|
||||
14 { return "The directory is not empty." }
|
||||
15 { return "There has been a sharing violation." }
|
||||
16 { return "The start file specified was not valid." }
|
||||
17 { return "A privilege required for the operation is not held." }
|
||||
21 { return "A parameter specified is not valid." }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-EscapedFileName {
|
||||
param(
|
||||
[string]$FullName
|
||||
)
|
||||
return $FullName.Replace("\","\\").Replace("'","\'")
|
||||
}
|
||||
|
||||
$is_compressed = ($item.Attributes -band [System.IO.FileAttributes]::Compressed) -eq [System.IO.FileAttributes]::Compressed
|
||||
$needs_changed = $is_compressed -ne ($state -eq 'present')
|
||||
|
||||
if($force -and $recurse -and $item.PSIsContainer) {
|
||||
if (-not $needs_changed) {
|
||||
# Check the subfolders and files
|
||||
$entries_to_check = $item.EnumerateFileSystemInfos("*", [System.IO.SearchOption]::AllDirectories)
|
||||
foreach ($entry in $entries_to_check) {
|
||||
$is_compressed = ($entry.Attributes -band [System.IO.FileAttributes]::Compressed) -eq [System.IO.FileAttributes]::Compressed
|
||||
if ($is_compressed -ne ($state -eq 'present')) {
|
||||
$needs_changed = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($needs_changed) {
|
||||
$module.Result.changed = $true
|
||||
if ($item.PSIsContainer) {
|
||||
$cim_obj = Get-CimInstance -ClassName 'Win32_Directory' -Filter "Name='$(Get-EscapedFileName -FullName $item.FullName)'"
|
||||
} else {
|
||||
$cim_obj = Get-CimInstance -ClassName 'CIM_LogicalFile' -Filter "Name='$(Get-EscapedFileName -FullName $item.FullName)'"
|
||||
}
|
||||
if($state -eq 'present') {
|
||||
if(-not $module.CheckMode) {
|
||||
$ret = Invoke-CimMethod -InputObject $cim_obj -MethodName 'CompressEx' -Arguments @{ Recursive = $recurse }
|
||||
$module.Result.rc = $ret.ReturnValue
|
||||
}
|
||||
} else {
|
||||
if(-not $module.CheckMode) {
|
||||
$ret = $ret = Invoke-CimMethod -InputObject $cim_obj -MethodName 'UnCompressEx' -Arguments @{ Recursive = $recurse }
|
||||
$module.Result.rc = $ret.ReturnValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$module.Result.msg = Get-ReturnCodeMessage -code $module.Result.rc
|
||||
if($module.Result.rc -ne 0) {
|
||||
$module.FailJson($module.Result.msg)
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
@ -1,100 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Micah Hunsberger (@mhunsber)
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_file_compression
|
||||
version_added: '2.10'
|
||||
short_description: Alters the compression of files and directories on NTFS partitions.
|
||||
description:
|
||||
- This module sets the compressed attribute for files and directories on a filesystem that supports it like NTFS.
|
||||
- NTFS compression can be used to save disk space.
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- The full path of the file or directory to modify.
|
||||
- The path must exist on file system that supports compression like NTFS.
|
||||
required: yes
|
||||
type: path
|
||||
state:
|
||||
description:
|
||||
- Set to C(present) to ensure the I(path) is compressed.
|
||||
- Set to C(absent) to ensure the I(path) is not compressed.
|
||||
type: str
|
||||
choices:
|
||||
- absent
|
||||
- present
|
||||
default: present
|
||||
recurse:
|
||||
description:
|
||||
- Whether to recursively apply changes to all subdirectories and files.
|
||||
- This option only has an effect when I(path) is a directory.
|
||||
- When set to C(false), only applies changes to I(path).
|
||||
- When set to C(true), applies changes to I(path) and all subdirectories and files.
|
||||
type: bool
|
||||
default: false
|
||||
force:
|
||||
description:
|
||||
- This option only has an effect when I(recurse) is C(true)
|
||||
- If C(true), will check the compressed state of all subdirectories and files
|
||||
and make a change if any are different from I(compressed).
|
||||
- If C(false), will only make a change if the compressed state of I(path) is different from I(compressed).
|
||||
- If the folder structure is complex or contains a lot of files, it is recommended to set this
|
||||
option to C(false) so that not every file has to be checked.
|
||||
type: bool
|
||||
default: true
|
||||
author:
|
||||
- Micah Hunsberger (@mhunsber)
|
||||
notes:
|
||||
- C(win_file_compression) sets the file system's compression state, it does not create a zip archive file.
|
||||
- For more about NTFS Compression, see U(http://www.ntfs.com/ntfs-compressed.htm)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Compress log files directory
|
||||
win_file_compression:
|
||||
path: C:\Logs
|
||||
state: present
|
||||
|
||||
- name: Decompress log files directory
|
||||
win_file_compression:
|
||||
path: C:\Logs
|
||||
state: absent
|
||||
|
||||
- name: Compress reports directory and all subdirectories
|
||||
win_file_compression:
|
||||
path: C:\business\reports
|
||||
state: present
|
||||
recurse: yes
|
||||
|
||||
# This will only check C:\business\reports for the compressed state
|
||||
# If C:\business\reports is compressed, it will not make a change
|
||||
# even if one of the child items is uncompressed
|
||||
|
||||
- name: Compress reports directory and all subdirectories (quick)
|
||||
win_file_compression:
|
||||
path: C:\business\reports
|
||||
compressed: yes
|
||||
recurse: yes
|
||||
force: no
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
rc:
|
||||
description:
|
||||
- The return code of the compress/uncompress operation.
|
||||
- If no changes are made or the operation is successful, rc is 0.
|
||||
returned: always
|
||||
sample: 0
|
||||
type: int
|
||||
'''
|
@ -1,63 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2015, Sam Liu <sam.liu@activenetwork.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
|
||||
$result = @{
|
||||
win_file_version = @{}
|
||||
changed = $false
|
||||
}
|
||||
|
||||
$path = Get-AnsibleParam -obj $params -name "path" -type "path" -failifempty $true -resultobj $result
|
||||
|
||||
If (-Not (Test-Path -Path $path -PathType Leaf)){
|
||||
Fail-Json $result "Specified path $path does not exist or is not a file."
|
||||
}
|
||||
$ext = [System.IO.Path]::GetExtension($path)
|
||||
If ( $ext -notin '.exe', '.dll'){
|
||||
Fail-Json $result "Specified path $path is not a valid file type; must be DLL or EXE."
|
||||
}
|
||||
|
||||
Try {
|
||||
$_version_fields = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($path)
|
||||
$file_version = $_version_fields.FileVersion
|
||||
If ($null -eq $file_version){
|
||||
$file_version = ''
|
||||
}
|
||||
$product_version = $_version_fields.ProductVersion
|
||||
If ($null -eq $product_version){
|
||||
$product_version= ''
|
||||
}
|
||||
$file_major_part = $_version_fields.FileMajorPart
|
||||
If ($null -eq $file_major_part){
|
||||
$file_major_part= ''
|
||||
}
|
||||
$file_minor_part = $_version_fields.FileMinorPart
|
||||
If ($null -eq $file_minor_part){
|
||||
$file_minor_part= ''
|
||||
}
|
||||
$file_build_part = $_version_fields.FileBuildPart
|
||||
If ($null -eq $file_build_part){
|
||||
$file_build_part = ''
|
||||
}
|
||||
$file_private_part = $_version_fields.FilePrivatePart
|
||||
If ($null -eq $file_private_part){
|
||||
$file_private_part = ''
|
||||
}
|
||||
}
|
||||
Catch{
|
||||
Fail-Json $result "Error: $_.Exception.Message"
|
||||
}
|
||||
|
||||
$result.win_file_version.path = $path.toString()
|
||||
$result.win_file_version.file_version = $file_version.toString()
|
||||
$result.win_file_version.product_version = $product_version.toString()
|
||||
$result.win_file_version.file_major_part = $file_major_part.toString()
|
||||
$result.win_file_version.file_minor_part = $file_minor_part.toString()
|
||||
$result.win_file_version.file_build_part = $file_build_part.toString()
|
||||
$result.win_file_version.file_private_part = $file_private_part.toString()
|
||||
Exit-Json $result;
|
@ -1,78 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2015, Sam Liu <sam.liu@activenetwork.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_file_version
|
||||
version_added: "2.1"
|
||||
short_description: Get DLL or EXE file build version
|
||||
description:
|
||||
- Get DLL or EXE file build version.
|
||||
notes:
|
||||
- This module will always return no change.
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- File to get version.
|
||||
- Always provide absolute path.
|
||||
type: path
|
||||
required: yes
|
||||
seealso:
|
||||
- module: win_file
|
||||
author:
|
||||
- Sam Liu (@SamLiu79)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Get acm instance version
|
||||
win_file_version:
|
||||
path: C:\Windows\System32\cmd.exe
|
||||
register: exe_file_version
|
||||
|
||||
- debug:
|
||||
msg: '{{ exe_file_version }}'
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
path:
|
||||
description: file path
|
||||
returned: always
|
||||
type: str
|
||||
|
||||
file_version:
|
||||
description: File version number..
|
||||
returned: no error
|
||||
type: str
|
||||
|
||||
product_version:
|
||||
description: The version of the product this file is distributed with.
|
||||
returned: no error
|
||||
type: str
|
||||
|
||||
file_major_part:
|
||||
description: the major part of the version number.
|
||||
returned: no error
|
||||
type: str
|
||||
|
||||
file_minor_part:
|
||||
description: the minor part of the version number of the file.
|
||||
returned: no error
|
||||
type: str
|
||||
|
||||
file_build_part:
|
||||
description: build number of the file.
|
||||
returned: no error
|
||||
type: str
|
||||
|
||||
file_private_part:
|
||||
description: file private part number.
|
||||
returned: no error
|
||||
type: str
|
||||
'''
|
@ -1,68 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Michael Eaton <meaton@iforium.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$firewall_profiles = @('Domain', 'Private', 'Public')
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
|
||||
$profiles = Get-AnsibleParam -obj $params -name "profiles" -type "list" -default @("Domain", "Private", "Public")
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -failifempty $true -validateset 'disabled','enabled'
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
profiles = $profiles
|
||||
state = $state
|
||||
}
|
||||
|
||||
try {
|
||||
get-command Get-NetFirewallProfile > $null
|
||||
get-command Set-NetFirewallProfile > $null
|
||||
}
|
||||
catch {
|
||||
Fail-Json $result "win_firewall requires Get-NetFirewallProfile and Set-NetFirewallProfile Cmdlets."
|
||||
}
|
||||
|
||||
Try {
|
||||
|
||||
ForEach ($profile in $firewall_profiles) {
|
||||
|
||||
$currentstate = (Get-NetFirewallProfile -Name $profile).Enabled
|
||||
$result.$profile = @{
|
||||
enabled = ($currentstate -eq 1)
|
||||
considered = ($profiles -contains $profile)
|
||||
currentstate = $currentstate
|
||||
}
|
||||
|
||||
if ($profiles -notcontains $profile) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($state -eq 'enabled') {
|
||||
|
||||
if ($currentstate -eq $false) {
|
||||
Set-NetFirewallProfile -name $profile -Enabled true -WhatIf:$check_mode
|
||||
$result.changed = $true
|
||||
$result.$profile.enabled = $true
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if ($currentstate -eq $true) {
|
||||
Set-NetFirewallProfile -name $profile -Enabled false -WhatIf:$check_mode
|
||||
$result.changed = $true
|
||||
$result.$profile.enabled = $false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} Catch {
|
||||
Fail-Json $result "an error occurred when attempting to change firewall status for profile $profile $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
Exit-Json $result
|
@ -1,75 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Michael Eaton <meaton@iforium.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_firewall
|
||||
version_added: '2.4'
|
||||
short_description: Enable or disable the Windows Firewall
|
||||
description:
|
||||
- Enable or Disable Windows Firewall profiles.
|
||||
requirements:
|
||||
- This module requires Windows Management Framework 5 or later.
|
||||
options:
|
||||
profiles:
|
||||
description:
|
||||
- Specify one or more profiles to change.
|
||||
type: list
|
||||
choices: [ Domain, Private, Public ]
|
||||
default: [ Domain, Private, Public ]
|
||||
state:
|
||||
description:
|
||||
- Set state of firewall for given profile.
|
||||
type: str
|
||||
choices: [ disabled, enabled ]
|
||||
seealso:
|
||||
- module: win_firewall_rule
|
||||
author:
|
||||
- Michael Eaton (@michaeldeaton)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Enable firewall for Domain, Public and Private profiles
|
||||
win_firewall:
|
||||
state: enabled
|
||||
profiles:
|
||||
- Domain
|
||||
- Private
|
||||
- Public
|
||||
tags: enable_firewall
|
||||
|
||||
- name: Disable Domain firewall
|
||||
win_firewall:
|
||||
state: disabled
|
||||
profiles:
|
||||
- Domain
|
||||
tags: disable_firewall
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
enabled:
|
||||
description: Current firewall status for chosen profile (after any potential change).
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
profiles:
|
||||
description: Chosen profile.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Domain
|
||||
state:
|
||||
description: Desired state of the given firewall profile(s).
|
||||
returned: always
|
||||
type: list
|
||||
sample: enabled
|
||||
'''
|
@ -1,257 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2014, Timothy Vandenbrande <timothy.vandenbrande@gmail.com>
|
||||
# Copyright: (c) 2017, Artem Zinenko <zinenkoartem@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
function Parse-ProtocolType {
|
||||
param($protocol)
|
||||
|
||||
$protocolNumber = $protocol -as [int]
|
||||
if ($protocolNumber -is [int]) {
|
||||
return $protocolNumber
|
||||
}
|
||||
|
||||
switch -wildcard ($protocol) {
|
||||
"tcp" { return [System.Net.Sockets.ProtocolType]::Tcp -as [int] }
|
||||
"udp" { return [System.Net.Sockets.ProtocolType]::Udp -as [int] }
|
||||
"icmpv4*" { return [System.Net.Sockets.ProtocolType]::Icmp -as [int] }
|
||||
"icmpv6*" { return [System.Net.Sockets.ProtocolType]::IcmpV6 -as [int] }
|
||||
default { throw "Unknown protocol '$protocol'." }
|
||||
}
|
||||
}
|
||||
|
||||
# See 'Direction' constants here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364724(v=vs.85).aspx
|
||||
function Parse-Direction {
|
||||
param($directionStr)
|
||||
|
||||
switch ($directionStr) {
|
||||
"in" { return 1 }
|
||||
"out" { return 2 }
|
||||
default { throw "Unknown direction '$directionStr'." }
|
||||
}
|
||||
}
|
||||
|
||||
# See 'Action' constants here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364724(v=vs.85).aspx
|
||||
function Parse-Action {
|
||||
param($actionStr)
|
||||
|
||||
switch ($actionStr) {
|
||||
"block" { return 0 }
|
||||
"allow" { return 1 }
|
||||
default { throw "Unknown action '$actionStr'." }
|
||||
}
|
||||
}
|
||||
|
||||
# Profile enum values: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366303(v=vs.85).aspx
|
||||
function Parse-Profiles
|
||||
{
|
||||
param($profilesList)
|
||||
|
||||
$profiles = ($profilesList | Select-Object -Unique | ForEach-Object {
|
||||
switch ($_) {
|
||||
"domain" { return 1 }
|
||||
"private" { return 2 }
|
||||
"public" { return 4 }
|
||||
default { throw "Unknown profile '$_'." }
|
||||
}
|
||||
} | Measure-Object -Sum).Sum
|
||||
|
||||
if ($profiles -eq 7) { return 0x7fffffff }
|
||||
return $profiles
|
||||
}
|
||||
|
||||
function Parse-InterfaceTypes
|
||||
{
|
||||
param($interfaceTypes)
|
||||
|
||||
return ($interfaceTypes | Select-Object -Unique | ForEach-Object {
|
||||
switch ($_) {
|
||||
"wireless" { return "Wireless" }
|
||||
"lan" { return "Lan" }
|
||||
"ras" { return "RemoteAccess" }
|
||||
default { throw "Unknown interface type '$_'." }
|
||||
}
|
||||
}) -Join ","
|
||||
}
|
||||
|
||||
function Parse-EdgeTraversalOptions
|
||||
{
|
||||
param($edgeTraversalOptionsStr)
|
||||
|
||||
switch ($edgeTraversalOptionsStr) {
|
||||
"yes" { return 1 }
|
||||
"deferapp" { return 2 }
|
||||
"deferuser" { return 3 }
|
||||
default { throw "Unknown edge traversal options '$edgeTraversalOptionsStr'." }
|
||||
}
|
||||
}
|
||||
|
||||
function Parse-SecureFlags
|
||||
{
|
||||
param($secureFlagsStr)
|
||||
|
||||
switch ($secureFlagsStr) {
|
||||
"authnoencap" { return 1 }
|
||||
"authenticate" { return 2 }
|
||||
"authdynenc" { return 3 }
|
||||
"authenc" { return 4 }
|
||||
default { throw "Unknown secure flags '$secureFlagsStr'." }
|
||||
}
|
||||
}
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
$diff_support = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false
|
||||
|
||||
$name = Get-AnsibleParam -obj $params -name "name" -failifempty $true
|
||||
$description = Get-AnsibleParam -obj $params -name "description" -type "str"
|
||||
$direction = Get-AnsibleParam -obj $params -name "direction" -type "str" -validateset "in","out"
|
||||
$action = Get-AnsibleParam -obj $params -name "action" -type "str" -validateset "allow","block"
|
||||
$program = Get-AnsibleParam -obj $params -name "program" -type "str"
|
||||
$group = Get-AnsibleParam -obj $params -name "group" -type "str"
|
||||
$service = Get-AnsibleParam -obj $params -name "service" -type "str"
|
||||
$enabled = Get-AnsibleParam -obj $params -name "enabled" -type "bool" -aliases "enable"
|
||||
$profiles = Get-AnsibleParam -obj $params -name "profiles" -type "list" -aliases "profile"
|
||||
$localip = Get-AnsibleParam -obj $params -name "localip" -type "str"
|
||||
$remoteip = Get-AnsibleParam -obj $params -name "remoteip" -type "str"
|
||||
$localport = Get-AnsibleParam -obj $params -name "localport" -type "str"
|
||||
$remoteport = Get-AnsibleParam -obj $params -name "remoteport" -type "str"
|
||||
$protocol = Get-AnsibleParam -obj $params -name "protocol" -type "str"
|
||||
$interfacetypes = Get-AnsibleParam -obj $params -name "interfacetypes" -type "list"
|
||||
$edge = Get-AnsibleParam -obj $params -name "edge" -type "str" -validateset "no","yes","deferapp","deferuser"
|
||||
$security = Get-AnsibleParam -obj $params -name "security" -type "str" -validateset "notrequired","authnoencap","authenticate","authdynenc","authenc"
|
||||
$icmp_type_code = Get-AnsibleParam -obj $params -name "icmp_type_code" -type "list"
|
||||
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent"
|
||||
|
||||
if ($diff_support) {
|
||||
$result.diff = @{}
|
||||
$result.diff.prepared = ""
|
||||
}
|
||||
|
||||
if ($null -ne $icmp_type_code) {
|
||||
# COM representation is just "<type>:<code>,<type2>:<code>" so we just join our list
|
||||
$icmp_type_code = $icmp_type_code -join ","
|
||||
}
|
||||
|
||||
try {
|
||||
$fw = New-Object -ComObject HNetCfg.FwPolicy2
|
||||
|
||||
$existingRule = $fw.Rules | Where-Object { $_.Name -eq $name }
|
||||
|
||||
if ($existingRule -is [System.Array]) {
|
||||
Fail-Json $result "Multiple firewall rules with name '$name' found."
|
||||
}
|
||||
|
||||
# INetFwRule interface description: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365344(v=vs.85).aspx
|
||||
$new_rule = New-Object -ComObject HNetCfg.FWRule
|
||||
$new_rule.Name = $name
|
||||
# the default for enabled in module description is "true", but the actual COM object defaults to "false" when created
|
||||
if ($null -ne $enabled) { $new_rule.Enabled = $enabled } else { $new_rule.Enabled = $true }
|
||||
if ($null -ne $description) { $new_rule.Description = $description }
|
||||
if ($null -ne $group) { $new_rule.Grouping = $group }
|
||||
if ($null -ne $program -and $program -ne "any") { $new_rule.ApplicationName = [System.Environment]::ExpandEnvironmentVariables($program) }
|
||||
if ($null -ne $service -and $program -ne "any") { $new_rule.ServiceName = $service }
|
||||
if ($null -ne $protocol -and $protocol -ne "any") { $new_rule.Protocol = Parse-ProtocolType -protocol $protocol }
|
||||
if ($null -ne $localport -and $localport -ne "any") { $new_rule.LocalPorts = $localport }
|
||||
if ($null -ne $remoteport -and $remoteport -ne "any") { $new_rule.RemotePorts = $remoteport }
|
||||
if ($null -ne $localip -and $localip -ne "any") { $new_rule.LocalAddresses = $localip }
|
||||
if ($null -ne $remoteip -and $remoteip -ne "any") { $new_rule.RemoteAddresses = $remoteip }
|
||||
if ($null -ne $icmp_type_code -and $icmp_type_code -ne "any") { $new_rule.IcmpTypesAndCodes = $icmp_type_code }
|
||||
if ($null -ne $direction) { $new_rule.Direction = Parse-Direction -directionStr $direction }
|
||||
if ($null -ne $action) { $new_rule.Action = Parse-Action -actionStr $action }
|
||||
# Profiles value cannot be a uint32, but the "all profiles" value (0x7FFFFFFF) will often become a uint32, so must cast to [int]
|
||||
if ($null -ne $profiles) { $new_rule.Profiles = [int](Parse-Profiles -profilesList $profiles) }
|
||||
if ($null -ne $interfacetypes -and @(Compare-Object -ReferenceObject $interfacetypes -DifferenceObject @("any")).Count -ne 0) { $new_rule.InterfaceTypes = Parse-InterfaceTypes -interfaceTypes $interfacetypes }
|
||||
if ($null -ne $edge -and $edge -ne "no") {
|
||||
# EdgeTraversalOptions property exists only from Windows 7/Windows Server 2008 R2: https://msdn.microsoft.com/en-us/library/windows/desktop/dd607256(v=vs.85).aspx
|
||||
if ($new_rule | Get-Member -Name 'EdgeTraversalOptions') {
|
||||
$new_rule.EdgeTraversalOptions = Parse-EdgeTraversalOptions -edgeTraversalOptionsStr $edge
|
||||
}
|
||||
}
|
||||
if ($null -ne $security -and $security -ne "notrequired") {
|
||||
# SecureFlags property exists only from Windows 8/Windows Server 2012: https://msdn.microsoft.com/en-us/library/windows/desktop/hh447465(v=vs.85).aspx
|
||||
if ($new_rule | Get-Member -Name 'SecureFlags') {
|
||||
$new_rule.SecureFlags = Parse-SecureFlags -secureFlagsStr $security
|
||||
}
|
||||
}
|
||||
|
||||
$fwPropertiesToCompare = @('Name','Description','Direction','Action','ApplicationName','Grouping','ServiceName','Enabled','Profiles','LocalAddresses','RemoteAddresses','LocalPorts','RemotePorts','Protocol','InterfaceTypes', 'EdgeTraversalOptions', 'SecureFlags','IcmpTypesAndCodes')
|
||||
$userPassedArguments = @($name, $description, $direction, $action, $program, $group, $service, $enabled, $profiles, $localip, $remoteip, $localport, $remoteport, $protocol, $interfacetypes, $edge, $security, $icmp_type_code)
|
||||
|
||||
if ($state -eq "absent") {
|
||||
if ($null -eq $existingRule) {
|
||||
$result.msg = "Firewall rule '$name' does not exist."
|
||||
} else {
|
||||
if ($diff_support) {
|
||||
foreach ($prop in $fwPropertiesToCompare) {
|
||||
$result.diff.prepared += "-[$($prop)='$($existingRule.$prop)']`n"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $check_mode) {
|
||||
$fw.Rules.Remove($existingRule.Name)
|
||||
}
|
||||
$result.changed = $true
|
||||
$result.msg = "Firewall rule '$name' removed."
|
||||
}
|
||||
} elseif ($state -eq "present") {
|
||||
if ($null -eq $existingRule) {
|
||||
if ($diff_support) {
|
||||
foreach ($prop in $fwPropertiesToCompare) {
|
||||
$result.diff.prepared += "+[$($prop)='$($new_rule.$prop)']`n"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $check_mode) {
|
||||
$fw.Rules.Add($new_rule)
|
||||
}
|
||||
$result.changed = $true
|
||||
$result.msg = "Firewall rule '$name' created."
|
||||
} else {
|
||||
for($i = 0; $i -lt $fwPropertiesToCompare.Length; $i++) {
|
||||
$prop = $fwPropertiesToCompare[$i]
|
||||
if($null -ne $userPassedArguments[$i]) { # only change values the user passes in task definition
|
||||
if ($existingRule.$prop -ne $new_rule.$prop) {
|
||||
if ($diff_support) {
|
||||
$result.diff.prepared += "-[$($prop)='$($existingRule.$prop)']`n"
|
||||
$result.diff.prepared += "+[$($prop)='$($new_rule.$prop)']`n"
|
||||
}
|
||||
|
||||
if (-not $check_mode) {
|
||||
# Profiles value cannot be a uint32, but the "all profiles" value (0x7FFFFFFF) will often become a uint32, so must cast to [int]
|
||||
# to prevent InvalidCastException under PS5+
|
||||
If($prop -eq 'Profiles') {
|
||||
$existingRule.Profiles = [int] $new_rule.$prop
|
||||
}
|
||||
Else {
|
||||
$existingRule.$prop = $new_rule.$prop
|
||||
}
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($result.changed) {
|
||||
$result.msg = "Firewall rule '$name' changed."
|
||||
} else {
|
||||
$result.msg = "Firewall rule '$name' already exists."
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch [Exception] {
|
||||
$ex = $_
|
||||
$result['exception'] = $($ex | Out-String)
|
||||
Fail-Json $result $ex.Exception.Message
|
||||
}
|
||||
|
||||
Exit-Json $result
|
@ -1,192 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2014, Timothy Vandenbrande <timothy.vandenbrande@gmail.com>
|
||||
# Copyright: (c) 2017, Artem Zinenko <zinenkoartem@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_firewall_rule
|
||||
version_added: "2.0"
|
||||
short_description: Windows firewall automation
|
||||
description:
|
||||
- Allows you to create/remove/update firewall rules.
|
||||
options:
|
||||
enabled:
|
||||
description:
|
||||
- Whether this firewall rule is enabled or disabled.
|
||||
- Defaults to C(true) when creating a new rule.
|
||||
type: bool
|
||||
aliases: [ enable ]
|
||||
state:
|
||||
description:
|
||||
- Should this rule be added or removed.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
name:
|
||||
description:
|
||||
- The rule's display name.
|
||||
type: str
|
||||
required: yes
|
||||
group:
|
||||
description:
|
||||
- The group name for the rule.
|
||||
version_added: '2.9'
|
||||
type: str
|
||||
direction:
|
||||
description:
|
||||
- Whether this rule is for inbound or outbound traffic.
|
||||
- Defaults to C(in) when creating a new rule.
|
||||
type: str
|
||||
choices: [ in, out ]
|
||||
action:
|
||||
description:
|
||||
- What to do with the items this rule is for.
|
||||
- Defaults to C(allow) when creating a new rule.
|
||||
type: str
|
||||
choices: [ allow, block ]
|
||||
description:
|
||||
description:
|
||||
- Description for the firewall rule.
|
||||
type: str
|
||||
localip:
|
||||
description:
|
||||
- The local ip address this rule applies to.
|
||||
- Set to C(any) to apply to all local ip addresses.
|
||||
- Defaults to C(any) when creating a new rule.
|
||||
type: str
|
||||
remoteip:
|
||||
description:
|
||||
- The remote ip address/range this rule applies to.
|
||||
- Set to C(any) to apply to all remote ip addresses.
|
||||
- Defaults to C(any) when creating a new rule.
|
||||
type: str
|
||||
localport:
|
||||
description:
|
||||
- The local port this rule applies to.
|
||||
- Set to C(any) to apply to all local ports.
|
||||
- Defaults to C(any) when creating a new rule.
|
||||
- Must have I(protocol) set
|
||||
type: str
|
||||
remoteport:
|
||||
description:
|
||||
- The remote port this rule applies to.
|
||||
- Set to C(any) to apply to all remote ports.
|
||||
- Defaults to C(any) when creating a new rule.
|
||||
- Must have I(protocol) set
|
||||
type: str
|
||||
program:
|
||||
description:
|
||||
- The program this rule applies to.
|
||||
- Set to C(any) to apply to all programs.
|
||||
- Defaults to C(any) when creating a new rule.
|
||||
type: str
|
||||
service:
|
||||
description:
|
||||
- The service this rule applies to.
|
||||
- Set to C(any) to apply to all services.
|
||||
- Defaults to C(any) when creating a new rule.
|
||||
type: str
|
||||
protocol:
|
||||
description:
|
||||
- The protocol this rule applies to.
|
||||
- Set to C(any) to apply to all services.
|
||||
- Defaults to C(any) when creating a new rule.
|
||||
type: str
|
||||
profiles:
|
||||
description:
|
||||
- The profile this rule applies to.
|
||||
- Defaults to C(domain,private,public) when creating a new rule.
|
||||
type: list
|
||||
aliases: [ profile ]
|
||||
icmp_type_code:
|
||||
description:
|
||||
- The ICMP types and codes for the rule.
|
||||
- This is only valid when I(protocol) is C(icmpv4) or C(icmpv6).
|
||||
- Each entry follows the format C(type:code) where C(type) is the type
|
||||
number and C(code) is the code number for that type or C(*) for all
|
||||
codes.
|
||||
- Set the value to just C(*) to apply the rule for all ICMP type codes.
|
||||
- See U(https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml)
|
||||
for a list of ICMP types and the codes that apply to them.
|
||||
type: list
|
||||
version_added: '2.10'
|
||||
seealso:
|
||||
- module: win_firewall
|
||||
author:
|
||||
- Artem Zinenko (@ar7z1)
|
||||
- Timothy Vandenbrande (@TimothyVandenbrande)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Firewall rule to allow SMTP on TCP port 25
|
||||
win_firewall_rule:
|
||||
name: SMTP
|
||||
localport: 25
|
||||
action: allow
|
||||
direction: in
|
||||
protocol: tcp
|
||||
state: present
|
||||
enabled: yes
|
||||
|
||||
- name: Firewall rule to allow RDP on TCP port 3389
|
||||
win_firewall_rule:
|
||||
name: Remote Desktop
|
||||
localport: 3389
|
||||
action: allow
|
||||
direction: in
|
||||
protocol: tcp
|
||||
profiles: private
|
||||
state: present
|
||||
enabled: yes
|
||||
|
||||
- name: Firewall rule to be created for application group
|
||||
win_firewall_rule:
|
||||
name: SMTP
|
||||
group: application
|
||||
localport: 25
|
||||
action: allow
|
||||
direction: in
|
||||
protocol: tcp
|
||||
state: present
|
||||
enabled: yes
|
||||
|
||||
- name: Firewall rule to allow port range
|
||||
win_firewall_rule:
|
||||
name: Sample port range
|
||||
localport: 5000-5010
|
||||
action: allow
|
||||
direction: in
|
||||
protocol: tcp
|
||||
state: present
|
||||
enabled: yes
|
||||
|
||||
- name: Firewall rule to allow ICMP v4 echo (ping)
|
||||
win_firewall_rule:
|
||||
name: ICMP Allow incoming V4 echo request
|
||||
enabled: yes
|
||||
state: present
|
||||
profiles: private
|
||||
action: allow
|
||||
direction: in
|
||||
protocol: icmpv4
|
||||
icmp_type_code:
|
||||
- '8:*'
|
||||
|
||||
- name: Firewall rule to alloc ICMP v4 on all type codes
|
||||
win_firewall_rule:
|
||||
name: ICMP Allow incoming V4 echo request
|
||||
enabled: yes
|
||||
state: present
|
||||
profiles: private
|
||||
action: allow
|
||||
direction: in
|
||||
protocol: icmpv4
|
||||
icmp_type_code: '*'
|
||||
'''
|
@ -1,200 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2019, Varun Chopra (@chopraaa) <v@chopraaa.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#AnsibleRequires -OSVersion 6.2
|
||||
|
||||
Set-StrictMode -Version 2
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
drive_letter = @{ type = "str" }
|
||||
path = @{ type = "str" }
|
||||
label = @{ type = "str" }
|
||||
new_label = @{ type = "str" }
|
||||
file_system = @{ type = "str"; choices = "ntfs", "refs", "exfat", "fat32", "fat" }
|
||||
allocation_unit_size = @{ type = "int" }
|
||||
large_frs = @{ type = "bool" }
|
||||
full = @{ type = "bool"; default = $false }
|
||||
compress = @{ type = "bool" }
|
||||
integrity_streams = @{ type = "bool" }
|
||||
force = @{ type = "bool"; default = $false }
|
||||
}
|
||||
mutually_exclusive = @(
|
||||
,@('drive_letter', 'path', 'label')
|
||||
)
|
||||
required_one_of = @(
|
||||
,@('drive_letter', 'path', 'label')
|
||||
)
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$drive_letter = $module.Params.drive_letter
|
||||
$path = $module.Params.path
|
||||
$label = $module.Params.label
|
||||
$new_label = $module.Params.new_label
|
||||
$file_system = $module.Params.file_system
|
||||
$allocation_unit_size = $module.Params.allocation_unit_size
|
||||
$large_frs = $module.Params.large_frs
|
||||
$full_format = $module.Params.full
|
||||
$compress_volume = $module.Params.compress
|
||||
$integrity_streams = $module.Params.integrity_streams
|
||||
$force_format = $module.Params.force
|
||||
|
||||
# Some pre-checks
|
||||
if ($null -ne $drive_letter -and $drive_letter -notmatch "^[a-zA-Z]$") {
|
||||
$module.FailJson("The parameter drive_letter should be a single character A-Z")
|
||||
}
|
||||
if ($integrity_streams -eq $true -and $file_system -ne "refs") {
|
||||
$module.FailJson("Integrity streams can be enabled only on ReFS volumes. You specified: $($file_system)")
|
||||
}
|
||||
if ($compress_volume -eq $true) {
|
||||
if ($file_system -eq "ntfs") {
|
||||
if ($null -ne $allocation_unit_size -and $allocation_unit_size -gt 4096) {
|
||||
$module.FailJson("NTFS compression is not supported for allocation unit sizes above 4096")
|
||||
}
|
||||
}
|
||||
else {
|
||||
$module.FailJson("Compression can be enabled only on NTFS volumes. You specified: $($file_system)")
|
||||
}
|
||||
}
|
||||
|
||||
function Get-AnsibleVolume {
|
||||
param(
|
||||
$DriveLetter,
|
||||
$Path,
|
||||
$Label
|
||||
)
|
||||
|
||||
if ($null -ne $DriveLetter) {
|
||||
try {
|
||||
$volume = Get-Volume -DriveLetter $DriveLetter
|
||||
} catch {
|
||||
$module.FailJson("There was an error retrieving the volume using drive_letter $($DriveLetter): $($_.Exception.Message)", $_)
|
||||
}
|
||||
}
|
||||
elseif ($null -ne $Path) {
|
||||
try {
|
||||
$volume = Get-Volume -Path $Path
|
||||
} catch {
|
||||
$module.FailJson("There was an error retrieving the volume using path $($Path): $($_.Exception.Message)", $_)
|
||||
}
|
||||
}
|
||||
elseif ($null -ne $Label) {
|
||||
try {
|
||||
$volume = Get-Volume -FileSystemLabel $Label
|
||||
} catch {
|
||||
$module.FailJson("There was an error retrieving the volume using label $($Label): $($_.Exception.Message)", $_)
|
||||
}
|
||||
}
|
||||
else {
|
||||
$module.FailJson("Unable to locate volume: drive_letter, path and label were not specified")
|
||||
}
|
||||
|
||||
return $volume
|
||||
}
|
||||
|
||||
function Format-AnsibleVolume {
|
||||
param(
|
||||
$Path,
|
||||
$Label,
|
||||
$FileSystem,
|
||||
$Full,
|
||||
$UseLargeFRS,
|
||||
$Compress,
|
||||
$SetIntegrityStreams,
|
||||
$AllocationUnitSize
|
||||
)
|
||||
$parameters = @{
|
||||
Path = $Path
|
||||
Full = $Full
|
||||
}
|
||||
if ($null -ne $UseLargeFRS) {
|
||||
$parameters.Add("UseLargeFRS", $UseLargeFRS)
|
||||
}
|
||||
if ($null -ne $SetIntegrityStreams) {
|
||||
$parameters.Add("SetIntegrityStreams", $SetIntegrityStreams)
|
||||
}
|
||||
if ($null -ne $Compress){
|
||||
$parameters.Add("Compress", $Compress)
|
||||
}
|
||||
if ($null -ne $Label) {
|
||||
$parameters.Add("NewFileSystemLabel", $Label)
|
||||
}
|
||||
if ($null -ne $FileSystem) {
|
||||
$parameters.Add("FileSystem", $FileSystem)
|
||||
}
|
||||
if ($null -ne $AllocationUnitSize) {
|
||||
$parameters.Add("AllocationUnitSize", $AllocationUnitSize)
|
||||
}
|
||||
|
||||
Format-Volume @parameters -Confirm:$false | Out-Null
|
||||
|
||||
}
|
||||
|
||||
$ansible_volume = Get-AnsibleVolume -DriveLetter $drive_letter -Path $path -Label $label
|
||||
$ansible_file_system = $ansible_volume.FileSystem
|
||||
$ansible_volume_size = $ansible_volume.Size
|
||||
$ansible_volume_alu = (Get-CimInstance -ClassName Win32_Volume -Filter "DeviceId = '$($ansible_volume.path.replace('\','\\'))'" -Property BlockSize).BlockSize
|
||||
|
||||
$ansible_partition = Get-Partition -Volume $ansible_volume
|
||||
|
||||
if (-not $force_format -and $null -ne $allocation_unit_size -and $ansible_volume_alu -ne 0 -and $null -ne $ansible_volume_alu -and $allocation_unit_size -ne $ansible_volume_alu) {
|
||||
$module.FailJson("Force format must be specified since target allocation unit size: $($allocation_unit_size) is different from the current allocation unit size of the volume: $($ansible_volume_alu)")
|
||||
}
|
||||
|
||||
foreach ($access_path in $ansible_partition.AccessPaths) {
|
||||
if ($access_path -ne $Path) {
|
||||
if ($null -ne $file_system -and
|
||||
-not [string]::IsNullOrEmpty($ansible_file_system) -and
|
||||
$file_system -ne $ansible_file_system)
|
||||
{
|
||||
if (-not $force_format)
|
||||
{
|
||||
$no_files_in_volume = (Get-ChildItem -LiteralPath $access_path -ErrorAction SilentlyContinue | Measure-Object).Count -eq 0
|
||||
if($no_files_in_volume)
|
||||
{
|
||||
$module.FailJson("Force format must be specified since target file system: $($file_system) is different from the current file system of the volume: $($ansible_file_system.ToLower())")
|
||||
}
|
||||
else
|
||||
{
|
||||
$module.FailJson("Force format must be specified to format non-pristine volumes")
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$pristine = -not $force_format
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($force_format) {
|
||||
if (-not $module.CheckMode) {
|
||||
Format-AnsibleVolume -Path $ansible_volume.Path -Full $full_format -Label $new_label -FileSystem $file_system -SetIntegrityStreams $integrity_streams -UseLargeFRS $large_frs -Compress $compress_volume -AllocationUnitSize $allocation_unit_size
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
else {
|
||||
if ($pristine) {
|
||||
if ($null -eq $new_label) {
|
||||
$new_label = $ansible_volume.FileSystemLabel
|
||||
}
|
||||
# Conditions for formatting
|
||||
if ($ansible_volume_size -eq 0 -or
|
||||
$ansible_volume.FileSystemLabel -ne $new_label) {
|
||||
if (-not $module.CheckMode) {
|
||||
Format-AnsibleVolume -Path $ansible_volume.Path -Full $full_format -Label $new_label -FileSystem $file_system -SetIntegrityStreams $integrity_streams -UseLargeFRS $large_frs -Compress $compress_volume -AllocationUnitSize $allocation_unit_size
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
@ -1,103 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Varun Chopra (@chopraaa) <v@chopraaa.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
module: win_format
|
||||
version_added: '2.8'
|
||||
short_description: Formats an existing volume or a new volume on an existing partition on Windows
|
||||
description:
|
||||
- The M(win_format) module formats an existing volume or a new volume on an existing partition on Windows
|
||||
options:
|
||||
drive_letter:
|
||||
description:
|
||||
- Used to specify the drive letter of the volume to be formatted.
|
||||
type: str
|
||||
path:
|
||||
description:
|
||||
- Used to specify the path to the volume to be formatted.
|
||||
type: str
|
||||
label:
|
||||
description:
|
||||
- Used to specify the label of the volume to be formatted.
|
||||
type: str
|
||||
new_label:
|
||||
description:
|
||||
- Used to specify the new file system label of the formatted volume.
|
||||
type: str
|
||||
file_system:
|
||||
description:
|
||||
- Used to specify the file system to be used when formatting the target volume.
|
||||
type: str
|
||||
choices: [ ntfs, refs, exfat, fat32, fat ]
|
||||
allocation_unit_size:
|
||||
description:
|
||||
- Specifies the cluster size to use when formatting the volume.
|
||||
- If no cluster size is specified when you format a partition, defaults are selected based on
|
||||
the size of the partition.
|
||||
- This value must be a multiple of the physical sector size of the disk.
|
||||
type: int
|
||||
large_frs:
|
||||
description:
|
||||
- Specifies that large File Record System (FRS) should be used.
|
||||
type: bool
|
||||
compress:
|
||||
description:
|
||||
- Enable compression on the resulting NTFS volume.
|
||||
- NTFS compression is not supported where I(allocation_unit_size) is more than 4096.
|
||||
type: bool
|
||||
integrity_streams:
|
||||
description:
|
||||
- Enable integrity streams on the resulting ReFS volume.
|
||||
type: bool
|
||||
full:
|
||||
description:
|
||||
- A full format writes to every sector of the disk, takes much longer to perform than the
|
||||
default (quick) format, and is not recommended on storage that is thinly provisioned.
|
||||
- Specify C(true) for full format.
|
||||
type: bool
|
||||
force:
|
||||
description:
|
||||
- Specify if formatting should be forced for volumes that are not created from new partitions
|
||||
or if the source and target file system are different.
|
||||
type: bool
|
||||
notes:
|
||||
- Microsoft Windows Server 2012 or Microsoft Windows 8 or newer is required to use this module. To check if your system is compatible, see
|
||||
U(https://docs.microsoft.com/en-us/windows/desktop/sysinfo/operating-system-version).
|
||||
- One of three parameters (I(drive_letter), I(path) and I(label)) are mandatory to identify the target
|
||||
volume but more than one cannot be specified at the same time.
|
||||
- This module is idempotent if I(force) is not specified and file system labels remain preserved.
|
||||
- For more information, see U(https://docs.microsoft.com/en-us/previous-versions/windows/desktop/stormgmt/format-msft-volume)
|
||||
seealso:
|
||||
- module: win_disk_facts
|
||||
- module: win_partition
|
||||
author:
|
||||
- Varun Chopra (@chopraaa) <v@chopraaa.com>
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create a partition with drive letter D and size 5 GiB
|
||||
win_partition:
|
||||
drive_letter: D
|
||||
partition_size: 5 GiB
|
||||
disk_number: 1
|
||||
|
||||
- name: Full format the newly created partition as NTFS and label it
|
||||
win_format:
|
||||
drive_letter: D
|
||||
file_system: NTFS
|
||||
new_label: Formatted
|
||||
full: True
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
#
|
||||
'''
|
@ -1,257 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2018, Micah Hunsberger (@mhunsber)
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
|
||||
Set-StrictMode -Version 2
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
state = @{ type = "str"; choices = "absent", "present"; default = "present" }
|
||||
aliases = @{ type = "list"; elements = "str" }
|
||||
canonical_name = @{ type = "str" }
|
||||
ip_address = @{ type = "str" }
|
||||
action = @{ type = "str"; choices = "add", "remove", "set"; default = "set" }
|
||||
}
|
||||
required_if = @(,@( "state", "present", @("canonical_name", "ip_address")))
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$state = $module.Params.state
|
||||
$aliases = $module.Params.aliases
|
||||
$canonical_name = $module.Params.canonical_name
|
||||
$ip_address = $module.Params.ip_address
|
||||
$action = $module.Params.action
|
||||
|
||||
$tmp = [ipaddress]::None
|
||||
if($ip_address -and -not [ipaddress]::TryParse($ip_address, [ref]$tmp)){
|
||||
$module.FailJson("win_hosts: Argument ip_address needs to be a valid ip address, but was $ip_address")
|
||||
}
|
||||
$ip_address_type = $tmp.AddressFamily
|
||||
|
||||
$hosts_file = Get-Item -LiteralPath "$env:SystemRoot\System32\drivers\etc\hosts"
|
||||
|
||||
Function Get-CommentIndex($line) {
|
||||
$c_index = $line.IndexOf('#')
|
||||
if($c_index -lt 0) {
|
||||
$c_index = $line.Length
|
||||
}
|
||||
return $c_index
|
||||
}
|
||||
|
||||
Function Get-HostEntryParts($line) {
|
||||
$success = $true
|
||||
$c_index = Get-CommentIndex -line $line
|
||||
$pure_line = $line.Substring(0,$c_index).Trim()
|
||||
$bits = $pure_line -split "\s+"
|
||||
if($bits.Length -lt 2){
|
||||
return @{
|
||||
success = $false
|
||||
ip_address = ""
|
||||
ip_type = ""
|
||||
canonical_name = ""
|
||||
aliases = @()
|
||||
}
|
||||
}
|
||||
$ip_obj = [ipaddress]::None
|
||||
if(-not [ipaddress]::TryParse($bits[0], [ref]$ip_obj) ){
|
||||
$success = $false
|
||||
}
|
||||
$cname = $bits[1]
|
||||
$als = New-Object string[] ($bits.Length - 2)
|
||||
[array]::Copy($bits, 2, $als, 0, $als.Length)
|
||||
return @{
|
||||
success = $success
|
||||
ip_address = $ip_obj.IPAddressToString
|
||||
ip_type = $ip_obj.AddressFamily
|
||||
canonical_name = $cname
|
||||
aliases = $als
|
||||
}
|
||||
}
|
||||
|
||||
Function Find-HostName($line, $name) {
|
||||
$c_idx = Get-CommentIndex -line $line
|
||||
$re = New-Object regex ("\s+$($name.Replace('.',"\."))(\s|$)", [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
|
||||
$match = $re.Match($line, 0, $c_idx)
|
||||
return $match
|
||||
}
|
||||
|
||||
Function Remove-HostEntry($list, $idx) {
|
||||
$module.Result.changed = $true
|
||||
$list.RemoveAt($idx)
|
||||
}
|
||||
|
||||
Function Add-HostEntry($list, $cname, $aliases, $ip) {
|
||||
$module.Result.changed = $true
|
||||
$line = "$ip $cname $($aliases -join ' ')"
|
||||
$list.Add($line) | Out-Null
|
||||
}
|
||||
|
||||
Function Remove-HostnamesFromEntry($list, $idx, $aliases) {
|
||||
$line = $list[$idx]
|
||||
$line_removed = $false
|
||||
|
||||
foreach($name in $aliases){
|
||||
$match = Find-HostName -line $line -name $name
|
||||
if($match.Success){
|
||||
$line = $line.Remove($match.Index + 1, $match.Length -1)
|
||||
# was this the last alias? (check for space characters after trimming)
|
||||
if($line.Substring(0,(Get-CommentIndex -line $line)).Trim() -inotmatch "\s") {
|
||||
$list.RemoveAt($idx)
|
||||
$line_removed = $true
|
||||
# we're done
|
||||
return @{
|
||||
line_removed = $line_removed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if($line -ne $list[$idx]){
|
||||
$module.Result.changed = $true
|
||||
$list[$idx] = $line
|
||||
}
|
||||
return @{
|
||||
line_removed = $line_removed
|
||||
}
|
||||
}
|
||||
|
||||
Function Add-AliasesToEntry($list, $idx, $aliases) {
|
||||
$line = $list[$idx]
|
||||
foreach($name in $aliases){
|
||||
$match = Find-HostName -line $line -name $name
|
||||
if(-not $match.Success) {
|
||||
# just add the alias before the comment
|
||||
$line = $line.Insert((Get-CommentIndex -line $line), " $name ")
|
||||
}
|
||||
}
|
||||
if($line -ne $list[$idx]){
|
||||
$module.Result.changed = $true
|
||||
$list[$idx] = $line
|
||||
}
|
||||
}
|
||||
|
||||
$hosts_lines = New-Object System.Collections.ArrayList
|
||||
|
||||
Get-Content -LiteralPath $hosts_file.FullName | ForEach-Object { $hosts_lines.Add($_) } | Out-Null
|
||||
$module.Diff.before = ($hosts_lines -join "`n") + "`n"
|
||||
|
||||
if ($state -eq 'absent') {
|
||||
# go through and remove canonical_name and ip
|
||||
for($idx = 0; $idx -lt $hosts_lines.Count; $idx++) {
|
||||
$entry = $hosts_lines[$idx]
|
||||
# skip comment lines
|
||||
if(-not $entry.Trim().StartsWith('#')) {
|
||||
$entry_parts = Get-HostEntryParts -line $entry
|
||||
if($entry_parts.success) {
|
||||
if(-not $ip_address -or $entry_parts.ip_address -eq $ip_address) {
|
||||
if(-not $canonical_name -or $entry_parts.canonical_name -eq $canonical_name) {
|
||||
if(Remove-HostEntry -list $hosts_lines -idx $idx){
|
||||
# keep index correct if we removed the line
|
||||
$idx = $idx - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if($state -eq 'present') {
|
||||
$entry_idx = -1
|
||||
$aliases_to_keep = @()
|
||||
# go through lines, find the entry and determine what to remove based on action
|
||||
for($idx = 0; $idx -lt $hosts_lines.Count; $idx++) {
|
||||
$entry = $hosts_lines[$idx]
|
||||
# skip comment lines
|
||||
if(-not $entry.Trim().StartsWith('#')) {
|
||||
$entry_parts = Get-HostEntryParts -line $entry
|
||||
if($entry_parts.success) {
|
||||
$aliases_to_remove = @()
|
||||
if($entry_parts.ip_address -eq $ip_address) {
|
||||
if($entry_parts.canonical_name -eq $canonical_name) {
|
||||
$entry_idx = $idx
|
||||
|
||||
if($action -eq 'set') {
|
||||
$aliases_to_remove = $entry_parts.aliases | Where-Object { $aliases -notcontains $_ }
|
||||
} elseif($action -eq 'remove') {
|
||||
$aliases_to_remove = $aliases
|
||||
}
|
||||
} else {
|
||||
# this is the right ip_address, but not the cname we were looking for.
|
||||
# we need to make sure none of aliases or canonical_name exist for this entry
|
||||
# since the given canonical_name should be an A/AAAA record,
|
||||
# and aliases should be cname records for the canonical_name.
|
||||
$aliases_to_remove = $aliases + $canonical_name
|
||||
}
|
||||
} else {
|
||||
# this is not the ip_address we are looking for
|
||||
if ($ip_address_type -eq $entry_parts.ip_type) {
|
||||
if ($entry_parts.canonical_name -eq $canonical_name) {
|
||||
Remove-HostEntry -list $hosts_lines -idx $idx
|
||||
$idx = $idx - 1
|
||||
if ($action -ne "set") {
|
||||
# keep old aliases intact
|
||||
$aliases_to_keep += $entry_parts.aliases | Where-Object { ($aliases + $aliases_to_keep + $canonical_name) -notcontains $_ }
|
||||
}
|
||||
} elseif ($action -eq "remove") {
|
||||
$aliases_to_remove = $canonical_name
|
||||
} elseif ($aliases -contains $entry_parts.canonical_name) {
|
||||
Remove-HostEntry -list $hosts_lines -idx $idx
|
||||
$idx = $idx - 1
|
||||
if ($action -eq "add") {
|
||||
# keep old aliases intact
|
||||
$aliases_to_keep += $entry_parts.aliases | Where-Object { ($aliases + $aliases_to_keep + $canonical_name) -notcontains $_ }
|
||||
}
|
||||
} else {
|
||||
$aliases_to_remove = $aliases + $canonical_name
|
||||
}
|
||||
} else {
|
||||
# TODO: Better ipv6 support. There is odd behavior for when an alias can be used for both ipv6 and ipv4
|
||||
}
|
||||
}
|
||||
|
||||
if($aliases_to_remove) {
|
||||
if((Remove-HostnamesFromEntry -list $hosts_lines -idx $idx -aliases $aliases_to_remove).line_removed) {
|
||||
$idx = $idx - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($entry_idx -ge 0) {
|
||||
$aliases_to_add = @()
|
||||
$entry_parts = Get-HostEntryParts -line $hosts_lines[$entry_idx]
|
||||
if($action -eq 'remove') {
|
||||
$aliases_to_add = $aliases_to_keep | Where-Object { $entry_parts.aliases -notcontains $_ }
|
||||
} else {
|
||||
$aliases_to_add = ($aliases + $aliases_to_keep) | Where-Object { $entry_parts.aliases -notcontains $_ }
|
||||
}
|
||||
|
||||
if($aliases_to_add) {
|
||||
Add-AliasesToEntry -list $hosts_lines -idx $entry_idx -aliases $aliases_to_add
|
||||
}
|
||||
} else {
|
||||
# add the entry at the end
|
||||
if($action -eq 'remove') {
|
||||
if($aliases_to_keep) {
|
||||
Add-HostEntry -list $hosts_lines -ip $ip_address -cname $canonical_name -aliases $aliases_to_keep
|
||||
} else {
|
||||
Add-HostEntry -list $hosts_lines -ip $ip_address -cname $canonical_name
|
||||
}
|
||||
} else {
|
||||
Add-HostEntry -list $hosts_lines -ip $ip_address -cname $canonical_name -aliases ($aliases + $aliases_to_keep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$module.Diff.after = ($hosts_lines -join "`n") + "`n"
|
||||
if( $module.Result.changed -and -not $module.CheckMode ) {
|
||||
Set-Content -LiteralPath $hosts_file.FullName -Value $hosts_lines
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
@ -1,126 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Micah Hunsberger (@mhunsber)
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_hosts
|
||||
version_added: '2.8'
|
||||
short_description: Manages hosts file entries on Windows.
|
||||
description:
|
||||
- Manages hosts file entries on Windows.
|
||||
- Maps IPv4 or IPv6 addresses to canonical names.
|
||||
- Adds, removes, or sets cname records for ip and hostname pairs.
|
||||
- Modifies %windir%\\system32\\drivers\\etc\\hosts.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether the entry should be present or absent.
|
||||
- If only I(canonical_name) is provided when C(state=absent), then
|
||||
all hosts entries with the canonical name of I(canonical_name)
|
||||
will be removed.
|
||||
- If only I(ip_address) is provided when C(state=absent), then all
|
||||
hosts entries with the ip address of I(ip_address) will be removed.
|
||||
- If I(ip_address) and I(canonical_name) are both omitted when
|
||||
C(state=absent), then all hosts entries will be removed.
|
||||
choices:
|
||||
- absent
|
||||
- present
|
||||
default: present
|
||||
type: str
|
||||
canonical_name:
|
||||
description:
|
||||
- A canonical name for the host entry.
|
||||
- required for C(state=present).
|
||||
type: str
|
||||
ip_address:
|
||||
description:
|
||||
- The ip address for the host entry.
|
||||
- Can be either IPv4 (A record) or IPv6 (AAAA record).
|
||||
- Required for C(state=present).
|
||||
type: str
|
||||
aliases:
|
||||
description:
|
||||
- A list of additional names (cname records) for the host entry.
|
||||
- Only applicable when C(state=present).
|
||||
type: list
|
||||
action:
|
||||
choices:
|
||||
- add
|
||||
- remove
|
||||
- set
|
||||
description:
|
||||
- Controls the behavior of I(aliases).
|
||||
- Only applicable when C(state=present).
|
||||
- If C(add), each alias in I(aliases) will be added to the host entry.
|
||||
- If C(set), each alias in I(aliases) will be added to the host entry,
|
||||
and other aliases will be removed from the entry.
|
||||
default: set
|
||||
type: str
|
||||
author:
|
||||
- Micah Hunsberger (@mhunsber)
|
||||
notes:
|
||||
- Each canonical name can only be mapped to one IPv4 and one IPv6 address.
|
||||
If I(canonical_name) is provided with C(state=present) and is found
|
||||
to be mapped to another IP address that is the same type as, but unique
|
||||
from I(ip_address), then I(canonical_name) and all I(aliases) will
|
||||
be removed from the entry and added to an entry with the provided IP address.
|
||||
- Each alias can only be mapped to one canonical name. If I(aliases) is provided
|
||||
with C(state=present) and an alias is found to be mapped to another canonical
|
||||
name, then the alias will be removed from the entry and either added to or removed
|
||||
from (depending on I(action)) an entry with the provided canonical name.
|
||||
seealso:
|
||||
- module: win_template
|
||||
- module: win_file
|
||||
- module: win_copy
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add 127.0.0.1 as an A record for localhost
|
||||
win_hosts:
|
||||
state: present
|
||||
canonical_name: localhost
|
||||
ip_address: 127.0.0.1
|
||||
|
||||
- name: Add ::1 as an AAAA record for localhost
|
||||
win_hosts:
|
||||
state: present
|
||||
canonical_name: localhost
|
||||
ip_address: '::1'
|
||||
|
||||
- name: Remove 'bar' and 'zed' from the list of aliases for foo (192.168.1.100)
|
||||
win_hosts:
|
||||
state: present
|
||||
canoncial_name: foo
|
||||
ip_address: 192.168.1.100
|
||||
action: remove
|
||||
aliases:
|
||||
- bar
|
||||
- zed
|
||||
|
||||
- name: Remove hosts entries with canonical name 'bar'
|
||||
win_hosts:
|
||||
state: absent
|
||||
canonical_name: bar
|
||||
|
||||
- name: Remove 10.2.0.1 from the list of hosts
|
||||
win_hosts:
|
||||
state: absent
|
||||
ip_address: 10.2.0.1
|
||||
|
||||
- name: Ensure all name resolution is handled by DNS
|
||||
win_hosts:
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
@ -1,239 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
|
||||
$hotfix_kb = Get-AnsibleParam -obj $params -name "hotfix_kb" -type "str"
|
||||
$hotfix_identifier = Get-AnsibleParam -obj $params -name "hotfix_identifier" -type "str"
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "state" -default "present" -validateset "absent","present"
|
||||
$source = Get-AnsibleParam -obj $params -name "source" -type "path"
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
reboot_required = $false
|
||||
}
|
||||
|
||||
if (Get-Module -Name DISM -ListAvailable) {
|
||||
Import-Module -Name DISM
|
||||
} else {
|
||||
# Server 2008 R2 doesn't have the DISM module installed on the path, check the Windows ADK path
|
||||
$adk_root = [System.Environment]::ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\Windows Kits\*\Assessment and Deployment Kit\Deployment Tools\amd64\DISM")
|
||||
if (Test-Path -Path $adk_root) {
|
||||
Import-Module -Name (Get-Item -Path $adk_root).FullName
|
||||
} else {
|
||||
Fail-Json $result "The DISM PS module needs to be installed, this can be done through the windows-adk chocolately package"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Function Extract-MSU($msu) {
|
||||
$temp_path = [IO.Path]::GetTempPath()
|
||||
$temp_foldername = [Guid]::NewGuid()
|
||||
$output_path = Join-Path -Path $temp_path -ChildPath $temp_foldername
|
||||
New-Item -Path $output_path -ItemType Directory | Out-Null
|
||||
|
||||
$expand_args = @($msu, $output_path, "-F:*")
|
||||
|
||||
try {
|
||||
&expand.exe $expand_args | Out-NUll
|
||||
} catch {
|
||||
Fail-Json $result "failed to run expand.exe $($expand_args): $($_.Exception.Message)"
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Fail-Json $result "failed to run expand.exe $($expand_args): RC = $LASTEXITCODE"
|
||||
}
|
||||
|
||||
return $output_path
|
||||
}
|
||||
|
||||
Function Get-HotfixMetadataFromName($name) {
|
||||
try {
|
||||
$dism_package_info = Get-WindowsPackage -Online -PackageName $name
|
||||
} catch {
|
||||
# build a basic stub for a missing result
|
||||
$dism_package_info = @{
|
||||
PackageState = "NotPresent"
|
||||
Description = ""
|
||||
PackageName = $name
|
||||
}
|
||||
}
|
||||
|
||||
if ($dism_package_info.Description -match "(KB\d*)") {
|
||||
$hotfix_kb = $Matches[0]
|
||||
} else {
|
||||
$hotfix_kb = "UNKNOWN"
|
||||
}
|
||||
|
||||
$metadata = @{
|
||||
name = $dism_package_info.PackageName
|
||||
state = $dism_package_info.PackageState
|
||||
kb = $hotfix_kb
|
||||
}
|
||||
|
||||
return $metadata
|
||||
}
|
||||
|
||||
Function Get-HotfixMetadataFromFile($extract_path) {
|
||||
# MSU contents https://support.microsoft.com/en-us/help/934307/description-of-the-windows-update-standalone-installer-in-windows
|
||||
$metadata_path = Get-ChildItem -Path $extract_path | Where-Object { $_.Extension -eq ".xml" }
|
||||
if ($null -eq $metadata_path) {
|
||||
Fail-Json $result "failed to get metadata xml inside MSU file, cannot get hotfix metadata required for this task"
|
||||
}
|
||||
[xml]$xml = Get-Content -Path $metadata_path.FullName
|
||||
|
||||
$cab_source_filename = $xml.unattend.servicing.package.source.GetAttribute("location")
|
||||
$cab_source_filename = Split-Path -Path $cab_source_filename -Leaf
|
||||
$cab_file = Join-Path -Path $extract_path -ChildPath $cab_source_filename
|
||||
|
||||
try {
|
||||
$dism_package_info = Get-WindowsPackage -Online -PackagePath $cab_file
|
||||
} catch {
|
||||
Fail-Json $result "failed to get DISM package metadata from path $($extract_path): $($_.Exception.Message)"
|
||||
}
|
||||
if ($dism_package_info.Applicable -eq $false) {
|
||||
Fail-Json $result "hotfix package is not applicable for this server"
|
||||
}
|
||||
|
||||
$package_properties_path = Get-ChildItem -Path $extract_path | Where-Object { $_.Extension -eq ".txt" }
|
||||
if ($null -eq $package_properties_path) {
|
||||
$hotfix_kb = "UNKNOWN"
|
||||
} else {
|
||||
$package_ini = Get-Content -Path $package_properties_path.FullName
|
||||
$entry = $package_ini | Where-Object { $_.StartsWith("KB Article Number") }
|
||||
if ($null -eq $entry) {
|
||||
$hotfix_kb = "UNKNOWN"
|
||||
} else {
|
||||
$hotfix_kb = ($entry -split '=')[-1]
|
||||
$hotfix_kb = "KB$($hotfix_kb.Substring(1, $hotfix_kb.Length - 2))"
|
||||
}
|
||||
}
|
||||
|
||||
$metadata = @{
|
||||
path = $cab_file
|
||||
name = $dism_package_info.PackageName
|
||||
state = $dism_package_info.PackageState
|
||||
kb = $hotfix_kb
|
||||
}
|
||||
|
||||
return $metadata
|
||||
}
|
||||
|
||||
Function Get-HotfixMetadataFromKB($kb) {
|
||||
# I really hate doing it this way
|
||||
$packages = Get-WindowsPackage -Online
|
||||
$identifier = $packages | Where-Object { $_.PackageName -like "*$kb*" }
|
||||
|
||||
if ($null -eq $identifier) {
|
||||
# still haven't found the KB, need to loop through the results and check the description
|
||||
foreach ($package in $packages) {
|
||||
$raw_metadata = Get-HotfixMetadataFromName -name $package.PackageName
|
||||
if ($raw_metadata.kb -eq $kb) {
|
||||
$identifier = $raw_metadata
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# if we still haven't found the package then we need to throw an error
|
||||
if ($null -eq $metadata) {
|
||||
Fail-Json $result "failed to get DISM package from KB, to continue specify hotfix_identifier instead"
|
||||
}
|
||||
} else {
|
||||
$metadata = Get-HotfixMetadataFromName -name $identifier.PackageName
|
||||
}
|
||||
|
||||
return $metadata
|
||||
}
|
||||
|
||||
if ($state -eq "absent") {
|
||||
# uninstall hotfix
|
||||
# this is a pretty poor way of doing this, is there a better way?
|
||||
|
||||
if ($null -ne $hotfix_identifier) {
|
||||
$hotfix_metadata = Get-HotfixMetadataFromName -name $hotfix_identifier
|
||||
} elseif ($null -ne $hotfix_kb) {
|
||||
$hotfix_install_info = Get-Hotfix -Id $hotfix_kb -ErrorAction SilentlyContinue
|
||||
if ($null -ne $hotfix_install_info) {
|
||||
$hotfix_metadata = Get-HotfixMetadataFromKB -kb $hotfix_kb
|
||||
} else {
|
||||
$hotfix_metadata = @{state = "NotPresent"}
|
||||
}
|
||||
} else {
|
||||
Fail-Json $result "either hotfix_identifier or hotfix_kb needs to be set when state=absent"
|
||||
}
|
||||
|
||||
# how do we want to deal with the other states?
|
||||
if ($hotfix_metadata.state -eq "UninstallPending") {
|
||||
$result.identifier = $hotfix_metadata.name
|
||||
$result.kb = $hotfix_metadata.kb
|
||||
$result.reboot_required = $true
|
||||
} elseif ($hotfix_metadata.state -eq "Installed") {
|
||||
$result.identifier = $hotfix_metadata.name
|
||||
$result.kb = $hotfix_metadata.kb
|
||||
|
||||
if (-not $check_mode) {
|
||||
try {
|
||||
$remove_result = Remove-WindowsPackage -Online -PackageName $hotfix_metadata.name -NoRestart
|
||||
} catch {
|
||||
Fail-Json $result "failed to remove package $($hotfix_metadata.name): $($_.Exception.Message)"
|
||||
}
|
||||
$result.reboot_required = $remove_Result.RestartNeeded
|
||||
}
|
||||
|
||||
$result.changed = $true
|
||||
}
|
||||
} else {
|
||||
if ($null -eq $source) {
|
||||
Fail-Json $result "source must be set when state=present"
|
||||
}
|
||||
if (-not (Test-Path -Path $source -PathType Leaf)) {
|
||||
Fail-Json $result "the path set for source $source does not exist or is not a file"
|
||||
}
|
||||
|
||||
# while we do extract the file in check mode we need to do so for valid checking
|
||||
$extract_path = Extract-MSU -msu $source
|
||||
try {
|
||||
$hotfix_metadata = Get-HotfixMetadataFromFile -extract_path $extract_path
|
||||
|
||||
# validate the hotfix matches if the hotfix id has been passed in
|
||||
if ($null -ne $hotfix_identifier) {
|
||||
if ($hotfix_metadata.name -ne $hotfix_identifier) {
|
||||
Fail-Json $result "the hotfix identifier $hotfix_identifier does not match with the source msu identifier $($hotfix_metadata.name), please omit or specify the correct identifier to continue"
|
||||
}
|
||||
}
|
||||
if ($null -ne $hotfix_kb) {
|
||||
if ($hotfix_metadata.kb -ne $hotfix_kb) {
|
||||
Fail-Json $result "the hotfix KB $hotfix_kb does not match with the source msu KB $($hotfix_metadata.kb), please omit or specify the correct KB to continue"
|
||||
}
|
||||
}
|
||||
|
||||
$result.identifier = $hotfix_metadata.name
|
||||
$result.kb = $hotfix_metadata.kb
|
||||
|
||||
# how do we want to deal with other states
|
||||
if ($hotfix_metadata.state -eq "InstallPending") {
|
||||
# return the reboot required flag, should we fail here instead
|
||||
$result.reboot_required = $true
|
||||
} elseif ($hotfix_metadata.state -ne "Installed") {
|
||||
if (-not $check_mode) {
|
||||
try {
|
||||
$install_result = Add-WindowsPackage -Online -PackagePath $hotfix_metadata.path -NoRestart
|
||||
} catch {
|
||||
Fail-Json $result "failed to add windows package from path $($hotfix_metadata.path): $($_.Exception.Message)"
|
||||
}
|
||||
$result.reboot_required = $install_result.RestartNeeded
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
} finally {
|
||||
Remove-Item -Path $extract_path -Force -Recurse
|
||||
}
|
||||
}
|
||||
|
||||
Exit-Json $result
|
@ -1,142 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub, actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_hotfix
|
||||
version_added: '2.4'
|
||||
short_description: Install and uninstalls Windows hotfixes
|
||||
description:
|
||||
- Install, uninstall a Windows hotfix.
|
||||
options:
|
||||
hotfix_identifier:
|
||||
description:
|
||||
- The name of the hotfix as shown in DISM, see examples for details.
|
||||
- This or C(hotfix_kb) MUST be set when C(state=absent).
|
||||
- If C(state=present) then the hotfix at C(source) will be validated
|
||||
against this value, if it does not match an error will occur.
|
||||
- You can get the identifier by running
|
||||
'Get-WindowsPackage -Online -PackagePath path-to-cab-in-msu' after
|
||||
expanding the msu file.
|
||||
type: str
|
||||
hotfix_kb:
|
||||
description:
|
||||
- The name of the KB the hotfix relates to, see examples for details.
|
||||
- This or C(hotfix_identifier) MUST be set when C(state=absent).
|
||||
- If C(state=present) then the hotfix at C(source) will be validated
|
||||
against this value, if it does not match an error will occur.
|
||||
- Because DISM uses the identifier as a key and doesn't refer to a KB in
|
||||
all cases it is recommended to use C(hotfix_identifier) instead.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Whether to install or uninstall the hotfix.
|
||||
- When C(present), C(source) MUST be set.
|
||||
- When C(absent), C(hotfix_identifier) or C(hotfix_kb) MUST be set.
|
||||
type: str
|
||||
default: present
|
||||
choices: [ absent, present ]
|
||||
source:
|
||||
description:
|
||||
- The path to the downloaded hotfix .msu file.
|
||||
- This MUST be set if C(state=present) and MUST be a .msu hotfix file.
|
||||
type: path
|
||||
notes:
|
||||
- This must be run on a host that has the DISM powershell module installed and
|
||||
a Powershell version >= 4.
|
||||
- This module is installed by default on Windows 8 and Server 2012 and newer.
|
||||
- You can manually install this module on Windows 7 and Server 2008 R2 by
|
||||
installing the Windows ADK
|
||||
U(https://developer.microsoft.com/en-us/windows/hardware/windows-assessment-deployment-kit),
|
||||
see examples to see how to do it with chocolatey.
|
||||
- You can download hotfixes from U(https://www.catalog.update.microsoft.com/Home.aspx).
|
||||
seealso:
|
||||
- module: win_package
|
||||
- module: win_updates
|
||||
author:
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Install Windows ADK with DISM for Server 2008 R2
|
||||
win_chocolatey:
|
||||
name: windows-adk
|
||||
version: 8.100.26866.0
|
||||
state: present
|
||||
install_args: /features OptionId.DeploymentTools
|
||||
|
||||
- name: Install hotfix without validating the KB and Identifier
|
||||
win_hotfix:
|
||||
source: C:\temp\windows8.1-kb3172729-x64_e8003822a7ef4705cbb65623b72fd3cec73fe222.msu
|
||||
state: present
|
||||
register: hotfix_install
|
||||
|
||||
- win_reboot:
|
||||
when: hotfix_install.reboot_required
|
||||
|
||||
- name: Install hotfix validating KB
|
||||
win_hotfix:
|
||||
hotfix_kb: KB3172729
|
||||
source: C:\temp\windows8.1-kb3172729-x64_e8003822a7ef4705cbb65623b72fd3cec73fe222.msu
|
||||
state: present
|
||||
register: hotfix_install
|
||||
|
||||
- win_reboot:
|
||||
when: hotfix_install.reboot_required
|
||||
|
||||
- name: Install hotfix validating Identifier
|
||||
win_hotfix:
|
||||
hotfix_identifier: Package_for_KB3172729~31bf3856ad364e35~amd64~~6.3.1.0
|
||||
source: C:\temp\windows8.1-kb3172729-x64_e8003822a7ef4705cbb65623b72fd3cec73fe222.msu
|
||||
state: present
|
||||
register: hotfix_install
|
||||
|
||||
- win_reboot:
|
||||
when: hotfix_install.reboot_required
|
||||
|
||||
- name: Uninstall hotfix with Identifier
|
||||
win_hotfix:
|
||||
hotfix_identifier: Package_for_KB3172729~31bf3856ad364e35~amd64~~6.3.1.0
|
||||
state: absent
|
||||
register: hotfix_uninstall
|
||||
|
||||
- win_reboot:
|
||||
when: hotfix_uninstall.reboot_required
|
||||
|
||||
- name: Uninstall hotfix with KB (not recommended)
|
||||
win_hotfix:
|
||||
hotfix_kb: KB3172729
|
||||
state: absent
|
||||
register: hotfix_uninstall
|
||||
|
||||
- win_reboot:
|
||||
when: hotfix_uninstall.reboot_required
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
identifier:
|
||||
description: The DISM identifier for the hotfix.
|
||||
returned: success
|
||||
type: str
|
||||
sample: Package_for_KB3172729~31bf3856ad364e35~amd64~~6.3.1.0
|
||||
kb:
|
||||
description: The KB the hotfix relates to.
|
||||
returned: success
|
||||
type: str
|
||||
sample: KB3172729
|
||||
reboot_required:
|
||||
description: Whether a reboot is required for the install or uninstall to
|
||||
finalise.
|
||||
returned: success
|
||||
type: str
|
||||
sample: true
|
||||
'''
|
@ -1,267 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2019, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#Requires -Module Ansible.ModuleUtils.AddType
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
bypass = @{ type = "list" }
|
||||
proxy = @{ type = "raw" }
|
||||
source = @{ type = "str"; choices = @("ie") }
|
||||
}
|
||||
mutually_exclusive = @(
|
||||
@("proxy", "source"),
|
||||
@("bypass", "source")
|
||||
)
|
||||
required_by = @{
|
||||
bypass = @("proxy")
|
||||
}
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$proxy = $module.Params.proxy
|
||||
$bypass = $module.Params.bypass
|
||||
$source = $module.Params.source
|
||||
|
||||
# Parse the raw value, it should be a Dictionary or String
|
||||
if ($proxy -is [System.Collections.IDictionary]) {
|
||||
$valid_keys = [System.Collections.Generic.List`1[String]]@("http", "https", "ftp", "socks")
|
||||
# Check to make sure we don't have any invalid keys in the dict
|
||||
$invalid_keys = [System.Collections.Generic.List`1[String]]@()
|
||||
foreach ($k in $proxy.Keys) {
|
||||
if ($k -notin $valid_keys) {
|
||||
$invalid_keys.Add($k)
|
||||
}
|
||||
}
|
||||
|
||||
if ($invalid_keys.Count -gt 0) {
|
||||
$invalid_keys = $invalid_keys | Sort-Object # So our test assertion doesn't fail due to random ordering
|
||||
$module.FailJson("Invalid keys found in proxy: $($invalid_keys -join ', '). Valid keys are $($valid_keys -join ', ').")
|
||||
}
|
||||
|
||||
# Build the proxy string in the form 'protocol=host;', the order of valid_keys is also important
|
||||
$proxy_list = [System.Collections.Generic.List`1[String]]@()
|
||||
foreach ($k in $valid_keys) {
|
||||
if ($proxy.ContainsKey($k)) {
|
||||
$proxy_list.Add("$k=$($proxy.$k)")
|
||||
}
|
||||
}
|
||||
$proxy = $proxy_list -join ";"
|
||||
} elseif ($null -ne $proxy) {
|
||||
$proxy = $proxy.ToString()
|
||||
}
|
||||
|
||||
if ($bypass) {
|
||||
if ([System.String]::IsNullOrEmpty($proxy)) {
|
||||
$module.FailJson("missing parameter(s) required by ''bypass'': proxy")
|
||||
}
|
||||
$bypass = $bypass -join ';'
|
||||
}
|
||||
|
||||
$win_http_invoke = @'
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ansible.WinHttpProxy
|
||||
{
|
||||
internal class NativeHelpers
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public class WINHTTP_CURRENT_USER_IE_PROXY_CONFIG : IDisposable
|
||||
{
|
||||
public bool fAutoDetect;
|
||||
public IntPtr lpszAutoConfigUrl;
|
||||
public IntPtr lpszProxy;
|
||||
public IntPtr lpszProxyBypass;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (lpszAutoConfigUrl != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(lpszAutoConfigUrl);
|
||||
if (lpszProxy != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(lpszProxy);
|
||||
if (lpszProxyBypass != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(lpszProxyBypass);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
~WINHTTP_CURRENT_USER_IE_PROXY_CONFIG() { this.Dispose(); }
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public class WINHTTP_PROXY_INFO : IDisposable
|
||||
{
|
||||
public UInt32 dwAccessType;
|
||||
public IntPtr lpszProxy;
|
||||
public IntPtr lpszProxyBypass;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (lpszProxy != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(lpszProxy);
|
||||
if (lpszProxyBypass != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(lpszProxyBypass);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
~WINHTTP_PROXY_INFO() { this.Dispose(); }
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("Winhttp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool WinHttpGetDefaultProxyConfiguration(
|
||||
[Out] NativeHelpers.WINHTTP_PROXY_INFO pProxyInfo);
|
||||
|
||||
[DllImport("Winhttp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool WinHttpGetIEProxyConfigForCurrentUser(
|
||||
[Out] NativeHelpers.WINHTTP_CURRENT_USER_IE_PROXY_CONFIG pProxyConfig);
|
||||
|
||||
[DllImport("Winhttp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool WinHttpSetDefaultProxyConfiguration(
|
||||
NativeHelpers.WINHTTP_PROXY_INFO pProxyInfo);
|
||||
}
|
||||
|
||||
public class Win32Exception : System.ComponentModel.Win32Exception
|
||||
{
|
||||
private string _msg;
|
||||
|
||||
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
|
||||
public Win32Exception(int errorCode, string message) : base(errorCode)
|
||||
{
|
||||
_msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
|
||||
}
|
||||
|
||||
public override string Message { get { return _msg; } }
|
||||
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
|
||||
}
|
||||
|
||||
public class WinINetProxy
|
||||
{
|
||||
public bool AutoDetect;
|
||||
public string AutoConfigUrl;
|
||||
public string Proxy;
|
||||
public string ProxyBypass;
|
||||
}
|
||||
|
||||
public class WinHttpProxy
|
||||
{
|
||||
public string Proxy;
|
||||
public string ProxyBypass;
|
||||
|
||||
public WinHttpProxy()
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public void Set()
|
||||
{
|
||||
using (NativeHelpers.WINHTTP_PROXY_INFO proxyInfo = new NativeHelpers.WINHTTP_PROXY_INFO())
|
||||
{
|
||||
if (String.IsNullOrEmpty(Proxy))
|
||||
proxyInfo.dwAccessType = 1; // WINHTTP_ACCESS_TYPE_NO_PROXY
|
||||
else
|
||||
{
|
||||
proxyInfo.dwAccessType = 3; // WINHTTP_ACCESS_TYPE_NAMED_PROXY
|
||||
proxyInfo.lpszProxy = Marshal.StringToHGlobalUni(Proxy);
|
||||
|
||||
if (!String.IsNullOrEmpty(ProxyBypass))
|
||||
proxyInfo.lpszProxyBypass = Marshal.StringToHGlobalUni(ProxyBypass);
|
||||
}
|
||||
|
||||
if (!NativeMethods.WinHttpSetDefaultProxyConfiguration(proxyInfo))
|
||||
throw new Win32Exception("WinHttpSetDefaultProxyConfiguration() failed");
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
using (NativeHelpers.WINHTTP_PROXY_INFO proxyInfo = new NativeHelpers.WINHTTP_PROXY_INFO())
|
||||
{
|
||||
if (!NativeMethods.WinHttpGetDefaultProxyConfiguration(proxyInfo))
|
||||
throw new Win32Exception("WinHttpGetDefaultProxyConfiguration() failed");
|
||||
|
||||
Proxy = Marshal.PtrToStringUni(proxyInfo.lpszProxy);
|
||||
ProxyBypass = Marshal.PtrToStringUni(proxyInfo.lpszProxyBypass);
|
||||
}
|
||||
}
|
||||
|
||||
public static WinINetProxy GetIEProxyConfig()
|
||||
{
|
||||
using (NativeHelpers.WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxy = new NativeHelpers.WINHTTP_CURRENT_USER_IE_PROXY_CONFIG())
|
||||
{
|
||||
if (!NativeMethods.WinHttpGetIEProxyConfigForCurrentUser(ieProxy))
|
||||
throw new Win32Exception("WinHttpGetIEProxyConfigForCurrentUser() failed");
|
||||
|
||||
return new WinINetProxy
|
||||
{
|
||||
AutoDetect = ieProxy.fAutoDetect,
|
||||
AutoConfigUrl = Marshal.PtrToStringUni(ieProxy.lpszAutoConfigUrl),
|
||||
Proxy = Marshal.PtrToStringUni(ieProxy.lpszProxy),
|
||||
ProxyBypass = Marshal.PtrToStringUni(ieProxy.lpszProxyBypass),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
Add-CSharpType -References $win_http_invoke -AnsibleModule $module
|
||||
|
||||
$actual_proxy = New-Object -TypeName Ansible.WinHttpProxy.WinHttpProxy
|
||||
|
||||
$module.Diff.before = @{
|
||||
proxy = $actual_proxy.Proxy
|
||||
bypass = $actual_proxy.ProxyBypass
|
||||
}
|
||||
|
||||
if ($source -eq "ie") {
|
||||
# If source=ie we need to get the server and bypass values from the IE configuration
|
||||
$ie_proxy = [Ansible.WinHttpProxy.WinHttpProxy]::GetIEProxyConfig()
|
||||
$proxy = $ie_proxy.Proxy
|
||||
$bypass = $ie_proxy.ProxyBypass
|
||||
}
|
||||
|
||||
$previous_proxy = $actual_proxy.Proxy
|
||||
$previous_bypass = $actual_proxy.ProxyBypass
|
||||
|
||||
# Make sure an empty string is converted to $null for easier comparisons
|
||||
if ([String]::IsNullOrEmpty($proxy)) {
|
||||
$proxy = $null
|
||||
}
|
||||
if ([String]::IsNullOrEmpty($bypass)) {
|
||||
$bypass = $null
|
||||
}
|
||||
|
||||
if ($previous_proxy -ne $proxy -or $previous_bypass -ne $bypass) {
|
||||
$actual_proxy.Proxy = $proxy
|
||||
$actual_proxy.ProxyBypass = $bypass
|
||||
|
||||
if (-not $module.CheckMode) {
|
||||
$actual_proxy.Set()
|
||||
|
||||
# Validate that the change was made correctly and revert if it wasn't. The Set() method won't fail on invalid
|
||||
# values so we need to check again to make sure all was good.
|
||||
$actual_proxy.Refresh()
|
||||
if ($actual_proxy.Proxy -ne $proxy -or $actual_proxy.ProxyBypass -ne $bypass) {
|
||||
$actual_proxy.Proxy = $previous_proxy
|
||||
$actual_proxy.ProxyBypass = $previous_bypass
|
||||
$actual_proxy.Set()
|
||||
|
||||
$module.FailJson("Unknown error when trying to set proxy '$proxy' or bypass '$bypass'")
|
||||
}
|
||||
}
|
||||
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
|
||||
$module.Diff.after = @{
|
||||
proxy = $proxy
|
||||
bypass = $bypass
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
||||
|
@ -1,103 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_http_proxy
|
||||
version_added: '2.8'
|
||||
short_description: Manages proxy settings for WinHTTP
|
||||
description:
|
||||
- Used to set, remove, or import proxy settings for Windows HTTP Services
|
||||
C(WinHTTP).
|
||||
- WinHTTP is a framework used by applications or services, typically .NET
|
||||
applications or non-interactive services, to make web requests.
|
||||
options:
|
||||
bypass:
|
||||
description:
|
||||
- A list of hosts that will bypass the set proxy when being accessed.
|
||||
- Use C(<local>) to match hostnames that are not fully qualified domain
|
||||
names. This is useful when needing to connect to intranet sites using
|
||||
just the hostname.
|
||||
- Omit, set to null or an empty string/list to remove the bypass list.
|
||||
- If this is set then I(proxy) must also be set.
|
||||
type: list
|
||||
proxy:
|
||||
description:
|
||||
- A string or dict that specifies the proxy to be set.
|
||||
- If setting a string, should be in the form C(hostname), C(hostname:port),
|
||||
or C(protocol=hostname:port).
|
||||
- If the port is undefined, the default port for the protocol in use is
|
||||
used.
|
||||
- If setting a dict, the keys should be the protocol and the values should
|
||||
be the hostname and/or port for that protocol.
|
||||
- Valid protocols are C(http), C(https), C(ftp), and C(socks).
|
||||
- Omit, set to null or an empty string to remove the proxy settings.
|
||||
source:
|
||||
description:
|
||||
- Instead of manually specifying the I(proxy) and/or I(bypass), set this to
|
||||
import the proxy from a set source like Internet Explorer.
|
||||
- Using C(ie) will import the Internet Explorer proxy settings for the
|
||||
current active network connection of the current user.
|
||||
- Only IE's proxy URL and bypass list will be imported into WinHTTP.
|
||||
- This is like running C(netsh winhttp import proxy source=ie).
|
||||
- The value is imported when the module runs and will not automatically
|
||||
be updated if the IE configuration changes in the future. The module will
|
||||
have to be run again to sync the latest changes.
|
||||
choices:
|
||||
- ie
|
||||
type: str
|
||||
notes:
|
||||
- This is not the same as the proxy settings set in Internet Explorer, also
|
||||
known as C(WinINet); use the M(win_inet_proxy) module to manage that instead.
|
||||
- These settings are set system wide and not per user, it will require
|
||||
Administrative privileges to run.
|
||||
seealso:
|
||||
- module: win_inet_proxy
|
||||
author:
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Set a proxy to use for all protocols
|
||||
win_http_proxy:
|
||||
proxy: hostname
|
||||
|
||||
- name: Set a proxy with a specific port with a bypass list
|
||||
win_http_proxy:
|
||||
proxy: hostname:8080
|
||||
bypass:
|
||||
- server1
|
||||
- server2
|
||||
- <local>
|
||||
|
||||
- name: Set the proxy based on the IE proxy settings
|
||||
win_http_proxy:
|
||||
source: ie
|
||||
|
||||
- name: Set a proxy for specific protocols
|
||||
win_http_proxy:
|
||||
proxy:
|
||||
http: hostname:8080
|
||||
https: hostname:8443
|
||||
|
||||
- name: Set a proxy for specific protocols using a string
|
||||
win_http_proxy:
|
||||
proxy: http=hostname:8080;https=hostname:8443
|
||||
bypass: server1,server2,<local>
|
||||
|
||||
- name: Remove any proxy settings
|
||||
win_http_proxy:
|
||||
proxy: ''
|
||||
bypass: ''
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
#
|
||||
'''
|
@ -1,99 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2015, Henrik Wallström <henrik@wallstroms.nu>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$params = Parse-Args $args
|
||||
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
|
||||
$site = Get-AnsibleParam -obj $params -name "site" -type "str" -failifempty $true
|
||||
$application = Get-AnsibleParam -obj $params -name "application" -type "str"
|
||||
$physical_path = Get-AnsibleParam -obj $params -name "physical_path" -type "str"
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent","present"
|
||||
|
||||
# Ensure WebAdministration module is loaded
|
||||
if ($null -eq (Get-Module "WebAdministration" -ErrorAction SilentlyContinue)) {
|
||||
Import-Module WebAdministration
|
||||
}
|
||||
|
||||
# Result
|
||||
$result = @{
|
||||
directory = @{}
|
||||
changed = $false
|
||||
};
|
||||
|
||||
# Construct path
|
||||
$directory_path = if($application) {
|
||||
"IIS:\Sites\$($site)\$($application)\$($name)"
|
||||
} else {
|
||||
"IIS:\Sites\$($site)\$($name)"
|
||||
}
|
||||
|
||||
# Directory info
|
||||
$directory = if($application) {
|
||||
Get-WebVirtualDirectory -Site $site -Name $name -Application $application
|
||||
} else {
|
||||
Get-WebVirtualDirectory -Site $site -Name $name
|
||||
}
|
||||
|
||||
try {
|
||||
# Add directory
|
||||
If(($state -eq 'present') -and (-not $directory)) {
|
||||
If (-not $physical_path) {
|
||||
Fail-Json -obj $result -message "missing required arguments: physical_path"
|
||||
}
|
||||
If (-not (Test-Path $physical_path)) {
|
||||
Fail-Json -obj $result -message "specified folder must already exist: physical_path"
|
||||
}
|
||||
|
||||
$directory_parameters = @{
|
||||
Site = $site
|
||||
Name = $name
|
||||
PhysicalPath = $physical_path
|
||||
}
|
||||
|
||||
If ($application) {
|
||||
$directory_parameters.Application = $application
|
||||
}
|
||||
|
||||
$directory = New-WebVirtualDirectory @directory_parameters -Force
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
# Remove directory
|
||||
If ($state -eq 'absent' -and $directory) {
|
||||
Remove-Item $directory_path -Recurse -Force
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
$directory = Get-WebVirtualDirectory -Site $site -Name $name
|
||||
If($directory) {
|
||||
|
||||
# Change Physical Path if needed
|
||||
if($physical_path) {
|
||||
If (-not (Test-Path $physical_path)) {
|
||||
Fail-Json -obj $result -message "specified folder must already exist: physical_path"
|
||||
}
|
||||
|
||||
$vdir_folder = Get-Item $directory.PhysicalPath
|
||||
$folder = Get-Item $physical_path
|
||||
If($folder.FullName -ne $vdir_folder.FullName) {
|
||||
Set-ItemProperty $directory_path -name physicalPath -value $physical_path
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Fail-Json $result $_.Exception.Message
|
||||
}
|
||||
|
||||
# Result
|
||||
$directory = Get-WebVirtualDirectory -Site $site -Name $name
|
||||
$result.directory = @{
|
||||
PhysicalPath = $directory.PhysicalPath
|
||||
}
|
||||
|
||||
Exit-Json -obj $result
|
@ -1,75 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2015, Henrik Wallström <henrik@wallstroms.nu>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_iis_virtualdirectory
|
||||
version_added: "2.0"
|
||||
short_description: Configures a virtual directory in IIS
|
||||
description:
|
||||
- Creates, Removes and configures a virtual directory in IIS.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the virtual directory to create or remove.
|
||||
type: str
|
||||
required: yes
|
||||
state:
|
||||
description:
|
||||
- Whether to add or remove the specified virtual directory.
|
||||
- Removing will remove the virtual directory and all under it (Recursively).
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
site:
|
||||
description:
|
||||
- The site name under which the virtual directory is created or exists.
|
||||
type: str
|
||||
required: yes
|
||||
application:
|
||||
description:
|
||||
- The application under which the virtual directory is created or exists.
|
||||
type: str
|
||||
physical_path:
|
||||
description:
|
||||
- The physical path to the folder in which the new virtual directory is created.
|
||||
- The specified folder must already exist.
|
||||
type: str
|
||||
seealso:
|
||||
- module: win_iis_webapplication
|
||||
- module: win_iis_webapppool
|
||||
- module: win_iis_webbinding
|
||||
- module: win_iis_website
|
||||
author:
|
||||
- Henrik Wallström (@henrikwallstrom)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create a virtual directory if it does not exist
|
||||
win_iis_virtualdirectory:
|
||||
name: somedirectory
|
||||
site: somesite
|
||||
state: present
|
||||
physical_path: C:\virtualdirectory\some
|
||||
|
||||
- name: Remove a virtual directory if it exists
|
||||
win_iis_virtualdirectory:
|
||||
name: somedirectory
|
||||
site: somesite
|
||||
state: absent
|
||||
|
||||
- name: Create a virtual directory on an application if it does not exist
|
||||
win_iis_virtualdirectory:
|
||||
name: somedirectory
|
||||
site: somesite
|
||||
application: someapp
|
||||
state: present
|
||||
physical_path: C:\virtualdirectory\some
|
||||
'''
|
@ -1,138 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2015, Henrik Wallström <henrik@wallstroms.nu>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
|
||||
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
|
||||
$site = Get-AnsibleParam -obj $params -name "site" -type "str" -failifempty $true
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent","present"
|
||||
$physical_path = Get-AnsibleParam -obj $params -name "physical_path" -type "str" -aliases "path"
|
||||
$application_pool = Get-AnsibleParam -obj $params -name "application_pool" -type "str"
|
||||
$connect_as = Get-AnsibleParam -obj $params -name 'connect_as' -type 'str' -validateset 'specific_user', 'pass_through'
|
||||
$username = Get-AnsibleParam -obj $params -name "username" -type "str" -failifempty ($connect_as -eq 'specific_user')
|
||||
$password = Get-AnsibleParam -obj $params -name "password" -type "str" -failifempty ($connect_as -eq 'specific_user')
|
||||
|
||||
$result = @{
|
||||
application_pool = $application_pool
|
||||
changed = $false
|
||||
physical_path = $physical_path
|
||||
}
|
||||
|
||||
# Ensure WebAdministration module is loaded
|
||||
if ($null -eq (Get-Module "WebAdministration" -ErrorAction SilentlyContinue)) {
|
||||
Import-Module WebAdministration
|
||||
}
|
||||
|
||||
# Application info
|
||||
$application = Get-WebApplication -Site $site -Name $name
|
||||
$website = Get-Website -Name $site
|
||||
|
||||
# Set ApplicationPool to current if not specified
|
||||
if (!$application_pool) {
|
||||
$application_pool = $website.applicationPool
|
||||
}
|
||||
|
||||
try {
|
||||
# Add application
|
||||
if (($state -eq 'present') -and (-not $application)) {
|
||||
if (-not $physical_path) {
|
||||
Fail-Json $result "missing required arguments: path"
|
||||
}
|
||||
if (-not (Test-Path -Path $physical_path)) {
|
||||
Fail-Json $result "specified folder must already exist: path"
|
||||
}
|
||||
|
||||
$application_parameters = @{
|
||||
Name = $name
|
||||
PhysicalPath = $physical_path
|
||||
Site = $site
|
||||
}
|
||||
|
||||
if ($application_pool) {
|
||||
$application_parameters.ApplicationPool = $application_pool
|
||||
}
|
||||
|
||||
if (-not $check_mode) {
|
||||
$application = New-WebApplication @application_parameters -Force
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
# Remove application
|
||||
if ($state -eq 'absent' -and $application) {
|
||||
$application = Remove-WebApplication -Site $site -Name $name -WhatIf:$check_mode
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
$application = Get-WebApplication -Site $site -Name $name
|
||||
if ($application) {
|
||||
|
||||
# Change Physical Path if needed
|
||||
if ($physical_path) {
|
||||
if (-not (Test-Path -Path $physical_path)) {
|
||||
Fail-Json $result "specified folder must already exist: path"
|
||||
}
|
||||
|
||||
$app_folder = Get-Item $application.PhysicalPath
|
||||
$folder = Get-Item $physical_path
|
||||
if ($folder.FullName -ne $app_folder.FullName) {
|
||||
Set-ItemProperty "IIS:\Sites\$($site)\$($name)" -name physicalPath -value $physical_path -WhatIf:$check_mode
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Change Application Pool if needed
|
||||
if ($application_pool) {
|
||||
if ($application_pool -ne $application.applicationPool) {
|
||||
Set-ItemProperty "IIS:\Sites\$($site)\$($name)" -name applicationPool -value $application_pool -WhatIf:$check_mode
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Change username and password if needed
|
||||
$app_user = Get-ItemProperty -Path "IIS:\Sites\$($site)\$($name)" -Name 'userName'
|
||||
$app_pass = Get-ItemProperty -Path "IIS:\Sites\$($site)\$($name)" -Name 'password'
|
||||
if ($connect_as -eq 'pass_through') {
|
||||
if ($app_user -ne '') {
|
||||
Clear-ItemProperty -Path "IIS:\Sites\$($site)\$($name)" -Name 'userName' -WhatIf:$check_mode
|
||||
$result.changed = $true
|
||||
}
|
||||
if ($app_pass -ne '') {
|
||||
Clear-ItemProperty -Path "IIS:\Sites\$($site)\$($name)" -Name 'password' -WhatIf:$check_mode
|
||||
$result.changed = $true
|
||||
}
|
||||
} elseif ($connect_as -eq 'specific_user') {
|
||||
if ($app_user -ne $username) {
|
||||
Set-ItemProperty -Path "IIS:\Sites\$($site)\$($name)" -Name 'userName' -Value $username -WhatIf:$check_mode
|
||||
$result.changed = $true
|
||||
}
|
||||
if ($app_pass -ne $password) {
|
||||
Set-ItemProperty -Path "IIS:\Sites\$($site)\$($name)" -Name 'password' -Value $password -WhatIf:$check_mode
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Fail-Json $result $_.Exception.Message
|
||||
}
|
||||
|
||||
# When in check-mode or on removal, this may fail
|
||||
$application = Get-WebApplication -Site $site -Name $name
|
||||
if ($application) {
|
||||
$app_user = Get-ItemProperty -Path "IIS:\Sites\$($site)\$($name)" -Name 'userName'
|
||||
if ($app_user -eq '') {
|
||||
$result.connect_as = 'pass_through'
|
||||
} else {
|
||||
$result.connect_as = 'specific_user'
|
||||
}
|
||||
|
||||
$result.physical_path = $application.PhysicalPath
|
||||
$result.application_pool = $application.ApplicationPool
|
||||
}
|
||||
|
||||
Exit-Json $result
|
@ -1,99 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2015, Henrik Wallström <henrik@wallstroms.nu>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_iis_webapplication
|
||||
version_added: "2.0"
|
||||
short_description: Configures IIS web applications
|
||||
description:
|
||||
- Creates, removes, and configures IIS web applications.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the web application.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- Name of the site on which the application is created.
|
||||
type: str
|
||||
required: yes
|
||||
state:
|
||||
description:
|
||||
- State of the web application.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
physical_path:
|
||||
description:
|
||||
- The physical path on the remote host to use for the new application.
|
||||
- The specified folder must already exist.
|
||||
type: str
|
||||
application_pool:
|
||||
description:
|
||||
- The application pool in which the new site executes.
|
||||
- If not specified, the application pool of the current website will be used.
|
||||
type: str
|
||||
connect_as:
|
||||
description:
|
||||
- The type of authentication to use for this application. Either C(pass_through) or C(specific_user)
|
||||
- If C(pass_through), IIS will use the identity of the user or application pool identity to access the file system or network.
|
||||
- If C(specific_user), IIS will use the credentials provided in I(username) and I(password) to access the file system or network.
|
||||
type: str
|
||||
choices: [pass_through, specific_user]
|
||||
version_added: '2.10'
|
||||
username:
|
||||
description:
|
||||
- Specifies the user name of an account that can access configuration files and content for this application.
|
||||
- Required when I(connect_as) is set to C(specific_user).
|
||||
type: str
|
||||
version_added: '2.10'
|
||||
password:
|
||||
description:
|
||||
- The password associated with I(username).
|
||||
- Required when I(connect_as) is set to C(specific_user).
|
||||
type: str
|
||||
version_added: '2.10'
|
||||
seealso:
|
||||
- module: win_iis_virtualdirectory
|
||||
- module: win_iis_webapppool
|
||||
- module: win_iis_webbinding
|
||||
- module: win_iis_website
|
||||
author:
|
||||
- Henrik Wallström (@henrikwallstrom)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add ACME webapplication on IIS.
|
||||
win_iis_webapplication:
|
||||
name: api
|
||||
site: acme
|
||||
state: present
|
||||
physical_path: C:\apps\acme\api
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
application_pool:
|
||||
description: The used/implemented application_pool value.
|
||||
returned: success
|
||||
type: str
|
||||
sample: DefaultAppPool
|
||||
physical_path:
|
||||
description: The used/implemented physical_path value.
|
||||
returned: success
|
||||
type: str
|
||||
sample: C:\apps\acme\api
|
||||
connect_as:
|
||||
description: How IIS will try to authenticate to the physical_path.
|
||||
returned: when the application exists
|
||||
type: str
|
||||
sample: specific_user
|
||||
'''
|
@ -1,307 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2015, Henrik Wallström <henrik@wallstroms.nu>
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$params = Parse-Args -arguments $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -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 "started","restarted","stopped","absent","present"
|
||||
$result = @{
|
||||
changed = $false
|
||||
attributes = @{}
|
||||
info = @{
|
||||
name = $name
|
||||
state = $state
|
||||
attributes = @{}
|
||||
cpu = @{}
|
||||
failure = @{}
|
||||
processModel = @{}
|
||||
recycling = @{
|
||||
periodicRestart = @{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Stores the free form attributes for the module
|
||||
$attributes = @{}
|
||||
$input_attributes = Get-AnsibleParam -obj $params -name "attributes"
|
||||
if ($input_attributes) {
|
||||
if ($input_attributes -is [System.Collections.Hashtable]) {
|
||||
# Uses dict style parameters, newer and recommended style
|
||||
$attributes = $input_attributes
|
||||
} else {
|
||||
Fail-Json -obj $result -message "Using a string for the attributes parameter is not longer supported, please use a dict instead"
|
||||
}
|
||||
}
|
||||
$result.attributes = $attributes
|
||||
|
||||
Function Get-DotNetClassForAttribute($attribute_parent) {
|
||||
switch ($attribute_parent) {
|
||||
"attributes" { [Microsoft.Web.Administration.ApplicationPool] }
|
||||
"cpu" { [Microsoft.Web.Administration.ApplicationPoolCpu] }
|
||||
"failure" { [Microsoft.Web.Administration.ApplicationPoolFailure] }
|
||||
"processModel" { [Microsoft.Web.Administration.ApplicationPoolProcessModel] }
|
||||
"recycling" { [Microsoft.Web.Administration.ApplicationPoolRecycling] }
|
||||
default { [Microsoft.Web.Administration.ApplicationPool] }
|
||||
}
|
||||
}
|
||||
|
||||
Function Convert-CollectionToList($collection) {
|
||||
$list = @()
|
||||
|
||||
if ($collection -is [String]) {
|
||||
$raw_list = $collection -split ","
|
||||
foreach ($entry in $raw_list) {
|
||||
$list += $entry.Trim()
|
||||
}
|
||||
} elseif ($collection -is [Microsoft.IIs.PowerShell.Framework.ConfigurationElement]) {
|
||||
# the collection is the value from IIS itself, we need to conver accordingly
|
||||
foreach ($entry in $collection.Collection) {
|
||||
$list += $entry.Value.ToString()
|
||||
}
|
||||
} elseif ($collection -isnot [Array]) {
|
||||
$list += $collection
|
||||
} else {
|
||||
$list = $collection
|
||||
}
|
||||
|
||||
return ,$list
|
||||
}
|
||||
|
||||
Function Compare-Values($current, $new) {
|
||||
if ($null -eq $current) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($current -is [Array]) {
|
||||
if ($new -isnot [Array]) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($current.Count -ne $new.Count) {
|
||||
return $true
|
||||
}
|
||||
for ($i = 0; $i -lt $current.Count; $i++) {
|
||||
if ($current[$i] -ne $new[$i]) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($current -ne $new) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
Function Convert-ToPropertyValue($pool, $attribute_key, $attribute_value) {
|
||||
# Will convert the new value to the enum value expected and cast accordingly to the type
|
||||
if ([bool]($attribute_value.PSobject.Properties -match "Value")) {
|
||||
$attribute_value = $attribute_value.Value
|
||||
}
|
||||
$attribute_key_split = $attribute_key -split "\."
|
||||
if ($attribute_key_split.Length -eq 1) {
|
||||
$attribute_parent = "attributes"
|
||||
$attribute_child = $attribute_key
|
||||
$attribute_meta = $pool.Attributes | Where-Object { $_.Name -eq $attribute_child }
|
||||
} elseif ($attribute_key_split.Length -gt 1) {
|
||||
$attribute_parent = $attribute_key_split[0]
|
||||
$attribute_key_split = $attribute_key_split[1..$($attribute_key_split.Length - 1)]
|
||||
$parent = $pool.$attribute_parent
|
||||
|
||||
foreach ($key in $attribute_key_split) {
|
||||
$attribute_meta = $parent.Attributes | Where-Object { $_.Name -eq $key }
|
||||
$parent = $parent.$key
|
||||
if ($null -eq $attribute_meta) {
|
||||
$attribute_meta = $parent
|
||||
}
|
||||
}
|
||||
$attribute_child = $attribute_key_split[-1]
|
||||
}
|
||||
|
||||
if ($attribute_meta) {
|
||||
if (($attribute_meta.PSObject.Properties.Name -eq "Collection").Count -gt 0) {
|
||||
return ,(Convert-CollectionToList -collection $attribute_value)
|
||||
}
|
||||
$type = $attribute_meta.Schema.Type
|
||||
$value = $attribute_value
|
||||
if ($type -eq "enum") {
|
||||
# Attempt to convert the value from human friendly to enum value - use existing value if we fail
|
||||
$dot_net_class = Get-DotNetClassForAttribute -attribute_parent $attribute_parent
|
||||
$enum_attribute_name = $attribute_child.Substring(0,1).ToUpper() + $attribute_child.Substring(1)
|
||||
$enum = $dot_net_class.GetProperty($enum_attribute_name).PropertyType.FullName
|
||||
if ($enum) {
|
||||
$enum_values = [Enum]::GetValues($enum)
|
||||
foreach ($enum_value in $enum_values) {
|
||||
if ($attribute_value.GetType() -is $enum_value.GetType()) {
|
||||
if ($enum_value -eq $attribute_value) {
|
||||
$value = $enum_value
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if ([System.String]$enum_value -eq [System.String]$attribute_value) {
|
||||
$value = $enum_value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# Try and cast the variable using the chosen type, revert to the default if it fails
|
||||
Set-Variable -Name casted_value -Value ($value -as ([type] $attribute_meta.TypeName))
|
||||
if ($null -eq $casted_value) {
|
||||
$value
|
||||
} else {
|
||||
$casted_value
|
||||
}
|
||||
} else {
|
||||
$attribute_value
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure WebAdministration module is loaded
|
||||
if ($null -eq (Get-Module -Name "WebAdministration" -ErrorAction SilentlyContinue)) {
|
||||
Import-Module WebAdministration
|
||||
$web_admin_dll_path = Join-Path $env:SystemRoot system32\inetsrv\Microsoft.Web.Administration.dll
|
||||
Add-Type -Path $web_admin_dll_path
|
||||
}
|
||||
|
||||
$pool = Get-Item -Path IIS:\AppPools\$name -ErrorAction SilentlyContinue
|
||||
if ($state -eq "absent") {
|
||||
# Remove pool if present
|
||||
if ($pool) {
|
||||
try {
|
||||
Remove-WebAppPool -Name $name -WhatIf:$check_mode
|
||||
} catch {
|
||||
Fail-Json $result "Failed to remove Web App pool $($name): $($_.Exception.Message)"
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
} else {
|
||||
# Add pool if absent
|
||||
if (-not $pool) {
|
||||
if (-not $check_mode) {
|
||||
try {
|
||||
New-WebAppPool -Name $name > $null
|
||||
} catch {
|
||||
Fail-Json $result "Failed to create new Web App Pool $($name): $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
$result.changed = $true
|
||||
# If in check mode this pool won't actually exists so skip it
|
||||
if (-not $check_mode) {
|
||||
$pool = Get-Item -Path IIS:\AppPools\$name
|
||||
}
|
||||
}
|
||||
|
||||
# Cannot run the below in check mode if the pool did not always exist
|
||||
if ($pool) {
|
||||
# Modify pool based on parameters
|
||||
foreach ($attribute in $attributes.GetEnumerator()) {
|
||||
$attribute_key = $attribute.Name
|
||||
$new_raw_value = $attribute.Value
|
||||
$new_value = Convert-ToPropertyValue -pool $pool -attribute_key $attribute_key -attribute_value $new_raw_value
|
||||
|
||||
$current_raw_value = Get-ItemProperty -Path IIS:\AppPools\$name -Name $attribute_key -ErrorAction SilentlyContinue
|
||||
$current_value = Convert-ToPropertyValue -pool $pool -attribute_key $attribute_key -attribute_value $current_raw_value
|
||||
|
||||
$changed = Compare-Values -current $current_value -new $new_value
|
||||
if ($changed -eq $true) {
|
||||
if ($new_value -is [Array]) {
|
||||
try {
|
||||
Clear-ItemProperty -Path IIS:\AppPools\$name -Name $attribute_key -WhatIf:$check_mode
|
||||
} catch {
|
||||
Fail-Json -obj $result -message "Failed to clear attribute to Web App Pool $name. Attribute: $attribute_key, Exception: $($_.Exception.Message)"
|
||||
}
|
||||
foreach ($value in $new_value) {
|
||||
try {
|
||||
New-ItemProperty -Path IIS:\AppPools\$name -Name $attribute_key -Value @{value=$value} -WhatIf:$check_mode > $null
|
||||
} catch {
|
||||
Fail-Json -obj $result -message "Failed to add new attribute to Web App Pool $name. Attribute: $attribute_key, Value: $value, Exception: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Set-ItemProperty -Path IIS:\AppPools\$name -Name $attribute_key -Value $new_value -WhatIf:$check_mode
|
||||
} catch {
|
||||
Fail-Json $result "Failed to set attribute to Web App Pool $name. Attribute: $attribute_key, Value: $new_value, Exception: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Set the state of the pool
|
||||
if ($pool.State -eq "Stopped") {
|
||||
if ($state -eq "started" -or $state -eq "restarted") {
|
||||
if (-not $check_mode) {
|
||||
try {
|
||||
Start-WebAppPool -Name $name > $null
|
||||
} catch {
|
||||
Fail-Json $result "Failed to start Web App Pool $($name): $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
} else {
|
||||
if ($state -eq "stopped") {
|
||||
if (-not $check_mode) {
|
||||
try {
|
||||
Stop-WebAppPool -Name $name > $null
|
||||
} catch {
|
||||
Fail-Json $result "Failed to stop Web App Pool $($name): $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
$result.changed = $true
|
||||
} elseif ($state -eq "restarted") {
|
||||
if (-not $check_mode) {
|
||||
try {
|
||||
Restart-WebAppPool -Name $name > $null
|
||||
} catch {
|
||||
Fail-Json $result "Failed to restart Web App Pool $($name): $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Get all the current attributes for the pool
|
||||
$pool = Get-Item -Path IIS:\AppPools\$name -ErrorAction SilentlyContinue
|
||||
$elements = @("attributes", "cpu", "failure", "processModel", "recycling")
|
||||
|
||||
foreach ($element in $elements) {
|
||||
if ($element -eq "attributes") {
|
||||
$attribute_collection = $pool.Attributes
|
||||
$attribute_parent = $pool
|
||||
} else {
|
||||
$attribute_collection = $pool.$element.Attributes
|
||||
$attribute_parent = $pool.$element
|
||||
}
|
||||
|
||||
foreach ($attribute in $attribute_collection) {
|
||||
$attribute_name = $attribute.Name
|
||||
if ($attribute_name -notlike "*password*") {
|
||||
$attribute_value = $attribute_parent.$attribute_name
|
||||
|
||||
$result.info.$element.Add($attribute_name, $attribute_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Manually get the periodicRestart attributes in recycling
|
||||
foreach ($attribute in $pool.recycling.periodicRestart.Attributes) {
|
||||
$attribute_name = $attribute.Name
|
||||
$attribute_value = $pool.recycling.periodicRestart.$attribute_name
|
||||
$result.info.recycling.periodicRestart.Add($attribute_name, $attribute_value)
|
||||
}
|
||||
|
||||
Exit-Json $result
|
@ -1,211 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2015, Henrik Wallström <henrik@wallstroms.nu>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_iis_webapppool
|
||||
version_added: "2.0"
|
||||
short_description: Configure IIS Web Application Pools
|
||||
description:
|
||||
- Creates, removes and configures an IIS Web Application Pool.
|
||||
options:
|
||||
attributes:
|
||||
description:
|
||||
- This field is a free form dictionary value for the application pool
|
||||
attributes.
|
||||
- These attributes are based on the naming standard at
|
||||
U(https://www.iis.net/configreference/system.applicationhost/applicationpools/add#005),
|
||||
see the examples section for more details on how to set this.
|
||||
- You can also set the attributes of child elements like cpu and
|
||||
processModel, see the examples to see how it is done.
|
||||
- While you can use the numeric values for enums it is recommended to use
|
||||
the enum name itself, e.g. use SpecificUser instead of 3 for
|
||||
processModel.identityType.
|
||||
- managedPipelineMode may be either "Integrated" or "Classic".
|
||||
- startMode may be either "OnDemand" or "AlwaysRunning".
|
||||
- Use C(state) module parameter to modify the state of the app pool.
|
||||
- When trying to set 'processModel.password' and you receive a 'Value
|
||||
does fall within the expected range' error, you have a corrupted
|
||||
keystore. Please follow
|
||||
U(http://structuredsight.com/2014/10/26/im-out-of-range-youre-out-of-range/)
|
||||
to help fix your host.
|
||||
name:
|
||||
description:
|
||||
- Name of the application pool.
|
||||
type: str
|
||||
required: yes
|
||||
state:
|
||||
description:
|
||||
- The state of the application pool.
|
||||
- If C(absent) will ensure the app pool is removed.
|
||||
- If C(present) will ensure the app pool is configured and exists.
|
||||
- If C(restarted) will ensure the app pool exists and will restart, this
|
||||
is never idempotent.
|
||||
- If C(started) will ensure the app pool exists and is started.
|
||||
- If C(stopped) will ensure the app pool exists and is stopped.
|
||||
type: str
|
||||
choices: [ absent, present, restarted, started, stopped ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: win_iis_virtualdirectory
|
||||
- module: win_iis_webapplication
|
||||
- module: win_iis_webbinding
|
||||
- module: win_iis_website
|
||||
author:
|
||||
- Henrik Wallström (@henrikwallstrom)
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Return information about an existing application pool
|
||||
win_iis_webapppool:
|
||||
name: DefaultAppPool
|
||||
state: present
|
||||
|
||||
- name: Create a new application pool in 'Started' state
|
||||
win_iis_webapppool:
|
||||
name: AppPool
|
||||
state: started
|
||||
|
||||
- name: Stop an application pool
|
||||
win_iis_webapppool:
|
||||
name: AppPool
|
||||
state: stopped
|
||||
|
||||
- name: Restart an application pool (non-idempotent)
|
||||
win_iis_webapppool:
|
||||
name: AppPool
|
||||
state: restarted
|
||||
|
||||
- name: Change application pool attributes using new dict style
|
||||
win_iis_webapppool:
|
||||
name: AppPool
|
||||
attributes:
|
||||
managedRuntimeVersion: v4.0
|
||||
autoStart: no
|
||||
|
||||
- name: Creates an application pool, sets attributes and starts it
|
||||
win_iis_webapppool:
|
||||
name: AnotherAppPool
|
||||
state: started
|
||||
attributes:
|
||||
managedRuntimeVersion: v4.0
|
||||
autoStart: no
|
||||
|
||||
# In the below example we are setting attributes in child element processModel
|
||||
# https://www.iis.net/configreference/system.applicationhost/applicationpools/add/processmodel
|
||||
- name: Manage child element and set identity of application pool
|
||||
win_iis_webapppool:
|
||||
name: IdentitiyAppPool
|
||||
state: started
|
||||
attributes:
|
||||
managedPipelineMode: Classic
|
||||
processModel.identityType: SpecificUser
|
||||
processModel.userName: '{{ansible_user}}'
|
||||
processModel.password: '{{ansible_password}}'
|
||||
processModel.loadUserProfile: true
|
||||
|
||||
- name: Manage a timespan attribute
|
||||
win_iis_webapppool:
|
||||
name: TimespanAppPool
|
||||
state: started
|
||||
attributes:
|
||||
# Timespan with full string "day:hour:minute:second.millisecond"
|
||||
recycling.periodicRestart.time: "00:00:05:00.000000"
|
||||
recycling.periodicRestart.schedule: ["00:10:00", "05:30:00"]
|
||||
# Shortened timespan "hour:minute:second"
|
||||
processModel.pingResponseTime: "00:03:00"
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
attributes:
|
||||
description: Application Pool attributes that were set and processed by this
|
||||
module invocation.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
enable32BitAppOnWin64: "true"
|
||||
managedRuntimeVersion: "v4.0"
|
||||
managedPipelineMode: "Classic"
|
||||
info:
|
||||
description: Information on current state of the Application Pool. See
|
||||
https://www.iis.net/configreference/system.applicationhost/applicationpools/add#005
|
||||
for the full list of return attributes based on your IIS version.
|
||||
returned: success
|
||||
type: complex
|
||||
sample:
|
||||
contains:
|
||||
attributes:
|
||||
description: Key value pairs showing the current Application Pool attributes.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
autoStart: true
|
||||
managedRuntimeLoader: "webengine4.dll"
|
||||
managedPipelineMode: "Classic"
|
||||
name: "DefaultAppPool"
|
||||
CLRConfigFile: ""
|
||||
passAnonymousToken: true
|
||||
applicationPoolSid: "S-1-5-82-1352790163-598702362-1775843902-1923651883-1762956711"
|
||||
queueLength: 1000
|
||||
managedRuntimeVersion: "v4.0"
|
||||
state: "Started"
|
||||
enableConfigurationOverride: true
|
||||
startMode: "OnDemand"
|
||||
enable32BitAppOnWin64: true
|
||||
cpu:
|
||||
description: Key value pairs showing the current Application Pool cpu attributes.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
action: "NoAction"
|
||||
limit: 0
|
||||
resetInterval:
|
||||
Days: 0
|
||||
Hours: 0
|
||||
failure:
|
||||
description: Key value pairs showing the current Application Pool failure attributes.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
autoShutdownExe: ""
|
||||
orphanActionExe: ""
|
||||
rapidFailProtextionInterval:
|
||||
Days: 0
|
||||
Hours: 0
|
||||
name:
|
||||
description: Name of Application Pool that was processed by this module invocation.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "DefaultAppPool"
|
||||
processModel:
|
||||
description: Key value pairs showing the current Application Pool processModel attributes.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
identityType: "ApplicationPoolIdentity"
|
||||
logonType: "LogonBatch"
|
||||
pingInterval:
|
||||
Days: 0
|
||||
Hours: 0
|
||||
recycling:
|
||||
description: Key value pairs showing the current Application Pool recycling attributes.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
disallowOverlappingRotation: false
|
||||
disallowRotationOnConfigChange: false
|
||||
logEventOnRecycle: "Time,Requests,Schedule,Memory,IsapiUnhealthy,OnDemand,ConfigChange,PrivateMemory"
|
||||
state:
|
||||
description: Current runtime state of the pool as the module completed.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "Started"
|
||||
'''
|
@ -1,377 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Noah Sparks <nsparks@outlook.com>
|
||||
# Copyright: (c) 2015, Henrik Wallström <henrik@wallstroms.nu>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$params = Parse-Args -arguments $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
|
||||
$name = Get-AnsibleParam $params -name "name" -type str -failifempty $true -aliases 'website'
|
||||
$state = Get-AnsibleParam $params "state" -default "present" -validateSet "present","absent"
|
||||
$host_header = Get-AnsibleParam $params -name "host_header" -type str
|
||||
$protocol = Get-AnsibleParam $params -name "protocol" -type str -default 'http'
|
||||
$port = Get-AnsibleParam $params -name "port" -default '80'
|
||||
$ip = Get-AnsibleParam $params -name "ip" -default '*'
|
||||
$certificateHash = Get-AnsibleParam $params -name "certificate_hash" -type str -default ([string]::Empty)
|
||||
$certificateStoreName = Get-AnsibleParam $params -name "certificate_store_name" -type str -default ([string]::Empty)
|
||||
$sslFlags = Get-AnsibleParam $params -name "ssl_flags" -default '0' -ValidateSet '0','1','2','3'
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
|
||||
#################
|
||||
### Functions ###
|
||||
#################
|
||||
function Create-BindingInfo {
|
||||
$ht = @{
|
||||
'bindingInformation' = $args[0].bindingInformation
|
||||
'ip' = $args[0].bindingInformation.split(':')[0]
|
||||
'port' = [int]$args[0].bindingInformation.split(':')[1]
|
||||
'hostheader' = $args[0].bindingInformation.split(':')[2]
|
||||
#'isDsMapperEnabled' = $args[0].isDsMapperEnabled
|
||||
'protocol' = $args[0].protocol
|
||||
'certificateStoreName' = $args[0].certificateStoreName
|
||||
'certificateHash' = $args[0].certificateHash
|
||||
}
|
||||
|
||||
#handle sslflag support
|
||||
If ([version][System.Environment]::OSVersion.Version -lt [version]'6.2')
|
||||
{
|
||||
$ht.sslFlags = 'not supported'
|
||||
}
|
||||
Else
|
||||
{
|
||||
$ht.sslFlags = [int]$args[0].sslFlags
|
||||
}
|
||||
|
||||
Return $ht
|
||||
}
|
||||
|
||||
# Used instead of get-webbinding to ensure we always return a single binding
|
||||
# We can't filter properly with get-webbinding...ex get-webbinding ip * returns all bindings
|
||||
# pass it $binding_parameters hashtable
|
||||
function Get-SingleWebBinding {
|
||||
|
||||
Try {
|
||||
$site_bindings = get-webbinding -name $args[0].name
|
||||
}
|
||||
Catch {
|
||||
# 2k8r2 throws this error when you run get-webbinding with no bindings in iis
|
||||
If (-not $_.Exception.Message.CompareTo('Cannot process argument because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value'))
|
||||
{
|
||||
Throw $_.Exception.Message
|
||||
}
|
||||
Else { return }
|
||||
}
|
||||
|
||||
Foreach ($binding in $site_bindings)
|
||||
{
|
||||
$splits = $binding.bindingInformation -split ':'
|
||||
|
||||
if (
|
||||
$args[0].protocol -eq $binding.protocol -and
|
||||
$args[0].ipaddress -eq $splits[0] -and
|
||||
$args[0].port -eq $splits[1] -and
|
||||
$args[0].hostheader -eq $splits[2]
|
||||
)
|
||||
{
|
||||
Return $binding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#############################
|
||||
### Pre-Action Validation ###
|
||||
#############################
|
||||
$os_version = [version][System.Environment]::OSVersion.Version
|
||||
|
||||
# Ensure WebAdministration module is loaded
|
||||
If ($os_version -lt [version]'6.1')
|
||||
{
|
||||
Try {
|
||||
Add-PSSnapin WebAdministration
|
||||
}
|
||||
Catch {
|
||||
Fail-Json -obj $result -message "The WebAdministration snap-in is not present. Please make sure it is installed."
|
||||
}
|
||||
}
|
||||
Else
|
||||
{
|
||||
Try {
|
||||
Import-Module WebAdministration
|
||||
}
|
||||
Catch {
|
||||
Fail-Json -obj $result -message "Failed to load WebAdministration module. Is IIS installed? $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# ensure website targetted exists. -Name filter doesn't work on 2k8r2 so do where-object instead
|
||||
$website_check = get-website | Where-Object {$_.name -eq $name}
|
||||
If (-not $website_check)
|
||||
{
|
||||
Fail-Json -obj $result -message "Unable to retrieve website with name $Name. Make sure the website name is valid and exists."
|
||||
}
|
||||
|
||||
# if OS older than 2012 (6.2) and ssl flags are set, fail. Otherwise toggle sni_support
|
||||
If ($os_version -lt [version]'6.2')
|
||||
{
|
||||
If ($sslFlags -ne 0)
|
||||
{
|
||||
Fail-Json -obj $result -message "SNI and Certificate Store support is not available for systems older than 2012 (6.2)"
|
||||
}
|
||||
$sni_support = $false #will cause the sslflags check later to skip
|
||||
}
|
||||
Else
|
||||
{
|
||||
$sni_support = $true
|
||||
}
|
||||
|
||||
# make sure ssl flags only specified with https protocol
|
||||
If ($protocol -ne 'https' -and $sslFlags -gt 0)
|
||||
{
|
||||
Fail-Json -obj $result -message "SSLFlags can only be set for HTTPS protocol"
|
||||
}
|
||||
|
||||
# validate certificate details if provided
|
||||
# we don't do anything with cert on state: absent, so only validate present
|
||||
If ($certificateHash -and $state -eq 'present')
|
||||
{
|
||||
If ($protocol -ne 'https')
|
||||
{
|
||||
Fail-Json -obj $result -message "You can only provide a certificate thumbprint when protocol is set to https"
|
||||
}
|
||||
|
||||
#apply default for cert store name
|
||||
If (-Not $certificateStoreName)
|
||||
{
|
||||
$certificateStoreName = 'my'
|
||||
}
|
||||
|
||||
#validate cert path
|
||||
$cert_path = "cert:\LocalMachine\$certificateStoreName\$certificateHash"
|
||||
If (-Not (Test-Path $cert_path) )
|
||||
{
|
||||
Fail-Json -obj $result -message "Unable to locate certificate at $cert_path"
|
||||
}
|
||||
}
|
||||
|
||||
# make sure binding info is valid for central cert store if sslflags -gt 1
|
||||
If ($sslFlags -gt 1 -and ($certificateHash -ne [string]::Empty -or $certificateStoreName -ne [string]::Empty))
|
||||
{
|
||||
Fail-Json -obj $result -message "You set sslFlags to $sslFlags. This indicates you wish to use the Central Certificate Store feature.
|
||||
This cannot be used in combination with certficiate_hash and certificate_store_name. When using the Central Certificate Store feature,
|
||||
the certificate is automatically retrieved from the store rather than manually assigned to the binding."
|
||||
}
|
||||
|
||||
# disallow host_header: '*'
|
||||
If ($host_header -eq '*')
|
||||
{
|
||||
Fail-Json -obj $result -message "To make or remove a catch-all binding, please omit the host_header parameter entirely rather than specify host_header *"
|
||||
}
|
||||
|
||||
##########################
|
||||
### start action items ###
|
||||
##########################
|
||||
|
||||
# create binding search splat
|
||||
$binding_parameters = @{
|
||||
Name = $name
|
||||
Protocol = $protocol
|
||||
Port = $port
|
||||
IPAddress = $ip
|
||||
}
|
||||
|
||||
# insert host header to search if specified, otherwise it will return * (all bindings matching protocol/ip)
|
||||
If ($host_header)
|
||||
{
|
||||
$binding_parameters.HostHeader = $host_header
|
||||
}
|
||||
Else
|
||||
{
|
||||
$binding_parameters.HostHeader = [string]::Empty
|
||||
}
|
||||
|
||||
# Get bindings matching parameters
|
||||
Try {
|
||||
$current_bindings = Get-SingleWebBinding $binding_parameters
|
||||
}
|
||||
Catch {
|
||||
Fail-Json -obj $result -message "Failed to retrieve bindings with Get-SingleWebBinding - $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
################################################
|
||||
### Remove binding or exit if already absent ###
|
||||
################################################
|
||||
If ($current_bindings -and $state -eq 'absent')
|
||||
{
|
||||
Try {
|
||||
#there is a bug in this method that will result in all bindings being removed if the IP in $current_bindings is a *
|
||||
#$current_bindings | Remove-WebBinding -verbose -WhatIf:$check_mode
|
||||
|
||||
#another method that did not work. It kept failing to match on element and removed everything.
|
||||
#$element = @{protocol="$protocol";bindingInformation="$ip`:$port`:$host_header"}
|
||||
#Remove-WebconfigurationProperty -filter $current_bindings.ItemXPath -Name Bindings.collection -AtElement $element -WhatIf #:$check_mode
|
||||
|
||||
#this method works
|
||||
[array]$bindings = Get-WebconfigurationProperty -filter $current_bindings.ItemXPath -Name Bindings.collection
|
||||
|
||||
$index = Foreach ($item in $bindings) {
|
||||
If ( $protocol -eq $item.protocol -and $current_bindings.bindingInformation -eq $item.bindingInformation ) {
|
||||
$bindings.indexof($item)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Remove-WebconfigurationProperty -filter $current_bindings.ItemXPath -Name Bindings.collection -AtIndex $index -WhatIf:$check_mode
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
Catch {
|
||||
Fail-Json -obj $result -message "Failed to remove the binding from IIS - $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# removing bindings from iis may not also remove them from iis:\sslbindings
|
||||
|
||||
$result.operation_type = 'removed'
|
||||
$result.binding_info = $current_bindings | ForEach-Object {Create-BindingInfo $_}
|
||||
Exit-Json -obj $result
|
||||
}
|
||||
ElseIf (-Not $current_bindings -and $state -eq 'absent')
|
||||
{
|
||||
# exit changed: false since it's already gone
|
||||
Exit-Json -obj $result
|
||||
}
|
||||
|
||||
|
||||
################################
|
||||
### Modify existing bindings ###
|
||||
################################
|
||||
<#
|
||||
since we have already have the parameters available to get-webbinding,
|
||||
we just need to check here for the ones that are not available which are the
|
||||
ssl settings (hash, store, sslflags). If they aren't set we update here, or
|
||||
exit with changed: false
|
||||
#>
|
||||
ElseIf ($current_bindings)
|
||||
{
|
||||
#ran into a strange edge case in testing where I was able to retrieve bindings but not expand all the properties
|
||||
#when adding a self-signed wildcard cert to a binding. it seemed to permanently break the binding. only removing it
|
||||
#would cause the error to stop.
|
||||
Try {
|
||||
$null = $current_bindings | Select-Object *
|
||||
}
|
||||
Catch {
|
||||
Fail-Json -obj $result -message "Found a matching binding, but failed to expand it's properties (get-binding | FL *). In testing, this was caused by using a self-signed wildcard certificate. $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# check if there is a match on the ssl parameters
|
||||
If ( ($current_bindings.sslFlags -ne $sslFlags -and $sni_support) -or
|
||||
$current_bindings.certificateHash -ne $certificateHash -or
|
||||
$current_bindings.certificateStoreName -ne $certificateStoreName)
|
||||
{
|
||||
# match/update SNI
|
||||
If ($current_bindings.sslFlags -ne $sslFlags -and $sni_support)
|
||||
{
|
||||
Try {
|
||||
Set-WebBinding -Name $name -IPAddress $ip -Port $port -HostHeader $host_header -PropertyName sslFlags -value $sslFlags -whatif:$check_mode
|
||||
$result.changed = $true
|
||||
}
|
||||
Catch {
|
||||
Fail-Json -obj $result -message "Failed to update sslFlags on binding - $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# Refresh the binding object since it has been changed
|
||||
Try {
|
||||
$current_bindings = Get-SingleWebBinding $binding_parameters
|
||||
}
|
||||
Catch {
|
||||
Fail-Json -obj $result -message "Failed to refresh bindings after setting sslFlags - $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
# match/update certificate
|
||||
If ($current_bindings.certificateHash -ne $certificateHash -or $current_bindings.certificateStoreName -ne $certificateStoreName)
|
||||
{
|
||||
If (-Not $check_mode)
|
||||
{
|
||||
Try {
|
||||
$current_bindings.AddSslCertificate($certificateHash,$certificateStoreName)
|
||||
}
|
||||
Catch {
|
||||
Fail-Json -obj $result -message "Failed to set new SSL certificate - $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
$result.changed = $true
|
||||
$result.operation_type = 'updated'
|
||||
$result.website_state = (Get-Website | Where-Object {$_.Name -eq $Name}).State
|
||||
$result.binding_info = Create-BindingInfo (Get-SingleWebBinding $binding_parameters)
|
||||
Exit-Json -obj $result #exit changed true
|
||||
}
|
||||
Else
|
||||
{
|
||||
$result.operation_type = 'matched'
|
||||
$result.website_state = (Get-Website | Where-Object {$_.Name -eq $Name}).State
|
||||
$result.binding_info = Create-BindingInfo (Get-SingleWebBinding $binding_parameters)
|
||||
Exit-Json -obj $result #exit changed false
|
||||
}
|
||||
}
|
||||
|
||||
########################
|
||||
### Add new bindings ###
|
||||
########################
|
||||
ElseIf (-not $current_bindings -and $state -eq 'present')
|
||||
{
|
||||
# add binding. this creates the binding, but does not apply a certificate to it.
|
||||
Try
|
||||
{
|
||||
If (-not $check_mode)
|
||||
{
|
||||
If ($sni_support)
|
||||
{
|
||||
New-WebBinding @binding_parameters -SslFlags $sslFlags -Force
|
||||
}
|
||||
Else
|
||||
{
|
||||
New-WebBinding @binding_parameters -Force
|
||||
}
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
Catch
|
||||
{
|
||||
$result.website_state = (Get-Website | Where-Object {$_.Name -eq $Name}).State
|
||||
Fail-Json -obj $result -message "Failed at creating new binding (note: creating binding and adding ssl are separate steps) - $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# add certificate to binding
|
||||
If ($certificateHash -and -not $check_mode)
|
||||
{
|
||||
Try {
|
||||
#$new_binding = get-webbinding -Name $name -IPAddress $ip -port $port -Protocol $protocol -hostheader $host_header
|
||||
$new_binding = Get-SingleWebBinding $binding_parameters
|
||||
$new_binding.addsslcertificate($certificateHash,$certificateStoreName)
|
||||
}
|
||||
Catch {
|
||||
$result.website_state = (Get-Website | Where-Object {$_.Name -eq $Name}).State
|
||||
Fail-Json -obj $result -message "Failed to set new SSL certificate - $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
$result.changed = $true
|
||||
$result.operation_type = 'added'
|
||||
$result.website_state = (Get-Website | Where-Object {$_.Name -eq $Name}).State
|
||||
|
||||
# incase there are no bindings we do a check before calling Create-BindingInfo
|
||||
$web_binding = Get-SingleWebBinding $binding_parameters
|
||||
if ($web_binding) {
|
||||
$result.binding_info = Create-BindingInfo $web_binding
|
||||
} else {
|
||||
$result.binding_info = $null
|
||||
}
|
||||
Exit-Json $result
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Noah Sparks <nsparks@outlook.com>
|
||||
# Copyright: (c) 2017, Henrik Wallström <henrik@wallstroms.nu>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_iis_webbinding
|
||||
version_added: "2.0"
|
||||
short_description: Configures a IIS Web site binding
|
||||
description:
|
||||
- Creates, removes and configures a binding to an existing IIS Web site.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Names of web site.
|
||||
type: str
|
||||
required: yes
|
||||
aliases: [ website ]
|
||||
state:
|
||||
description:
|
||||
- State of the binding.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
port:
|
||||
description:
|
||||
- The port to bind to / use for the new site.
|
||||
type: str
|
||||
default: 80
|
||||
ip:
|
||||
description:
|
||||
- The IP address to bind to / use for the new site.
|
||||
type: str
|
||||
default: '*'
|
||||
host_header:
|
||||
description:
|
||||
- The host header to bind to / use for the new site.
|
||||
- If you are creating/removing a catch-all binding, omit this parameter rather than defining it as '*'.
|
||||
type: str
|
||||
protocol:
|
||||
description:
|
||||
- The protocol to be used for the Web binding (usually HTTP, HTTPS, or FTP).
|
||||
type: str
|
||||
default: http
|
||||
certificate_hash:
|
||||
description:
|
||||
- Certificate hash (thumbprint) for the SSL binding. The certificate hash is the unique identifier for the certificate.
|
||||
type: str
|
||||
certificate_store_name:
|
||||
description:
|
||||
- Name of the certificate store where the certificate for the binding is located.
|
||||
type: str
|
||||
default: my
|
||||
ssl_flags:
|
||||
description:
|
||||
- This parameter is only valid on Server 2012 and newer.
|
||||
- Primarily used for enabling and disabling server name indication (SNI).
|
||||
- Set to C(0) to disable SNI.
|
||||
- Set to C(1) to enable SNI.
|
||||
type: str
|
||||
version_added: "2.5"
|
||||
seealso:
|
||||
- module: win_iis_virtualdirectory
|
||||
- module: win_iis_webapplication
|
||||
- module: win_iis_webapppool
|
||||
- module: win_iis_website
|
||||
author:
|
||||
- Noah Sparks (@nwsparks)
|
||||
- Henrik Wallström (@henrikwallstrom)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a HTTP binding on port 9090
|
||||
win_iis_webbinding:
|
||||
name: Default Web Site
|
||||
port: 9090
|
||||
state: present
|
||||
|
||||
- name: Remove the HTTP binding on port 9090
|
||||
win_iis_webbinding:
|
||||
name: Default Web Site
|
||||
port: 9090
|
||||
state: absent
|
||||
|
||||
- name: Remove the default http binding
|
||||
win_iis_webbinding:
|
||||
name: Default Web Site
|
||||
port: 80
|
||||
ip: '*'
|
||||
state: absent
|
||||
|
||||
- name: Add a HTTPS binding
|
||||
win_iis_webbinding:
|
||||
name: Default Web Site
|
||||
protocol: https
|
||||
port: 443
|
||||
ip: 127.0.0.1
|
||||
certificate_hash: B0D0FA8408FC67B230338FCA584D03792DA73F4C
|
||||
state: present
|
||||
|
||||
- name: Add a HTTPS binding with host header and SNI enabled
|
||||
win_iis_webbinding:
|
||||
name: Default Web Site
|
||||
protocol: https
|
||||
port: 443
|
||||
host_header: test.com
|
||||
ssl_flags: 1
|
||||
certificate_hash: D1A3AF8988FD32D1A3AF8988FD323792DA73F4C
|
||||
state: present
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
website_state:
|
||||
description:
|
||||
- The state of the website being targetted
|
||||
- Can be helpful in case you accidentally cause a binding collision
|
||||
which can result in the targetted site being stopped
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Started"
|
||||
version_added: "2.5"
|
||||
operation_type:
|
||||
description:
|
||||
- The type of operation performed
|
||||
- Can be removed, updated, matched, or added
|
||||
returned: on success
|
||||
type: str
|
||||
sample: "removed"
|
||||
version_added: "2.5"
|
||||
binding_info:
|
||||
description:
|
||||
- Information on the binding being manipulated
|
||||
returned: on success
|
||||
type: dict
|
||||
sample: |-
|
||||
"binding_info": {
|
||||
"bindingInformation": "127.0.0.1:443:",
|
||||
"certificateHash": "FF3910CE089397F1B5A77EB7BAFDD8F44CDE77DD",
|
||||
"certificateStoreName": "MY",
|
||||
"hostheader": "",
|
||||
"ip": "127.0.0.1",
|
||||
"port": 443,
|
||||
"protocol": "https",
|
||||
"sslFlags": "not supported"
|
||||
}
|
||||
version_added: "2.5"
|
||||
'''
|
@ -1,180 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2015, Henrik Wallström <henrik@wallstroms.nu>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$params = Parse-Args $args
|
||||
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
|
||||
$application_pool = Get-AnsibleParam -obj $params -name "application_pool" -type "str"
|
||||
$physical_path = Get-AnsibleParam -obj $params -name "physical_path" -type "str"
|
||||
$site_id = Get-AnsibleParam -obj $params -name "site_id" -type "str"
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -validateset "absent","restarted","started","stopped"
|
||||
|
||||
# Binding Parameters
|
||||
$bind_port = Get-AnsibleParam -obj $params -name "port" -type "int"
|
||||
$bind_ip = Get-AnsibleParam -obj $params -name "ip" -type "str"
|
||||
$bind_hostname = Get-AnsibleParam -obj $params -name "hostname" -type "str"
|
||||
|
||||
# Custom site Parameters from string where properties
|
||||
# are separated by a pipe and property name/values by colon.
|
||||
# Ex. "foo:1|bar:2"
|
||||
$parameters = Get-AnsibleParam -obj $params -name "parameters" -type "str"
|
||||
if($null -ne $parameters) {
|
||||
$parameters = @($parameters -split '\|' | ForEach-Object {
|
||||
return ,($_ -split "\:", 2);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
# Ensure WebAdministration module is loaded
|
||||
if ($null -eq (Get-Module "WebAdministration" -ErrorAction SilentlyContinue)) {
|
||||
Import-Module WebAdministration
|
||||
}
|
||||
|
||||
# Result
|
||||
$result = @{
|
||||
site = @{}
|
||||
changed = $false
|
||||
}
|
||||
|
||||
# Site info
|
||||
$site = Get-Website | Where-Object { $_.Name -eq $name }
|
||||
|
||||
Try {
|
||||
# Add site
|
||||
If(($state -ne 'absent') -and (-not $site)) {
|
||||
If (-not $physical_path) {
|
||||
Fail-Json -obj $result -message "missing required arguments: physical_path"
|
||||
}
|
||||
ElseIf (-not (Test-Path $physical_path)) {
|
||||
Fail-Json -obj $result -message "specified folder must already exist: physical_path"
|
||||
}
|
||||
|
||||
$site_parameters = @{
|
||||
Name = $name
|
||||
PhysicalPath = $physical_path
|
||||
}
|
||||
|
||||
If ($application_pool) {
|
||||
$site_parameters.ApplicationPool = $application_pool
|
||||
}
|
||||
|
||||
If ($site_id) {
|
||||
$site_parameters.ID = $site_id
|
||||
}
|
||||
|
||||
If ($bind_port) {
|
||||
$site_parameters.Port = $bind_port
|
||||
}
|
||||
|
||||
If ($bind_ip) {
|
||||
$site_parameters.IPAddress = $bind_ip
|
||||
}
|
||||
|
||||
If ($bind_hostname) {
|
||||
$site_parameters.HostHeader = $bind_hostname
|
||||
}
|
||||
|
||||
# Fix for error "New-Item : Index was outside the bounds of the array."
|
||||
# This is a bug in the New-WebSite commandlet. Apparently there must be at least one site configured in IIS otherwise New-WebSite crashes.
|
||||
# For more details, see http://stackoverflow.com/questions/3573889/ps-c-new-website-blah-throws-index-was-outside-the-bounds-of-the-array
|
||||
$sites_list = get-childitem -Path IIS:\sites
|
||||
if ($null -eq $sites_list) {
|
||||
if ($site_id) {
|
||||
$site_parameters.ID = $site_id
|
||||
} else {
|
||||
$site_parameters.ID = 1
|
||||
}
|
||||
}
|
||||
|
||||
$site = New-Website @site_parameters -Force
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
# Remove site
|
||||
If ($state -eq 'absent' -and $site) {
|
||||
$site = Remove-Website -Name $name
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
$site = Get-Website | Where-Object { $_.Name -eq $name }
|
||||
If($site) {
|
||||
# Change Physical Path if needed
|
||||
if($physical_path) {
|
||||
If (-not (Test-Path $physical_path)) {
|
||||
Fail-Json -obj $result -message "specified folder must already exist: physical_path"
|
||||
}
|
||||
|
||||
$folder = Get-Item $physical_path
|
||||
If($folder.FullName -ne $site.PhysicalPath) {
|
||||
Set-ItemProperty "IIS:\Sites\$($site.Name)" -name physicalPath -value $folder.FullName
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Change Application Pool if needed
|
||||
if($application_pool) {
|
||||
If($application_pool -ne $site.applicationPool) {
|
||||
Set-ItemProperty "IIS:\Sites\$($site.Name)" -name applicationPool -value $application_pool
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Set properties
|
||||
if($parameters) {
|
||||
$parameters | ForEach-Object {
|
||||
$property_value = Get-ItemProperty "IIS:\Sites\$($site.Name)" $_[0]
|
||||
|
||||
switch ($property_value.GetType().Name)
|
||||
{
|
||||
"ConfigurationAttribute" { $parameter_value = $property_value.value }
|
||||
"String" { $parameter_value = $property_value }
|
||||
}
|
||||
|
||||
if((-not $parameter_value) -or ($parameter_value) -ne $_[1]) {
|
||||
Set-ItemProperty -LiteralPath "IIS:\Sites\$($site.Name)" $_[0] $_[1]
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Set run state
|
||||
if ((($state -eq 'stopped') -or ($state -eq 'restarted')) -and ($site.State -eq 'Started'))
|
||||
{
|
||||
Stop-Website -Name $name -ErrorAction Stop
|
||||
$result.changed = $true
|
||||
}
|
||||
if ((($state -eq 'started') -and ($site.State -eq 'Stopped')) -or ($state -eq 'restarted'))
|
||||
{
|
||||
Start-Website -Name $name -ErrorAction Stop
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
Catch
|
||||
{
|
||||
Fail-Json -obj $result -message $_.Exception.Message
|
||||
}
|
||||
|
||||
if ($state -ne 'absent')
|
||||
{
|
||||
$site = Get-Website | Where-Object { $_.Name -eq $name }
|
||||
}
|
||||
|
||||
if ($site)
|
||||
{
|
||||
$result.site = @{
|
||||
Name = $site.Name
|
||||
ID = $site.ID
|
||||
State = $site.State
|
||||
PhysicalPath = $site.PhysicalPath
|
||||
ApplicationPool = $site.applicationPool
|
||||
Bindings = @($site.Bindings.Collection | ForEach-Object { $_.BindingInformation })
|
||||
}
|
||||
}
|
||||
|
||||
Exit-Json -obj $result
|
@ -1,121 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2015, Henrik Wallström <henrik@wallstroms.nu>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_iis_website
|
||||
version_added: "2.0"
|
||||
short_description: Configures a IIS Web site
|
||||
description:
|
||||
- Creates, Removes and configures a IIS Web site.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Names of web site.
|
||||
type: str
|
||||
required: yes
|
||||
site_id:
|
||||
description:
|
||||
- Explicitly set the IIS numeric ID for a site.
|
||||
- Note that this value cannot be changed after the website has been created.
|
||||
type: str
|
||||
version_added: "2.1"
|
||||
state:
|
||||
description:
|
||||
- State of the web site
|
||||
type: str
|
||||
choices: [ absent, started, stopped, restarted ]
|
||||
physical_path:
|
||||
description:
|
||||
- The physical path on the remote host to use for the new site.
|
||||
- The specified folder must already exist.
|
||||
type: str
|
||||
application_pool:
|
||||
description:
|
||||
- The application pool in which the new site executes.
|
||||
type: str
|
||||
port:
|
||||
description:
|
||||
- The port to bind to / use for the new site.
|
||||
type: int
|
||||
ip:
|
||||
description:
|
||||
- The IP address to bind to / use for the new site.
|
||||
type: str
|
||||
hostname:
|
||||
description:
|
||||
- The host header to bind to / use for the new site.
|
||||
type: str
|
||||
ssl:
|
||||
description:
|
||||
- Enables HTTPS binding on the site..
|
||||
type: str
|
||||
parameters:
|
||||
description:
|
||||
- Custom site Parameters from string where properties are separated by a pipe and property name/values by colon Ex. "foo:1|bar:2"
|
||||
type: str
|
||||
seealso:
|
||||
- module: win_iis_virtualdirectory
|
||||
- module: win_iis_webapplication
|
||||
- module: win_iis_webapppool
|
||||
- module: win_iis_webbinding
|
||||
author:
|
||||
- Henrik Wallström (@henrikwallstrom)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
|
||||
# Start a website
|
||||
|
||||
- name: Acme IIS site
|
||||
win_iis_website:
|
||||
name: Acme
|
||||
state: started
|
||||
port: 80
|
||||
ip: 127.0.0.1
|
||||
hostname: acme.local
|
||||
application_pool: acme
|
||||
physical_path: C:\sites\acme
|
||||
parameters: logfile.directory:C:\sites\logs
|
||||
register: website
|
||||
|
||||
# Remove Default Web Site and the standard port 80 binding
|
||||
- name: Remove Default Web Site
|
||||
win_iis_website:
|
||||
name: "Default Web Site"
|
||||
state: absent
|
||||
|
||||
# Some commandline examples:
|
||||
|
||||
# This return information about an existing host
|
||||
# $ ansible -i vagrant-inventory -m win_iis_website -a "name='Default Web Site'" window
|
||||
# host | success >> {
|
||||
# "changed": false,
|
||||
# "site": {
|
||||
# "ApplicationPool": "DefaultAppPool",
|
||||
# "Bindings": [
|
||||
# "*:80:"
|
||||
# ],
|
||||
# "ID": 1,
|
||||
# "Name": "Default Web Site",
|
||||
# "PhysicalPath": "%SystemDrive%\\inetpub\\wwwroot",
|
||||
# "State": "Stopped"
|
||||
# }
|
||||
# }
|
||||
|
||||
# This stops an existing site.
|
||||
# $ ansible -i hosts -m win_iis_website -a "name='Default Web Site' state=stopped" host
|
||||
|
||||
# This creates a new site.
|
||||
# $ ansible -i hosts -m win_iis_website -a "name=acme physical_path=C:\\sites\\acme" host
|
||||
|
||||
# Change logfile.
|
||||
# $ ansible -i hosts -m win_iis_website -a "name=acme physical_path=C:\\sites\\acme" host
|
||||
'''
|
@ -1,495 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2019, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#Requires -Module Ansible.ModuleUtils.AddType
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
auto_detect = @{ type = "bool"; default = $true }
|
||||
auto_config_url = @{ type = "str" }
|
||||
proxy = @{ type = "raw" }
|
||||
bypass = @{ type = "list" }
|
||||
connection = @{ type = "str" }
|
||||
}
|
||||
required_by = @{
|
||||
bypass = @("proxy")
|
||||
}
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$auto_detect = $module.Params.auto_detect
|
||||
$auto_config_url = $module.Params.auto_config_url
|
||||
$proxy = $module.Params.proxy
|
||||
$bypass = $module.Params.bypass
|
||||
$connection = $module.Params.connection
|
||||
|
||||
# Parse the raw value, it should be a Dictionary or String
|
||||
if ($proxy -is [System.Collections.IDictionary]) {
|
||||
$valid_keys = [System.Collections.Generic.List`1[String]]@("http", "https", "ftp", "socks")
|
||||
# Check to make sure we don't have any invalid keys in the dict
|
||||
$invalid_keys = [System.Collections.Generic.List`1[String]]@()
|
||||
foreach ($k in $proxy.Keys) {
|
||||
if ($k -notin $valid_keys) {
|
||||
$invalid_keys.Add($k)
|
||||
}
|
||||
}
|
||||
|
||||
if ($invalid_keys.Count -gt 0) {
|
||||
$invalid_keys = $invalid_keys | Sort-Object # So our test assertion doesn't fail due to random ordering
|
||||
$module.FailJson("Invalid keys found in proxy: $($invalid_keys -join ', '). Valid keys are $($valid_keys -join ', ').")
|
||||
}
|
||||
|
||||
# Build the proxy string in the form 'protocol=host;', the order of valid_keys is also important
|
||||
$proxy_list = [System.Collections.Generic.List`1[String]]@()
|
||||
foreach ($k in $valid_keys) {
|
||||
if ($proxy.ContainsKey($k)) {
|
||||
$proxy_list.Add("$k=$($proxy.$k)")
|
||||
}
|
||||
}
|
||||
$proxy = $proxy_list -join ";"
|
||||
} elseif ($null -ne $proxy) {
|
||||
$proxy = $proxy.ToString()
|
||||
}
|
||||
|
||||
if ($bypass) {
|
||||
if ([System.String]::IsNullOrEmpty($proxy)) {
|
||||
$module.FailJson("missing parameter(s) required by ''bypass'': proxy")
|
||||
}
|
||||
$bypass = $bypass -join ';'
|
||||
}
|
||||
|
||||
$win_inet_invoke = @'
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ansible.WinINetProxy
|
||||
{
|
||||
internal class NativeHelpers
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public class INTERNET_PER_CONN_OPTION_LISTW : IDisposable
|
||||
{
|
||||
public UInt32 dwSize;
|
||||
public IntPtr pszConnection;
|
||||
public UInt32 dwOptionCount;
|
||||
public UInt32 dwOptionError;
|
||||
public IntPtr pOptions;
|
||||
|
||||
public INTERNET_PER_CONN_OPTION_LISTW()
|
||||
{
|
||||
dwSize = (UInt32)Marshal.SizeOf(this);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (pszConnection != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(pszConnection);
|
||||
if (pOptions != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(pOptions);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
~INTERNET_PER_CONN_OPTION_LISTW() { this.Dispose(); }
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public class INTERNET_PER_CONN_OPTIONW : IDisposable
|
||||
{
|
||||
public INTERNET_PER_CONN_OPTION dwOption;
|
||||
public ValueUnion Value;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public class ValueUnion
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public UInt32 dwValue;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public IntPtr pszValue;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public System.Runtime.InteropServices.ComTypes.FILETIME ftValue;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// We can't just check if Value.pszValue is not IntPtr.Zero as the union means it could be set even
|
||||
// when the value is a UInt32 or FILETIME. We check against a known string option type and only free
|
||||
// the value in those cases.
|
||||
List<INTERNET_PER_CONN_OPTION> stringOptions = new List<INTERNET_PER_CONN_OPTION>
|
||||
{
|
||||
{ INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_AUTOCONFIG_URL },
|
||||
{ INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_BYPASS },
|
||||
{ INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_SERVER }
|
||||
};
|
||||
if (Value != null && Value.pszValue != IntPtr.Zero && stringOptions.Contains(dwOption))
|
||||
Marshal.FreeHGlobal(Value.pszValue);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
~INTERNET_PER_CONN_OPTIONW() { this.Dispose(); }
|
||||
}
|
||||
|
||||
public enum INTERNET_OPTION : uint
|
||||
{
|
||||
INTERNET_OPTION_PER_CONNECTION_OPTION = 75,
|
||||
INTERNET_OPTION_PROXY_SETTINGS_CHANGED = 95,
|
||||
}
|
||||
|
||||
public enum INTERNET_PER_CONN_OPTION : uint
|
||||
{
|
||||
INTERNET_PER_CONN_FLAGS = 1,
|
||||
INTERNET_PER_CONN_PROXY_SERVER = 2,
|
||||
INTERNET_PER_CONN_PROXY_BYPASS = 3,
|
||||
INTERNET_PER_CONN_AUTOCONFIG_URL = 4,
|
||||
INTERNET_PER_CONN_AUTODISCOVERY_FLAGS = 5,
|
||||
INTERNET_PER_CONN_FLAGS_UI = 10, // IE8+ - Included with Windows 7 and Server 2008 R2
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum PER_CONN_FLAGS : uint
|
||||
{
|
||||
PROXY_TYPE_DIRECT = 0x00000001,
|
||||
PROXY_TYPE_PROXY = 0x00000002,
|
||||
PROXY_TYPE_AUTO_PROXY_URL = 0x00000004,
|
||||
PROXY_TYPE_AUTO_DETECT = 0x00000008,
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("Wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool InternetQueryOptionW(
|
||||
IntPtr hInternet,
|
||||
NativeHelpers.INTERNET_OPTION dwOption,
|
||||
SafeMemoryBuffer lpBuffer,
|
||||
ref UInt32 lpdwBufferLength);
|
||||
|
||||
[DllImport("Wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool InternetSetOptionW(
|
||||
IntPtr hInternet,
|
||||
NativeHelpers.INTERNET_OPTION dwOption,
|
||||
SafeMemoryBuffer lpBuffer,
|
||||
UInt32 dwBufferLength);
|
||||
|
||||
[DllImport("Rasapi32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern UInt32 RasValidateEntryNameW(
|
||||
string lpszPhonebook,
|
||||
string lpszEntry);
|
||||
}
|
||||
|
||||
internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
public SafeMemoryBuffer() : base(true) { }
|
||||
public SafeMemoryBuffer(int cb) : base(true)
|
||||
{
|
||||
base.SetHandle(Marshal.AllocHGlobal(cb));
|
||||
}
|
||||
public SafeMemoryBuffer(IntPtr handle) : base(true)
|
||||
{
|
||||
base.SetHandle(handle);
|
||||
}
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
Marshal.FreeHGlobal(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class Win32Exception : System.ComponentModel.Win32Exception
|
||||
{
|
||||
private string _msg;
|
||||
|
||||
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
|
||||
public Win32Exception(int errorCode, string message) : base(errorCode)
|
||||
{
|
||||
_msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
|
||||
}
|
||||
|
||||
public override string Message { get { return _msg; } }
|
||||
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
|
||||
}
|
||||
|
||||
public class WinINetProxy
|
||||
{
|
||||
private string Connection;
|
||||
|
||||
public string AutoConfigUrl;
|
||||
public bool AutoDetect;
|
||||
public string Proxy;
|
||||
public string ProxyBypass;
|
||||
|
||||
public WinINetProxy(string connection)
|
||||
{
|
||||
Connection = connection;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public static bool IsValidConnection(string name)
|
||||
{
|
||||
// RasValidateEntryName is used to verify is a name can be a valid phonebook entry. It returns 0 if no
|
||||
// entry exists and 183 if it already exists. We just need to check if it returns 183 to verify the
|
||||
// connection name.
|
||||
return NativeMethods.RasValidateEntryNameW(null, name) == 183;
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
using (var connFlags = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_FLAGS_UI))
|
||||
using (var autoConfigUrl = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_AUTOCONFIG_URL))
|
||||
using (var server = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_SERVER))
|
||||
using (var bypass = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_BYPASS))
|
||||
{
|
||||
NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options = new NativeHelpers.INTERNET_PER_CONN_OPTIONW[]
|
||||
{
|
||||
connFlags, autoConfigUrl, server, bypass
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
QueryOption(options, Connection);
|
||||
}
|
||||
catch (Win32Exception e)
|
||||
{
|
||||
if (e.NativeErrorCode == 87) // ERROR_INVALID_PARAMETER
|
||||
{
|
||||
// INTERNET_PER_CONN_FLAGS_UI only works for IE8+, try the fallback in case we are still working
|
||||
// with an ancient version.
|
||||
connFlags.dwOption = NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_FLAGS;
|
||||
QueryOption(options, Connection);
|
||||
}
|
||||
else
|
||||
throw;
|
||||
}
|
||||
|
||||
NativeHelpers.PER_CONN_FLAGS flags = (NativeHelpers.PER_CONN_FLAGS)connFlags.Value.dwValue;
|
||||
|
||||
AutoConfigUrl = flags.HasFlag(NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_PROXY_URL)
|
||||
? Marshal.PtrToStringUni(autoConfigUrl.Value.pszValue) : null;
|
||||
AutoDetect = flags.HasFlag(NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_DETECT);
|
||||
if (flags.HasFlag(NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_PROXY))
|
||||
{
|
||||
Proxy = Marshal.PtrToStringUni(server.Value.pszValue);
|
||||
ProxyBypass = Marshal.PtrToStringUni(bypass.Value.pszValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
Proxy = null;
|
||||
ProxyBypass = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Set()
|
||||
{
|
||||
using (var connFlags = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_FLAGS_UI))
|
||||
using (var autoConfigUrl = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_AUTOCONFIG_URL))
|
||||
using (var server = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_SERVER))
|
||||
using (var bypass = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_BYPASS))
|
||||
{
|
||||
List<NativeHelpers.INTERNET_PER_CONN_OPTIONW> options = new List<NativeHelpers.INTERNET_PER_CONN_OPTIONW>();
|
||||
|
||||
// PROXY_TYPE_DIRECT seems to always be set, need to verify
|
||||
NativeHelpers.PER_CONN_FLAGS flags = NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_DIRECT;
|
||||
if (AutoDetect)
|
||||
flags |= NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_DETECT;
|
||||
|
||||
if (!String.IsNullOrEmpty(AutoConfigUrl))
|
||||
{
|
||||
flags |= NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_PROXY_URL;
|
||||
autoConfigUrl.Value.pszValue = Marshal.StringToHGlobalUni(AutoConfigUrl);
|
||||
}
|
||||
options.Add(autoConfigUrl);
|
||||
|
||||
if (!String.IsNullOrEmpty(Proxy))
|
||||
{
|
||||
flags |= NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_PROXY;
|
||||
server.Value.pszValue = Marshal.StringToHGlobalUni(Proxy);
|
||||
}
|
||||
options.Add(server);
|
||||
|
||||
if (!String.IsNullOrEmpty(ProxyBypass))
|
||||
bypass.Value.pszValue = Marshal.StringToHGlobalUni(ProxyBypass);
|
||||
options.Add(bypass);
|
||||
|
||||
connFlags.Value.dwValue = (UInt32)flags;
|
||||
options.Add(connFlags);
|
||||
|
||||
SetOption(options.ToArray(), Connection);
|
||||
|
||||
// Tell IE that the proxy settings have been changed.
|
||||
if (!NativeMethods.InternetSetOptionW(
|
||||
IntPtr.Zero,
|
||||
NativeHelpers.INTERNET_OPTION.INTERNET_OPTION_PROXY_SETTINGS_CHANGED,
|
||||
new SafeMemoryBuffer(IntPtr.Zero),
|
||||
0))
|
||||
{
|
||||
throw new Win32Exception("InternetSetOptionW(INTERNET_OPTION_PROXY_SETTINGS_CHANGED) failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static NativeHelpers.INTERNET_PER_CONN_OPTIONW CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION option)
|
||||
{
|
||||
return new NativeHelpers.INTERNET_PER_CONN_OPTIONW
|
||||
{
|
||||
dwOption = option,
|
||||
Value = new NativeHelpers.INTERNET_PER_CONN_OPTIONW.ValueUnion(),
|
||||
};
|
||||
}
|
||||
|
||||
internal static void QueryOption(NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options, string connection = null)
|
||||
{
|
||||
using (NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW optionList = new NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW())
|
||||
using (SafeMemoryBuffer optionListPtr = MarshalOptionList(optionList, options, connection))
|
||||
{
|
||||
UInt32 bufferSize = optionList.dwSize;
|
||||
if (!NativeMethods.InternetQueryOptionW(
|
||||
IntPtr.Zero,
|
||||
NativeHelpers.INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION,
|
||||
optionListPtr,
|
||||
ref bufferSize))
|
||||
{
|
||||
throw new Win32Exception("InternetQueryOptionW(INTERNET_OPTION_PER_CONNECTION_OPTION) failed");
|
||||
}
|
||||
|
||||
for (int i = 0; i < options.Length; i++)
|
||||
{
|
||||
IntPtr opt = IntPtr.Add(optionList.pOptions, i * Marshal.SizeOf(typeof(NativeHelpers.INTERNET_PER_CONN_OPTIONW)));
|
||||
NativeHelpers.INTERNET_PER_CONN_OPTIONW option = (NativeHelpers.INTERNET_PER_CONN_OPTIONW)Marshal.PtrToStructure(opt,
|
||||
typeof(NativeHelpers.INTERNET_PER_CONN_OPTIONW));
|
||||
options[i].Value = option.Value;
|
||||
option.Value = null; // Stops the GC from freeing the same memory twice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetOption(NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options, string connection = null)
|
||||
{
|
||||
using (NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW optionList = new NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW())
|
||||
using (SafeMemoryBuffer optionListPtr = MarshalOptionList(optionList, options, connection))
|
||||
{
|
||||
if (!NativeMethods.InternetSetOptionW(
|
||||
IntPtr.Zero,
|
||||
NativeHelpers.INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION,
|
||||
optionListPtr,
|
||||
optionList.dwSize))
|
||||
{
|
||||
throw new Win32Exception("InternetSetOptionW(INTERNET_OPTION_PER_CONNECTION_OPTION) failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static SafeMemoryBuffer MarshalOptionList(NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW optionList,
|
||||
NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options, string connection)
|
||||
{
|
||||
optionList.pszConnection = Marshal.StringToHGlobalUni(connection);
|
||||
optionList.dwOptionCount = (UInt32)options.Length;
|
||||
|
||||
int optionSize = Marshal.SizeOf(typeof(NativeHelpers.INTERNET_PER_CONN_OPTIONW));
|
||||
optionList.pOptions = Marshal.AllocHGlobal(optionSize * options.Length);
|
||||
for (int i = 0; i < options.Length; i++)
|
||||
{
|
||||
IntPtr option = IntPtr.Add(optionList.pOptions, i * optionSize);
|
||||
Marshal.StructureToPtr(options[i], option, false);
|
||||
}
|
||||
|
||||
SafeMemoryBuffer optionListPtr = new SafeMemoryBuffer((int)optionList.dwSize);
|
||||
Marshal.StructureToPtr(optionList, optionListPtr.DangerousGetHandle(), false);
|
||||
return optionListPtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
Add-CSharpType -References $win_inet_invoke -AnsibleModule $module
|
||||
|
||||
# We need to validate the connection because WinINet will just silently continue even if the connection does not
|
||||
# already exist.
|
||||
if ($null -ne $connection -and -not [Ansible.WinINetProxy.WinINetProxy]::IsValidConnection($connection)) {
|
||||
$module.FailJson("The connection '$connection' does not exist.")
|
||||
}
|
||||
|
||||
$actual_proxy = New-Object -TypeName Ansible.WinINetProxy.WinINetProxy -ArgumentList @(,$connection)
|
||||
$module.Diff.before = @{
|
||||
auto_config_url = $actual_proxy.AutoConfigUrl
|
||||
auto_detect = $actual_proxy.AutoDetect
|
||||
bypass = $actual_proxy.ProxyBypass
|
||||
server = $actual_proxy.Proxy
|
||||
}
|
||||
|
||||
# Make sure an empty string is converted to $null for easier comparisons
|
||||
if ([String]::IsNullOrEmpty($auto_config_url)) {
|
||||
$auto_config_url = $null
|
||||
}
|
||||
if ([String]::IsNullOrEmpty($proxy)) {
|
||||
$proxy = $null
|
||||
}
|
||||
if ([String]::IsNullOrEmpty($bypass)) {
|
||||
$bypass = $null
|
||||
}
|
||||
|
||||
# Record the original values in case we need to revert on a failure
|
||||
$previous_auto_config_url = $actual_proxy.AutoConfigUrl
|
||||
$previous_auto_detect = $actual_proxy.AutoDetect
|
||||
$previous_proxy = $actual_proxy.Proxy
|
||||
$previous_bypass = $actual_proxy.ProxyBypass
|
||||
|
||||
$changed = $false
|
||||
if ($auto_config_url -ne $previous_auto_config_url) {
|
||||
$actual_proxy.AutoConfigUrl = $auto_config_url
|
||||
$changed = $true
|
||||
}
|
||||
|
||||
if ($auto_detect -ne $previous_auto_detect) {
|
||||
$actual_proxy.AutoDetect = $auto_detect
|
||||
$changed = $true
|
||||
}
|
||||
|
||||
if ($proxy -ne $previous_proxy) {
|
||||
$actual_proxy.Proxy = $proxy
|
||||
$changed = $true
|
||||
}
|
||||
|
||||
if ($bypass -ne $previous_bypass) {
|
||||
$actual_proxy.ProxyBypass = $bypass
|
||||
$changed = $true
|
||||
}
|
||||
|
||||
if ($changed -and -not $module.CheckMode) {
|
||||
$actual_proxy.Set()
|
||||
|
||||
# Validate that the change was made correctly and revert if it wasn't. THe Set() method won't fail on invalid
|
||||
# values so we need to check again to make sure all was good
|
||||
$actual_proxy.Refresh()
|
||||
if ($actual_proxy.AutoConfigUrl -ne $auto_config_url -or
|
||||
$actual_proxy.AutoDetect -ne $auto_detect -or
|
||||
$actual_proxy.Proxy -ne $proxy -or
|
||||
$actual_proxy.ProxyBypass -ne $bypass) {
|
||||
|
||||
$actual_proxy.AutoConfigUrl = $previous_auto_config_url
|
||||
$actual_proxy.AutoDetect = $previous_auto_detect
|
||||
$actual_proxy.Proxy = $previous_proxy
|
||||
$actual_proxy.ProxyBypass = $previous_bypass
|
||||
$actual_proxy.Set()
|
||||
|
||||
$module.FailJson("Unknown error when trying to set auto_config_url '$auto_config_url', proxy '$proxy', or bypass '$bypass'")
|
||||
}
|
||||
}
|
||||
$module.Result.changed = $changed
|
||||
|
||||
$module.Diff.after = @{
|
||||
auto_config_url = $auto_config_url
|
||||
auto_detect = $auto_detect
|
||||
bypass = $bypass
|
||||
proxy = $proxy
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
@ -1,173 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_inet_proxy
|
||||
version_added: '2.8'
|
||||
short_description: Manages proxy settings for WinINet and Internet Explorer
|
||||
description:
|
||||
- Used to set or remove proxy settings for Windows INet which includes Internet
|
||||
Explorer.
|
||||
- WinINet is a framework used by interactive applications to submit web
|
||||
requests through.
|
||||
- The proxy settings can also be used by other applications like Firefox,
|
||||
Chrome, and others but there is no definitive list.
|
||||
options:
|
||||
auto_detect:
|
||||
description:
|
||||
- Whether to configure WinINet to automatically detect proxy settings
|
||||
through Web Proxy Auto-Detection C(WPAD).
|
||||
- This corresponds to the checkbox I(Automatically detect settings) in the
|
||||
connection settings window.
|
||||
default: yes
|
||||
type: bool
|
||||
auto_config_url:
|
||||
description:
|
||||
- The URL of a proxy configuration script.
|
||||
- Proxy configuration scripts are typically JavaScript files with the
|
||||
C(.pac) extension that implement the C(FindProxyForURL(url, host)
|
||||
function.
|
||||
- Omit, set to null or an empty string to remove the auto config URL.
|
||||
- This corresponds to the checkbox I(Use automatic configuration script) in
|
||||
the connection settings window.
|
||||
type: str
|
||||
bypass:
|
||||
description:
|
||||
- A list of hosts that will bypass the set proxy when being accessed.
|
||||
- Use C(<local>) to match hostnames that are not fully qualified domain
|
||||
names. This is useful when needing to connect to intranet sites using
|
||||
just the hostname. If defined, this should be the last entry in the
|
||||
bypass list.
|
||||
- Use C(<-loopback>) to stop automatically bypassing the proxy when
|
||||
connecting through any loopback address like C(127.0.0.1), C(localhost),
|
||||
or the local hostname.
|
||||
- Omit, set to null or an empty string/list to remove the bypass list.
|
||||
- If this is set then I(proxy) must also be set.
|
||||
type: list
|
||||
connection:
|
||||
description:
|
||||
- The name of the IE connection to set the proxy settings for.
|
||||
- These are the connections under the I(Dial-up and Virtual Private Network)
|
||||
header in the IE settings.
|
||||
- When omitted, the default LAN connection is used.
|
||||
type: str
|
||||
proxy:
|
||||
description:
|
||||
- A string or dict that specifies the proxy to be set.
|
||||
- If setting a string, should be in the form C(hostname), C(hostname:port),
|
||||
or C(protocol=hostname:port).
|
||||
- If the port is undefined, the default port for the protocol in use is
|
||||
used.
|
||||
- If setting a dict, the keys should be the protocol and the values should
|
||||
be the hostname and/or port for that protocol.
|
||||
- Valid protocols are C(http), C(https), C(ftp), and C(socks).
|
||||
- Omit, set to null or an empty string to remove the proxy settings.
|
||||
notes:
|
||||
- This is not the same as the proxy settings set in WinHTTP through the
|
||||
C(netsh) command. Use the M(win_http_proxy) module to manage that instead.
|
||||
- These settings are by default set per user and not system wide. A registry
|
||||
property must be set independently from this module if you wish to apply the
|
||||
proxy for all users. See examples for more detail.
|
||||
- If per user proxy settings are desired, use I(become) to become any local
|
||||
user on the host. No password is needed to be set for this to work.
|
||||
- If the proxy requires authentication, set the credentials using the
|
||||
M(win_credential) module. This requires I(become) to be used so the
|
||||
credential store can be accessed.
|
||||
seealso:
|
||||
- module: win_http_proxy
|
||||
- module: win_credential
|
||||
author:
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# This should be set before running the win_inet_proxy module
|
||||
- name: Configure IE proxy settings to apply to all users
|
||||
win_regedit:
|
||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings
|
||||
name: ProxySettingsPerUser
|
||||
data: 0
|
||||
type: dword
|
||||
state: present
|
||||
|
||||
# This should be set before running the win_inet_proxy module
|
||||
- name: Configure IE proxy settings to apply per user
|
||||
win_regedit:
|
||||
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings
|
||||
name: ProxySettingsPerUser
|
||||
data: 1
|
||||
type: dword
|
||||
state: present
|
||||
|
||||
- name: Configure IE proxy to use auto detected settings without an explicit proxy
|
||||
win_inet_proxy:
|
||||
auto_detect: yes
|
||||
|
||||
- name: Configure IE proxy to use auto detected settings with a configuration script
|
||||
win_inet_proxy:
|
||||
auto_detect: yes
|
||||
auto_config_url: http://proxy.ansible.com/proxy.pac
|
||||
|
||||
- name: Configure IE to use explicit proxy host
|
||||
win_inet_proxy:
|
||||
auto_detect: yes
|
||||
proxy: ansible.proxy
|
||||
|
||||
- name: Configure IE to use explicit proxy host with port and without auto detection
|
||||
win_inet_proxy:
|
||||
auto_detect: no
|
||||
proxy: ansible.proxy:8080
|
||||
|
||||
- name: Configure IE to use a specific proxy per protocol
|
||||
win_inet_proxy:
|
||||
proxy:
|
||||
http: ansible.proxy:8080
|
||||
https: ansible.proxy:8443
|
||||
|
||||
- name: Configure IE to use a specific proxy per protocol using a string
|
||||
win_inet_proxy:
|
||||
proxy: http=ansible.proxy:8080;https=ansible.proxy:8443
|
||||
|
||||
- name: Set a proxy with a bypass list
|
||||
win_inet_proxy:
|
||||
proxy: ansible.proxy
|
||||
bypass:
|
||||
- server1
|
||||
- server2
|
||||
- <-loopback>
|
||||
- <local>
|
||||
|
||||
- name: Remove any explicit proxies that are set
|
||||
win_inet_proxy:
|
||||
proxy: ''
|
||||
bypass: ''
|
||||
|
||||
# This should be done after setting the IE proxy with win_inet_proxy
|
||||
- name: Import IE proxy configuration to WinHTTP
|
||||
win_http_proxy:
|
||||
source: ie
|
||||
|
||||
# Explicit credentials can only be set per user and require become to work
|
||||
- name: Set credential to use for proxy auth
|
||||
win_credential:
|
||||
name: ansible.proxy # The name should be the FQDN of the proxy host
|
||||
type: generic_password
|
||||
username: proxyuser
|
||||
secret: proxypass
|
||||
state: present
|
||||
become: yes
|
||||
become_user: '{{ ansible_user }}'
|
||||
become_method: runas
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
#
|
||||
'''
|
@ -1,151 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2019, Brant Evans <bevans@redhat.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#AnsibleRequires -OSVersion 6.2
|
||||
|
||||
Set-StrictMode -Version 2
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
disk_number = @{ type = "int" }
|
||||
uniqueid = @{ type = "str" }
|
||||
path = @{ type = "str" }
|
||||
style = @{ type = "str"; choices = "gpt", "mbr"; default = "gpt" }
|
||||
online = @{ type = "bool"; default = $true }
|
||||
force = @{ type = "bool"; default = $false }
|
||||
}
|
||||
mutually_exclusive = @(
|
||||
,@('disk_number', 'uniqueid', 'path')
|
||||
)
|
||||
required_one_of = @(
|
||||
,@('disk_number', 'uniqueid', 'path')
|
||||
)
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$disk_number = $module.Params.disk_number
|
||||
$uniqueid = $module.Params.uniqueid
|
||||
$path = $module.Params.path
|
||||
$partition_style = $module.Params.style
|
||||
$bring_online = $module.Params.online
|
||||
$force_init = $module.Params.force
|
||||
|
||||
function Get-AnsibleDisk {
|
||||
param(
|
||||
$DiskNumber,
|
||||
$UniqueId,
|
||||
$Path
|
||||
)
|
||||
|
||||
if ($null -ne $DiskNumber) {
|
||||
try {
|
||||
$disk = Get-Disk -Number $DiskNumber
|
||||
} catch {
|
||||
$module.FailJson("There was an error retrieving the disk using disk_number $($DiskNumber): $($_.Exception.Message)")
|
||||
}
|
||||
} elseif ($null -ne $UniqueId) {
|
||||
try {
|
||||
$disk = Get-Disk -UniqueId $UniqueId
|
||||
} catch {
|
||||
$module.FailJson("There was an error retrieving the disk using id $($UniqueId): $($_.Exception.Message)")
|
||||
}
|
||||
} elseif ($null -ne $Path) {
|
||||
try {
|
||||
$disk = Get-Disk -Path $Path
|
||||
} catch {
|
||||
$module.FailJson("There was an error retrieving the disk using path $($Path): $($_.Exception.Message)")
|
||||
}
|
||||
} else {
|
||||
$module.FailJson("Unable to retrieve disk: disk_number, id, or path was not specified")
|
||||
}
|
||||
|
||||
return $disk
|
||||
}
|
||||
|
||||
function Initialize-AnsibleDisk {
|
||||
param(
|
||||
$AnsibleDisk,
|
||||
$PartitionStyle
|
||||
)
|
||||
|
||||
if ($AnsibleDisk.IsReadOnly) {
|
||||
$module.FailJson("Unable to initialize disk as it is read-only")
|
||||
}
|
||||
|
||||
$parameters = @{
|
||||
Number = $AnsibleDisk.Number
|
||||
PartitionStyle = $PartitionStyle
|
||||
}
|
||||
|
||||
if (-Not $module.CheckMode) {
|
||||
Initialize-Disk @parameters -Confirm:$false
|
||||
}
|
||||
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
|
||||
function Clear-AnsibleDisk {
|
||||
param(
|
||||
$AnsibleDisk
|
||||
)
|
||||
|
||||
$parameters = @{
|
||||
Number = $AnsibleDisk.Number
|
||||
}
|
||||
|
||||
if (-Not $module.CheckMode) {
|
||||
Clear-Disk @parameters -RemoveData -RemoveOEM -Confirm:$false
|
||||
}
|
||||
}
|
||||
|
||||
function Set-AnsibleDisk {
|
||||
param(
|
||||
$AnsibleDisk,
|
||||
$BringOnline
|
||||
)
|
||||
|
||||
$refresh_disk_status = $false
|
||||
|
||||
if ($BringOnline) {
|
||||
if (-Not $module.CheckMode) {
|
||||
if ($AnsibleDisk.IsOffline) {
|
||||
Set-Disk -Number $AnsibleDisk.Number -IsOffline:$false
|
||||
$refresh_disk_status = $true
|
||||
}
|
||||
|
||||
if ($AnsibleDisk.IsReadOnly) {
|
||||
Set-Disk -Number $AnsibleDisk.Number -IsReadOnly:$false
|
||||
$refresh_disk_status = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($refresh_disk_status) {
|
||||
$AnsibleDisk = Get-AnsibleDisk -DiskNumber $AnsibleDisk.Number
|
||||
}
|
||||
|
||||
return $AnsibleDisk
|
||||
}
|
||||
|
||||
$ansible_disk = Get-AnsibleDisk -DiskNumber $disk_number -UniqueId $uniqueid -Path $path
|
||||
$ansible_part_style = $ansible_disk.PartitionStyle
|
||||
|
||||
if ("RAW" -eq $ansible_part_style) {
|
||||
$ansible_disk = Set-AnsibleDisk -AnsibleDisk $ansible_disk -BringOnline $bring_online
|
||||
Initialize-AnsibleDisk -AnsibleDisk $ansible_disk -PartitionStyle $partition_style
|
||||
} else {
|
||||
if (($ansible_part_style -ne $partition_style.ToUpper()) -And -Not $force_init) {
|
||||
$module.FailJson("Force initialization must be specified since the target partition style: $($partition_style.ToLower()) is different from the current partition style: $($ansible_part_style.ToLower())")
|
||||
} elseif ($force_init) {
|
||||
$ansible_disk = Set-AnsibleDisk -AnsibleDisk $ansible_disk -BringOnline $bring_online
|
||||
Clear-AnsibleDisk -AnsibleDisk $ansible_disk
|
||||
Initialize-AnsibleDisk -AnsibleDisk $ansible_disk -PartitionStyle $partition_style
|
||||
}
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
@ -1,88 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Copyright: (c) 2019, Brant Evans <bevans@redhat.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: win_initialize_disk
|
||||
|
||||
short_description: Initializes disks on Windows Server
|
||||
|
||||
version_added: "2.10"
|
||||
|
||||
description:
|
||||
- "The M(win_initialize_disk) module initializes disks"
|
||||
|
||||
options:
|
||||
disk_number:
|
||||
description:
|
||||
- Used to specify the disk number of the disk to be initialized.
|
||||
type: int
|
||||
uniqueid:
|
||||
description:
|
||||
- Used to specify the uniqueid of the disk to be initialized.
|
||||
type: str
|
||||
path:
|
||||
description:
|
||||
- Used to specify the path to the disk to be initialized.
|
||||
type: str
|
||||
style:
|
||||
description:
|
||||
- The partition style to use for the disk. Valid options are mbr or gpt.
|
||||
type: str
|
||||
choices: [ gpt, mbr ]
|
||||
default: gpt
|
||||
online:
|
||||
description:
|
||||
- If the disk is offline and/or readonly update the disk to be online and not readonly.
|
||||
type: bool
|
||||
default: true
|
||||
force:
|
||||
description:
|
||||
- Specify if initializing should be forced for disks that are already initialized.
|
||||
type: bool
|
||||
|
||||
notes:
|
||||
- One of three parameters (I(disk_number), I(uniqueid), and I(path)) are mandatory to identify the target disk, but
|
||||
more than one cannot be specified at the same time.
|
||||
- A minimum Operating System Version of Server 2012 or Windows 8 is required to use this module.
|
||||
- This module is idempotent if I(force) is not specified.
|
||||
|
||||
seealso:
|
||||
- module: win_disk_facts
|
||||
- module: win_partition
|
||||
- module: win_format
|
||||
|
||||
author:
|
||||
- Brant Evans (@branic)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Initialize a disk
|
||||
win_initialize_disk:
|
||||
disk_number: 1
|
||||
|
||||
- name: Initialize a disk with an MBR partition style
|
||||
win_initialize_disk:
|
||||
disk_number: 1
|
||||
style: mbr
|
||||
|
||||
- name: Forcefully initiallize a disk
|
||||
win_initialize_disk:
|
||||
disk_number: 2
|
||||
force: yes
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
#
|
||||
'''
|
@ -1,450 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#Requires -Module Ansible.ModuleUtils.Backup
|
||||
|
||||
function WriteLines($outlines, $path, $linesep, $encodingobj, $validate, $check_mode) {
|
||||
Try {
|
||||
$temppath = [System.IO.Path]::GetTempFileName();
|
||||
}
|
||||
Catch {
|
||||
Fail-Json @{} "Cannot create temporary file! ($($_.Exception.Message))";
|
||||
}
|
||||
$joined = $outlines -join $linesep;
|
||||
[System.IO.File]::WriteAllText($temppath, $joined, $encodingobj);
|
||||
|
||||
If ($validate) {
|
||||
|
||||
If (-not ($validate -like "*%s*")) {
|
||||
Fail-Json @{} "validate must contain %s: $validate";
|
||||
}
|
||||
|
||||
$validate = $validate.Replace("%s", $temppath);
|
||||
|
||||
$parts = [System.Collections.ArrayList] $validate.Split(" ");
|
||||
$cmdname = $parts[0];
|
||||
|
||||
$cmdargs = $validate.Substring($cmdname.Length + 1);
|
||||
|
||||
$process = [Diagnostics.Process]::Start($cmdname, $cmdargs);
|
||||
$process.WaitForExit();
|
||||
|
||||
If ($process.ExitCode -ne 0) {
|
||||
[string] $output = $process.StandardOutput.ReadToEnd();
|
||||
[string] $error = $process.StandardError.ReadToEnd();
|
||||
Remove-Item $temppath -force;
|
||||
Fail-Json @{} "failed to validate $cmdname $cmdargs with error: $output $error";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Commit changes to the path
|
||||
$cleanpath = $path.Replace("/", "\");
|
||||
Try {
|
||||
Copy-Item -Path $temppath -Destination $cleanpath -Force -WhatIf:$check_mode;
|
||||
}
|
||||
Catch {
|
||||
Fail-Json @{} "Cannot write to: $cleanpath ($($_.Exception.Message))";
|
||||
}
|
||||
|
||||
Try {
|
||||
Remove-Item -Path $temppath -Force -WhatIf:$check_mode;
|
||||
}
|
||||
Catch {
|
||||
Fail-Json @{} "Cannot remove temporary file: $temppath ($($_.Exception.Message))";
|
||||
}
|
||||
|
||||
return $joined;
|
||||
|
||||
}
|
||||
|
||||
|
||||
# Implement the functionality for state == 'present'
|
||||
function Present($path, $regex, $line, $insertafter, $insertbefore, $create, $backup, $backrefs, $validate, $encodingobj, $linesep, $check_mode, $diff_support) {
|
||||
|
||||
# Note that we have to clean up the path because ansible wants to treat / and \ as
|
||||
# interchangeable in windows pathnames, but .NET framework internals do not support that.
|
||||
$cleanpath = $path.Replace("/", "\");
|
||||
|
||||
# Check if path exists. If it does not exist, either create it if create == "yes"
|
||||
# was specified or fail with a reasonable error message.
|
||||
If (-not (Test-Path -LiteralPath $path)) {
|
||||
If (-not $create) {
|
||||
Fail-Json @{} "Path $path does not exist !";
|
||||
}
|
||||
# Create new empty file, using the specified encoding to write correct BOM
|
||||
[System.IO.File]::WriteAllLines($cleanpath, "", $encodingobj);
|
||||
}
|
||||
|
||||
# Initialize result information
|
||||
$result = @{
|
||||
backup = "";
|
||||
changed = $false;
|
||||
msg = "";
|
||||
}
|
||||
|
||||
# Read the dest file lines using the indicated encoding into a mutable ArrayList.
|
||||
$before = [System.IO.File]::ReadAllLines($cleanpath, $encodingobj)
|
||||
If ($null -eq $before) {
|
||||
$lines = New-Object System.Collections.ArrayList;
|
||||
}
|
||||
Else {
|
||||
$lines = [System.Collections.ArrayList] $before;
|
||||
}
|
||||
|
||||
if ($diff_support) {
|
||||
$result.diff = @{
|
||||
before = $before -join $linesep;
|
||||
}
|
||||
}
|
||||
|
||||
# Compile the regex specified, if provided
|
||||
$mre = $null;
|
||||
If ($regex) {
|
||||
$mre = New-Object Regex $regex, 'Compiled';
|
||||
}
|
||||
|
||||
# Compile the regex for insertafter or insertbefore, if provided
|
||||
$insre = $null;
|
||||
If ($insertafter -and $insertafter -ne "BOF" -and $insertafter -ne "EOF") {
|
||||
$insre = New-Object Regex $insertafter, 'Compiled';
|
||||
}
|
||||
ElseIf ($insertbefore -and $insertbefore -ne "BOF") {
|
||||
$insre = New-Object Regex $insertbefore, 'Compiled';
|
||||
}
|
||||
|
||||
# index[0] is the line num where regex has been found
|
||||
# index[1] is the line num where insertafter/insertbefore has been found
|
||||
$index = -1, -1;
|
||||
$lineno = 0;
|
||||
|
||||
# The latest match object and matched line
|
||||
$matched_line = "";
|
||||
|
||||
# Iterate through the lines in the file looking for matches
|
||||
Foreach ($cur_line in $lines) {
|
||||
If ($regex) {
|
||||
$m = $mre.Match($cur_line);
|
||||
$match_found = $m.Success;
|
||||
If ($match_found) {
|
||||
$matched_line = $cur_line;
|
||||
}
|
||||
}
|
||||
Else {
|
||||
$match_found = $line -ceq $cur_line;
|
||||
}
|
||||
If ($match_found) {
|
||||
$index[0] = $lineno;
|
||||
}
|
||||
ElseIf ($insre -and $insre.Match($cur_line).Success) {
|
||||
If ($insertafter) {
|
||||
$index[1] = $lineno + 1;
|
||||
}
|
||||
If ($insertbefore) {
|
||||
$index[1] = $lineno;
|
||||
}
|
||||
}
|
||||
$lineno = $lineno + 1;
|
||||
}
|
||||
|
||||
If ($index[0] -ne -1) {
|
||||
If ($backrefs) {
|
||||
$new_line = [regex]::Replace($matched_line, $regex, $line);
|
||||
}
|
||||
Else {
|
||||
$new_line = $line;
|
||||
}
|
||||
If ($lines[$index[0]] -cne $new_line) {
|
||||
$lines[$index[0]] = $new_line;
|
||||
$result.changed = $true;
|
||||
$result.msg = "line replaced";
|
||||
}
|
||||
}
|
||||
ElseIf ($backrefs) {
|
||||
# No matches - no-op
|
||||
}
|
||||
ElseIf ($insertbefore -eq "BOF" -or $insertafter -eq "BOF") {
|
||||
$lines.Insert(0, $line);
|
||||
$result.changed = $true;
|
||||
$result.msg = "line added";
|
||||
}
|
||||
ElseIf ($insertafter -eq "EOF" -or $index[1] -eq -1) {
|
||||
$lines.Add($line) > $null;
|
||||
$result.changed = $true;
|
||||
$result.msg = "line added";
|
||||
}
|
||||
Else {
|
||||
$lines.Insert($index[1], $line);
|
||||
$result.changed = $true;
|
||||
$result.msg = "line added";
|
||||
}
|
||||
|
||||
# Write changes to the path if changes were made
|
||||
If ($result.changed) {
|
||||
|
||||
# Write backup file if backup == "yes"
|
||||
If ($backup) {
|
||||
$result.backup_file = Backup-File -path $path -WhatIf:$check_mode
|
||||
# Ensure backward compatibility (deprecate in future)
|
||||
$result.backup = $result.backup_file
|
||||
}
|
||||
|
||||
$writelines_params = @{
|
||||
outlines = $lines
|
||||
path = $path
|
||||
linesep = $linesep
|
||||
encodingobj = $encodingobj
|
||||
validate = $validate
|
||||
check_mode = $check_mode
|
||||
}
|
||||
$after = WriteLines @writelines_params;
|
||||
|
||||
if ($diff_support) {
|
||||
$result.diff.after = $after;
|
||||
}
|
||||
}
|
||||
|
||||
$result.encoding = $encodingobj.WebName;
|
||||
|
||||
Exit-Json $result;
|
||||
}
|
||||
|
||||
|
||||
# Implement the functionality for state == 'absent'
|
||||
function Absent($path, $regex, $line, $backup, $validate, $encodingobj, $linesep, $check_mode, $diff_support) {
|
||||
|
||||
# Check if path exists. If it does not exist, fail with a reasonable error message.
|
||||
If (-not (Test-Path -LiteralPath $path)) {
|
||||
Fail-Json @{} "Path $path does not exist !";
|
||||
}
|
||||
|
||||
# Initialize result information
|
||||
$result = @{
|
||||
backup = "";
|
||||
changed = $false;
|
||||
msg = "";
|
||||
}
|
||||
|
||||
# Read the dest file lines using the indicated encoding into a mutable ArrayList. Note
|
||||
# that we have to clean up the path because ansible wants to treat / and \ as
|
||||
# interchangeable in windows pathnames, but .NET framework internals do not support that.
|
||||
$cleanpath = $path.Replace("/", "\");
|
||||
$before = [System.IO.File]::ReadAllLines($cleanpath, $encodingobj);
|
||||
If ($null -eq $before) {
|
||||
$lines = New-Object System.Collections.ArrayList;
|
||||
}
|
||||
Else {
|
||||
$lines = [System.Collections.ArrayList] $before;
|
||||
}
|
||||
|
||||
if ($diff_support) {
|
||||
$result.diff = @{
|
||||
before = $before -join $linesep;
|
||||
}
|
||||
}
|
||||
|
||||
# Compile the regex specified, if provided
|
||||
$cre = $null;
|
||||
If ($regex) {
|
||||
$cre = New-Object Regex $regex, 'Compiled';
|
||||
}
|
||||
|
||||
$found = New-Object System.Collections.ArrayList;
|
||||
$left = New-Object System.Collections.ArrayList;
|
||||
|
||||
Foreach ($cur_line in $lines) {
|
||||
If ($regex) {
|
||||
$m = $cre.Match($cur_line);
|
||||
$match_found = $m.Success;
|
||||
}
|
||||
Else {
|
||||
$match_found = $line -ceq $cur_line;
|
||||
}
|
||||
If ($match_found) {
|
||||
$found.Add($cur_line) > $null;
|
||||
$result.changed = $true;
|
||||
}
|
||||
Else {
|
||||
$left.Add($cur_line) > $null;
|
||||
}
|
||||
}
|
||||
|
||||
# Write changes to the path if changes were made
|
||||
If ($result.changed) {
|
||||
|
||||
# Write backup file if backup == "yes"
|
||||
If ($backup) {
|
||||
$result.backup_file = Backup-File -path $path -WhatIf:$check_mode
|
||||
# Ensure backward compatibility (deprecate in future)
|
||||
$result.backup = $result.backup_file
|
||||
}
|
||||
|
||||
$writelines_params = @{
|
||||
outlines = $left
|
||||
path = $path
|
||||
linesep = $linesep
|
||||
encodingobj = $encodingobj
|
||||
validate = $validate
|
||||
check_mode = $check_mode
|
||||
}
|
||||
$after = WriteLines @writelines_params;
|
||||
|
||||
if ($diff_support) {
|
||||
$result.diff.after = $after;
|
||||
}
|
||||
}
|
||||
|
||||
$result.encoding = $encodingobj.WebName;
|
||||
$result.found = $found.Count;
|
||||
$result.msg = "$($found.Count) line(s) removed";
|
||||
|
||||
Exit-Json $result;
|
||||
}
|
||||
|
||||
|
||||
# Parse the parameters file dropped by the Ansible machinery
|
||||
$params = Parse-Args $args -supports_check_mode $true;
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false;
|
||||
$diff_support = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false;
|
||||
|
||||
# Initialize defaults for input parameters.
|
||||
$path = Get-AnsibleParam -obj $params -name "path" -type "path" -failifempty $true -aliases "dest","destfile","name";
|
||||
$regex = Get-AnsibleParam -obj $params -name "regex" -type "str" -aliases "regexp";
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent";
|
||||
$line = Get-AnsibleParam -obj $params -name "line" -type "str";
|
||||
$backrefs = Get-AnsibleParam -obj $params -name "backrefs" -type "bool" -default $false;
|
||||
$insertafter = Get-AnsibleParam -obj $params -name "insertafter" -type "str";
|
||||
$insertbefore = Get-AnsibleParam -obj $params -name "insertbefore" -type "str";
|
||||
$create = Get-AnsibleParam -obj $params -name "create" -type "bool" -default $false;
|
||||
$backup = Get-AnsibleParam -obj $params -name "backup" -type "bool" -default $false;
|
||||
$validate = Get-AnsibleParam -obj $params -name "validate" -type "str";
|
||||
$encoding = Get-AnsibleParam -obj $params -name "encoding" -type "str" -default "auto";
|
||||
$newline = Get-AnsibleParam -obj $params -name "newline" -type "str" -default "windows" -validateset "unix","windows";
|
||||
|
||||
# Fail if the path is not a file
|
||||
If (Test-Path -LiteralPath $path -PathType "container") {
|
||||
Fail-Json @{} "Path $path is a directory";
|
||||
}
|
||||
|
||||
# Default to windows line separator - probably most common
|
||||
$linesep = "`r`n"
|
||||
If ($newline -eq "unix") {
|
||||
$linesep = "`n";
|
||||
}
|
||||
|
||||
# Figure out the proper encoding to use for reading / writing the target file.
|
||||
|
||||
# The default encoding is UTF-8 without BOM
|
||||
$encodingobj = [System.Text.UTF8Encoding] $false;
|
||||
|
||||
# If an explicit encoding is specified, use that instead
|
||||
If ($encoding -ne "auto") {
|
||||
$encodingobj = [System.Text.Encoding]::GetEncoding($encoding);
|
||||
}
|
||||
|
||||
# Otherwise see if we can determine the current encoding of the target file.
|
||||
# If the file doesn't exist yet (create == 'yes') we use the default or
|
||||
# explicitly specified encoding set above.
|
||||
ElseIf (Test-Path -LiteralPath $path) {
|
||||
|
||||
# Get a sorted list of encodings with preambles, longest first
|
||||
$max_preamble_len = 0;
|
||||
$sortedlist = New-Object System.Collections.SortedList;
|
||||
Foreach ($encodinginfo in [System.Text.Encoding]::GetEncodings()) {
|
||||
$encoding = $encodinginfo.GetEncoding();
|
||||
$plen = $encoding.GetPreamble().Length;
|
||||
If ($plen -gt $max_preamble_len) {
|
||||
$max_preamble_len = $plen;
|
||||
}
|
||||
If ($plen -gt 0) {
|
||||
$sortedlist.Add(-($plen * 1000000 + $encoding.CodePage), $encoding) > $null;
|
||||
}
|
||||
}
|
||||
|
||||
# Get the first N bytes from the file, where N is the max preamble length we saw
|
||||
[Byte[]]$bom = Get-Content -Encoding Byte -ReadCount $max_preamble_len -TotalCount $max_preamble_len -LiteralPath $path;
|
||||
|
||||
# Iterate through the sorted encodings, looking for a full match.
|
||||
$found = $false;
|
||||
Foreach ($encoding in $sortedlist.GetValueList()) {
|
||||
$preamble = $encoding.GetPreamble();
|
||||
If ($preamble -and $bom) {
|
||||
Foreach ($i in 0..($preamble.Length - 1)) {
|
||||
If ($i -ge $bom.Length) {
|
||||
break;
|
||||
}
|
||||
If ($preamble[$i] -ne $bom[$i]) {
|
||||
break;
|
||||
}
|
||||
ElseIf ($i + 1 -eq $preamble.Length) {
|
||||
$encodingobj = $encoding;
|
||||
$found = $true;
|
||||
}
|
||||
}
|
||||
If ($found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Main dispatch - based on the value of 'state', perform argument validation and
|
||||
# call the appropriate handler function.
|
||||
If ($state -eq "present") {
|
||||
|
||||
If ($backrefs -and -not $regex) {
|
||||
Fail-Json @{} "regexp= is required with backrefs=true";
|
||||
}
|
||||
|
||||
If (-not $line) {
|
||||
Fail-Json @{} "line= is required with state=present";
|
||||
}
|
||||
|
||||
If ($insertbefore -and $insertafter) {
|
||||
Add-Warning $result "Both insertbefore and insertafter parameters found, ignoring `"insertafter=$insertafter`""
|
||||
}
|
||||
|
||||
If (-not $insertbefore -and -not $insertafter) {
|
||||
$insertafter = "EOF";
|
||||
}
|
||||
|
||||
$present_params = @{
|
||||
path = $path
|
||||
regex = $regex
|
||||
line = $line
|
||||
insertafter = $insertafter
|
||||
insertbefore = $insertbefore
|
||||
create = $create
|
||||
backup = $backup
|
||||
backrefs = $backrefs
|
||||
validate = $validate
|
||||
encodingobj = $encodingobj
|
||||
linesep = $linesep
|
||||
check_mode = $check_mode
|
||||
diff_support = $diff_support
|
||||
}
|
||||
Present @present_params;
|
||||
|
||||
}
|
||||
ElseIf ($state -eq "absent") {
|
||||
|
||||
If (-not $regex -and -not $line) {
|
||||
Fail-Json @{} "one of line= or regexp= is required with state=absent";
|
||||
}
|
||||
|
||||
$absent_params = @{
|
||||
path = $path
|
||||
regex = $regex
|
||||
line = $line
|
||||
backup = $backup
|
||||
validate = $validate
|
||||
encodingobj = $encodingobj
|
||||
linesep = $linesep
|
||||
check_mode = $check_mode
|
||||
diff_support = $diff_support
|
||||
}
|
||||
Absent @absent_params;
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_lineinfile
|
||||
short_description: Ensure a particular line is in a file, or replace an existing line using a back-referenced regular expression
|
||||
description:
|
||||
- This module will search a file for a line, and ensure that it is present or absent.
|
||||
- This is primarily useful when you want to change a single line in a file only.
|
||||
version_added: "2.0"
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- The path of the file to modify.
|
||||
- Note that the Windows path delimiter C(\) must be escaped as C(\\) when the line is double quoted.
|
||||
- Before Ansible 2.3 this option was only usable as I(dest), I(destfile) and I(name).
|
||||
type: path
|
||||
required: yes
|
||||
aliases: [ dest, destfile, name ]
|
||||
backup:
|
||||
description:
|
||||
- Determine whether a backup should be created.
|
||||
- When set to C(yes), create a backup file including the timestamp information
|
||||
so you can get the original file back if you somehow clobbered it incorrectly.
|
||||
type: bool
|
||||
default: no
|
||||
regex:
|
||||
description:
|
||||
- The regular expression to look for in every line of the file. For C(state=present), the pattern to replace if found; only the last line found
|
||||
will be replaced. For C(state=absent), the pattern of the line to remove. Uses .NET compatible regular expressions;
|
||||
see U(https://msdn.microsoft.com/en-us/library/hs600312%28v=vs.110%29.aspx).
|
||||
aliases: [ "regexp" ]
|
||||
state:
|
||||
description:
|
||||
- Whether the line should be there or not.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
line:
|
||||
description:
|
||||
- Required for C(state=present). The line to insert/replace into the file. If C(backrefs) is set, may contain backreferences that will get
|
||||
expanded with the C(regexp) capture groups if the regexp matches.
|
||||
- Be aware that the line is processed first on the controller and thus is dependent on yaml quoting rules. Any double quoted line
|
||||
will have control characters, such as '\r\n', expanded. To print such characters literally, use single or no quotes.
|
||||
type: str
|
||||
backrefs:
|
||||
description:
|
||||
- Used with C(state=present). If set, line can contain backreferences (both positional and named) that will get populated if the C(regexp)
|
||||
matches. This flag changes the operation of the module slightly; C(insertbefore) and C(insertafter) will be ignored, and if the C(regexp)
|
||||
doesn't match anywhere in the file, the file will be left unchanged.
|
||||
- If the C(regexp) does match, the last matching line will be replaced by the expanded line parameter.
|
||||
type: bool
|
||||
default: no
|
||||
insertafter:
|
||||
description:
|
||||
- Used with C(state=present). If specified, the line will be inserted after the last match of specified regular expression. A special value is
|
||||
available; C(EOF) for inserting the line at the end of the file.
|
||||
- If specified regular expression has no matches, EOF will be used instead. May not be used with C(backrefs).
|
||||
type: str
|
||||
choices: [ EOF, '*regex*' ]
|
||||
default: EOF
|
||||
insertbefore:
|
||||
description:
|
||||
- Used with C(state=present). If specified, the line will be inserted before the last match of specified regular expression. A value is available;
|
||||
C(BOF) for inserting the line at the beginning of the file.
|
||||
- If specified regular expression has no matches, the line will be inserted at the end of the file. May not be used with C(backrefs).
|
||||
type: str
|
||||
choices: [ BOF, '*regex*' ]
|
||||
create:
|
||||
description:
|
||||
- Used with C(state=present). If specified, the file will be created if it does not already exist. By default it will fail if the file is missing.
|
||||
type: bool
|
||||
default: no
|
||||
validate:
|
||||
description:
|
||||
- Validation to run before copying into place. Use %s in the command to indicate the current file to validate.
|
||||
- The command is passed securely so shell features like expansion and pipes won't work.
|
||||
type: str
|
||||
encoding:
|
||||
description:
|
||||
- Specifies the encoding of the source text file to operate on (and thus what the output encoding will be). The default of C(auto) will cause
|
||||
the module to auto-detect the encoding of the source file and ensure that the modified file is written with the same encoding.
|
||||
- An explicit encoding can be passed as a string that is a valid value to pass to the .NET framework System.Text.Encoding.GetEncoding() method -
|
||||
see U(https://msdn.microsoft.com/en-us/library/system.text.encoding%28v=vs.110%29.aspx).
|
||||
- This is mostly useful with C(create=yes) if you want to create a new file with a specific encoding. If C(create=yes) is specified without a
|
||||
specific encoding, the default encoding (UTF-8, no BOM) will be used.
|
||||
type: str
|
||||
default: auto
|
||||
newline:
|
||||
description:
|
||||
- Specifies the line separator style to use for the modified file. This defaults to the windows line separator (C(\r\n)). Note that the indicated
|
||||
line separator will be used for file output regardless of the original line separator that appears in the input file.
|
||||
type: str
|
||||
choices: [ unix, windows ]
|
||||
default: windows
|
||||
notes:
|
||||
- As of Ansible 2.3, the I(dest) option has been changed to I(path) as default, but I(dest) still works as well.
|
||||
seealso:
|
||||
- module: assemble
|
||||
- module: lineinfile
|
||||
author:
|
||||
- Brian Lloyd (@brianlloyd)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Before Ansible 2.3, option 'dest', 'destfile' or 'name' was used instead of 'path'
|
||||
- name: Insert path without converting \r\n
|
||||
win_lineinfile:
|
||||
path: c:\file.txt
|
||||
line: c:\return\new
|
||||
|
||||
- win_lineinfile:
|
||||
path: C:\Temp\example.conf
|
||||
regex: '^name='
|
||||
line: 'name=JohnDoe'
|
||||
|
||||
- win_lineinfile:
|
||||
path: C:\Temp\example.conf
|
||||
regex: '^name='
|
||||
state: absent
|
||||
|
||||
- win_lineinfile:
|
||||
path: C:\Temp\example.conf
|
||||
regex: '^127\.0\.0\.1'
|
||||
line: '127.0.0.1 localhost'
|
||||
|
||||
- win_lineinfile:
|
||||
path: C:\Temp\httpd.conf
|
||||
regex: '^Listen '
|
||||
insertafter: '^#Listen '
|
||||
line: Listen 8080
|
||||
|
||||
- win_lineinfile:
|
||||
path: C:\Temp\services
|
||||
regex: '^# port for http'
|
||||
insertbefore: '^www.*80/tcp'
|
||||
line: '# port for http by default'
|
||||
|
||||
- name: Create file if it doesn't exist with a specific encoding
|
||||
win_lineinfile:
|
||||
path: C:\Temp\utf16.txt
|
||||
create: yes
|
||||
encoding: utf-16
|
||||
line: This is a utf-16 encoded file
|
||||
|
||||
- name: Add a line to a file and ensure the resulting file uses unix line separators
|
||||
win_lineinfile:
|
||||
path: C:\Temp\testfile.txt
|
||||
line: Line added to file
|
||||
newline: unix
|
||||
|
||||
- name: Update a line using backrefs
|
||||
win_lineinfile:
|
||||
path: C:\Temp\example.conf
|
||||
backrefs: yes
|
||||
regex: '(^name=)'
|
||||
line: '$1JohnDoe'
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
backup:
|
||||
description:
|
||||
- Name of the backup file that was created.
|
||||
- This is now deprecated, use C(backup_file) instead.
|
||||
returned: if backup=yes
|
||||
type: str
|
||||
sample: C:\Path\To\File.txt.11540.20150212-220915.bak
|
||||
backup_file:
|
||||
description: Name of the backup file that was created.
|
||||
returned: if backup=yes
|
||||
type: str
|
||||
sample: C:\Path\To\File.txt.11540.20150212-220915.bak
|
||||
'''
|
@ -1,444 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.AccessToken
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#Requires -Module Ansible.ModuleUtils.AddType
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
letter = @{ type = "str"; required = $true }
|
||||
path = @{ type = "path"; }
|
||||
state = @{ type = "str"; default = "present"; choices = @("absent", "present") }
|
||||
username = @{ type = "str" }
|
||||
password = @{ type = "str"; no_log = $true }
|
||||
}
|
||||
required_if = @(
|
||||
,@("state", "present", @("path"))
|
||||
)
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$letter = $module.Params.letter
|
||||
$path = $module.Params.path
|
||||
$state = $module.Params.state
|
||||
$username = $module.Params.username
|
||||
$password = $module.Params.password
|
||||
|
||||
if ($letter -notmatch "^[a-zA-z]{1}$") {
|
||||
$module.FailJson("letter must be a single letter from A-Z, was: $letter")
|
||||
}
|
||||
$letter_root = "$($letter):"
|
||||
|
||||
$module.Diff.before = ""
|
||||
$module.Diff.after = ""
|
||||
|
||||
Add-CSharpType -AnsibleModule $module -References @'
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ansible.MappedDrive
|
||||
{
|
||||
internal class NativeHelpers
|
||||
{
|
||||
public enum ResourceScope : uint
|
||||
{
|
||||
Connected = 0x00000001,
|
||||
GlobalNet = 0x00000002,
|
||||
Remembered = 0x00000003,
|
||||
Recent = 0x00000004,
|
||||
Context = 0x00000005,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ResourceType : uint
|
||||
{
|
||||
Any = 0x0000000,
|
||||
Disk = 0x00000001,
|
||||
Print = 0x00000002,
|
||||
Reserved = 0x00000008,
|
||||
Unknown = 0xFFFFFFFF,
|
||||
}
|
||||
|
||||
public enum CloseFlags : uint
|
||||
{
|
||||
None = 0x00000000,
|
||||
UpdateProfile = 0x00000001,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum AddFlags : uint
|
||||
{
|
||||
UpdateProfile = 0x00000001,
|
||||
UpdateRecent = 0x00000002,
|
||||
Temporary = 0x00000004,
|
||||
Interactive = 0x00000008,
|
||||
Prompt = 0x00000010,
|
||||
Redirect = 0x00000080,
|
||||
CurrentMedia = 0x00000200,
|
||||
CommandLine = 0x00000800,
|
||||
CmdSaveCred = 0x00001000,
|
||||
CredReset = 0x00002000,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct NETRESOURCEW
|
||||
{
|
||||
public ResourceScope dwScope;
|
||||
public ResourceType dwType;
|
||||
public UInt32 dwDisplayType;
|
||||
public UInt32 dwUsage;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string lpLocalName;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string lpRemoteName;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string lpComment;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string lpProvider;
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool CloseHandle(
|
||||
IntPtr hObject);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool ImpersonateLoggedOnUser(
|
||||
IntPtr hToken);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool RevertToSelf();
|
||||
|
||||
[DllImport("Mpr.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern UInt32 WNetAddConnection2W(
|
||||
NativeHelpers.NETRESOURCEW lpNetResource,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpPassword,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpUserName,
|
||||
NativeHelpers.AddFlags dwFlags);
|
||||
|
||||
[DllImport("Mpr.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern UInt32 WNetCancelConnection2W(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpName,
|
||||
NativeHelpers.CloseFlags dwFlags,
|
||||
bool fForce);
|
||||
|
||||
[DllImport("Mpr.dll")]
|
||||
public static extern UInt32 WNetCloseEnum(
|
||||
IntPtr hEnum);
|
||||
|
||||
[DllImport("Mpr.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern UInt32 WNetEnumResourceW(
|
||||
IntPtr hEnum,
|
||||
ref Int32 lpcCount,
|
||||
SafeMemoryBuffer lpBuffer,
|
||||
ref UInt32 lpBufferSize);
|
||||
|
||||
[DllImport("Mpr.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern UInt32 WNetOpenEnumW(
|
||||
NativeHelpers.ResourceScope dwScope,
|
||||
NativeHelpers.ResourceType dwType,
|
||||
UInt32 dwUsage,
|
||||
IntPtr lpNetResource,
|
||||
out IntPtr lphEnum);
|
||||
}
|
||||
|
||||
internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
public SafeMemoryBuffer() : base(true) { }
|
||||
public SafeMemoryBuffer(int cb) : base(true)
|
||||
{
|
||||
base.SetHandle(Marshal.AllocHGlobal(cb));
|
||||
}
|
||||
public SafeMemoryBuffer(IntPtr handle) : base(true)
|
||||
{
|
||||
base.SetHandle(handle);
|
||||
}
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
Marshal.FreeHGlobal(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Impersonation : IDisposable
|
||||
{
|
||||
private IntPtr hToken = IntPtr.Zero;
|
||||
|
||||
public Impersonation(IntPtr token)
|
||||
{
|
||||
hToken = token;
|
||||
if (token != IntPtr.Zero)
|
||||
if (!NativeMethods.ImpersonateLoggedOnUser(hToken))
|
||||
throw new Win32Exception("Failed to impersonate token with ImpersonateLoggedOnUser()");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (hToken != null)
|
||||
NativeMethods.RevertToSelf();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
~Impersonation() { Dispose(); }
|
||||
}
|
||||
|
||||
public class DriveInfo
|
||||
{
|
||||
public string Drive;
|
||||
public string Path;
|
||||
}
|
||||
|
||||
public class Win32Exception : System.ComponentModel.Win32Exception
|
||||
{
|
||||
private string _msg;
|
||||
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
|
||||
public Win32Exception(int errorCode, string message) : base(errorCode)
|
||||
{
|
||||
_msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
|
||||
}
|
||||
public override string Message { get { return _msg; } }
|
||||
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
|
||||
}
|
||||
|
||||
public class Utils
|
||||
{
|
||||
private const UInt32 ERROR_SUCCESS = 0x00000000;
|
||||
private const UInt32 ERROR_NO_MORE_ITEMS = 0x0000103;
|
||||
|
||||
public static void AddMappedDrive(string drive, string path, IntPtr iToken, string username = null, string password = null)
|
||||
{
|
||||
NativeHelpers.NETRESOURCEW resource = new NativeHelpers.NETRESOURCEW
|
||||
{
|
||||
dwType = NativeHelpers.ResourceType.Disk,
|
||||
lpLocalName = drive,
|
||||
lpRemoteName = path,
|
||||
};
|
||||
NativeHelpers.AddFlags dwFlags = NativeHelpers.AddFlags.UpdateProfile;
|
||||
// While WNetAddConnection2W supports user/pass, this is only used for the first connection and the
|
||||
// password is not remembered. We will delete the username mapping afterwards as it interferes with
|
||||
// the implicit credential cache used in Windows
|
||||
using (Impersonation imp = new Impersonation(iToken))
|
||||
{
|
||||
UInt32 res = NativeMethods.WNetAddConnection2W(resource, password, username, dwFlags);
|
||||
if (res != ERROR_SUCCESS)
|
||||
throw new Win32Exception((int)res, String.Format("Failed to map {0} to '{1}' with WNetAddConnection2W()", drive, path));
|
||||
}
|
||||
}
|
||||
|
||||
public static List<DriveInfo> GetMappedDrives(IntPtr iToken)
|
||||
{
|
||||
using (Impersonation imp = new Impersonation(iToken))
|
||||
{
|
||||
IntPtr enumPtr = IntPtr.Zero;
|
||||
UInt32 res = NativeMethods.WNetOpenEnumW(NativeHelpers.ResourceScope.Remembered, NativeHelpers.ResourceType.Disk,
|
||||
0, IntPtr.Zero, out enumPtr);
|
||||
if (res != ERROR_SUCCESS)
|
||||
throw new Win32Exception((int)res, "WNetOpenEnumW()");
|
||||
|
||||
List<DriveInfo> resources = new List<DriveInfo>();
|
||||
try
|
||||
{
|
||||
// MS recommend a buffer size of 16 KiB
|
||||
UInt32 bufferSize = 16384;
|
||||
int lpcCount = -1;
|
||||
|
||||
// keep iterating the enum until ERROR_NO_MORE_ITEMS is returned
|
||||
do
|
||||
{
|
||||
using (SafeMemoryBuffer buffer = new SafeMemoryBuffer((int)bufferSize))
|
||||
{
|
||||
res = NativeMethods.WNetEnumResourceW(enumPtr, ref lpcCount, buffer, ref bufferSize);
|
||||
if (res == ERROR_NO_MORE_ITEMS)
|
||||
continue;
|
||||
else if (res != ERROR_SUCCESS)
|
||||
throw new Win32Exception((int)res, "WNetEnumResourceW()");
|
||||
lpcCount = lpcCount < 0 ? 0 : lpcCount;
|
||||
|
||||
NativeHelpers.NETRESOURCEW[] rawResources = new NativeHelpers.NETRESOURCEW[lpcCount];
|
||||
PtrToStructureArray(rawResources, buffer.DangerousGetHandle());
|
||||
foreach (NativeHelpers.NETRESOURCEW resource in rawResources)
|
||||
{
|
||||
DriveInfo currentDrive = new DriveInfo
|
||||
{
|
||||
Drive = resource.lpLocalName,
|
||||
Path = resource.lpRemoteName,
|
||||
};
|
||||
resources.Add(currentDrive);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (res != ERROR_NO_MORE_ITEMS);
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.WNetCloseEnum(enumPtr);
|
||||
}
|
||||
|
||||
return resources;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveMappedDrive(string drive, IntPtr iToken)
|
||||
{
|
||||
using (Impersonation imp = new Impersonation(iToken))
|
||||
{
|
||||
UInt32 res = NativeMethods.WNetCancelConnection2W(drive, NativeHelpers.CloseFlags.UpdateProfile, true);
|
||||
if (res != ERROR_SUCCESS)
|
||||
throw new Win32Exception((int)res, String.Format("Failed to remove mapped drive {0} with WNetCancelConnection2W()", drive));
|
||||
}
|
||||
}
|
||||
|
||||
private static void PtrToStructureArray<T>(T[] array, IntPtr ptr)
|
||||
{
|
||||
IntPtr ptrOffset = ptr;
|
||||
for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T))))
|
||||
array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
Function Get-LimitedToken {
|
||||
$h_process = [Ansible.AccessToken.TokenUtil]::OpenProcess()
|
||||
$h_token = [Ansible.AccessToken.TokenUtil]::OpenProcessToken($h_process, "Duplicate, Query")
|
||||
|
||||
try {
|
||||
# If we don't have a Full token, we don't need to get the limited one to set a mapped drive
|
||||
$tet = [Ansible.AccessToken.TokenUtil]::GetTokenElevationType($h_token)
|
||||
if ($tet -ne [Ansible.AccessToken.TokenElevationType]::Full) {
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($system_token in [Ansible.AccessToken.TokenUtil]::EnumerateUserTokens("S-1-5-18", "Duplicate")) {
|
||||
# To get the TokenLinkedToken we need the SeTcbPrivilege, not all SYSTEM tokens have this assigned so
|
||||
# we need to check before impersonating that token
|
||||
$token_privileges = [Ansible.AccessToken.TokenUtil]::GetTokenPrivileges($system_token)
|
||||
if ($null -eq ($token_privileges | Where-Object { $_.Name -eq "SeTcbPrivilege" })) {
|
||||
continue
|
||||
}
|
||||
|
||||
[Ansible.AccessToken.TokenUtil]::ImpersonateToken($system_token)
|
||||
try {
|
||||
return [Ansible.AccessToken.TokenUtil]::GetTokenLinkedToken($h_token)
|
||||
} finally {
|
||||
[Ansible.AccessToken.TokenUtil]::RevertToSelf()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$h_token.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
When we run with become and UAC is enabled, the become process will most likely be the Admin/Full token. This is
|
||||
an issue with the WNetConnection APIs as the Full token is unable to add/enumerate/remove connections due to
|
||||
Windows storing the connection details on each token session ID. Unless EnabledLinkedConnections (reg key) is
|
||||
set to 1, the Full token is unable to manage connections in a persisted way whereas the Limited token is. This
|
||||
is similar to running 'net use' normally and an admin process is unable to see those and vice versa.
|
||||
|
||||
To overcome this problem, we attempt to get a handle on the Limited token for the current logon and impersonate
|
||||
that before making any WNetConnection calls. If the token is not split, or we are already running on the Limited
|
||||
token then no impersonatoin is used/required. This allows the module to run with become (required to access the
|
||||
credential store) but still be able to manage the mapped connections.
|
||||
|
||||
These are the following scenarios we have to handle;
|
||||
|
||||
1. Run without become
|
||||
A network logon is usually not split so GetLimitedToken() will return $null and no impersonation is needed
|
||||
2. Run with become on admin user with admin priv
|
||||
We will have a Full token, GetLimitedToken() will return the limited token and impersonation is used
|
||||
3. Run with become on admin user without admin priv
|
||||
We are already running with a Limited token, GetLimitedToken() return $nul and no impersonation is needed
|
||||
4. Run with become on standard user
|
||||
There's no split token, GetLimitedToken() will return $null and no impersonation is needed
|
||||
#>
|
||||
$impersonation_token = Get-LimitedToken
|
||||
|
||||
try {
|
||||
$i_token_ptr = [System.IntPtr]::Zero
|
||||
if ($null -ne $impersonation_token) {
|
||||
$i_token_ptr = $impersonation_token.DangerousGetHandle()
|
||||
}
|
||||
|
||||
$existing_targets = [Ansible.MappedDrive.Utils]::GetMappedDrives($i_token_ptr)
|
||||
$existing_target = $existing_targets | Where-Object { $_.Drive -eq $letter_root }
|
||||
|
||||
if ($existing_target) {
|
||||
$module.Diff.before = @{
|
||||
letter = $letter
|
||||
path = $existing_target.Path
|
||||
}
|
||||
}
|
||||
|
||||
if ($state -eq "absent") {
|
||||
if ($null -ne $existing_target) {
|
||||
if ($null -ne $path -and $existing_target.Path -ne $path) {
|
||||
$module.FailJson("did not delete mapped drive $letter, the target path is pointing to a different location at $( $existing_target.Path )")
|
||||
}
|
||||
if (-not $module.CheckMode) {
|
||||
[Ansible.MappedDrive.Utils]::RemoveMappedDrive($letter_root, $i_token_ptr)
|
||||
}
|
||||
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
} else {
|
||||
$physical_drives = Get-PSDrive -PSProvider "FileSystem"
|
||||
if ($letter -in $physical_drives.Name) {
|
||||
$module.FailJson("failed to create mapped drive $letter, this letter is in use and is pointing to a non UNC path")
|
||||
}
|
||||
|
||||
# PowerShell converts a $null value to "" when crossing the .NET marshaler, we need to convert the input
|
||||
# to a missing value so it uses the defaults. We also need to Invoke it with MethodInfo.Invoke so the defaults
|
||||
# are still used
|
||||
$input_username = $username
|
||||
if ($null -eq $username) {
|
||||
$input_username = [Type]::Missing
|
||||
}
|
||||
$input_password = $password
|
||||
if ($null -eq $password) {
|
||||
$input_password = [Type]::Missing
|
||||
}
|
||||
$add_method = [Ansible.MappedDrive.Utils].GetMethod("AddMappedDrive")
|
||||
|
||||
if ($null -ne $existing_target) {
|
||||
if ($existing_target.Path -ne $path) {
|
||||
if (-not $module.CheckMode) {
|
||||
[Ansible.MappedDrive.Utils]::RemoveMappedDrive($letter_root, $i_token_ptr)
|
||||
$add_method.Invoke($null, [Object[]]@($letter_root, $path, $i_token_ptr, $input_username, $input_password))
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
} else {
|
||||
if (-not $module.CheckMode) {
|
||||
$add_method.Invoke($null, [Object[]]@($letter_root, $path, $i_token_ptr, $input_username, $input_password))
|
||||
}
|
||||
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
|
||||
# If username was set and we made a change, remove the UserName value so Windows will continue to use the cred
|
||||
# cache. If we don't do this then the drive will fail to map in the future as WNetAddConnection does not cache
|
||||
# the password and relies on the credential store.
|
||||
if ($null -ne $username -and $module.Result.changed -and -not $module.CheckMode) {
|
||||
Set-ItemProperty -Path HKCU:\Network\$letter -Name UserName -Value "" -WhatIf:$module.CheckMode
|
||||
}
|
||||
|
||||
$module.Diff.after = @{
|
||||
letter = $letter
|
||||
path = $path
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if ($null -ne $impersonation_token) {
|
||||
$impersonation_token.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
@ -1,155 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub, actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_mapped_drive
|
||||
version_added: '2.4'
|
||||
short_description: Map network drives for users
|
||||
description:
|
||||
- Allows you to modify mapped network drives for individual users.
|
||||
- Also support WebDAV endpoints in the UNC form.
|
||||
options:
|
||||
letter:
|
||||
description:
|
||||
- The letter of the network path to map to.
|
||||
- This letter must not already be in use with Windows.
|
||||
type: str
|
||||
required: yes
|
||||
password:
|
||||
description:
|
||||
- The password for C(username) that is used when testing the initial
|
||||
connection.
|
||||
- This is never saved with a mapped drive, use the M(win_credential) module
|
||||
to persist a username and password for a host.
|
||||
type: str
|
||||
path:
|
||||
description:
|
||||
- The UNC path to map the drive to.
|
||||
- If pointing to a WebDAV location this must still be in a UNC path in the
|
||||
format C(\\hostname\path) and not a URL, see examples for more details.
|
||||
- To specify a C(https) WebDAV path, add C(@SSL) after the hostname. To
|
||||
specify a custom WebDAV port add C(@<port num>) after the C(@SSL) or
|
||||
hostname portion of the UNC path, e.g. C(\\server@SSL@1234) or
|
||||
C(\\server@1234).
|
||||
- This is required if C(state=present).
|
||||
- If C(state=absent) and I(path) is not set, the module will delete the
|
||||
mapped drive regardless of the target.
|
||||
- If C(state=absent) and the I(path) is set, the module will throw an error
|
||||
if path does not match the target of the mapped drive.
|
||||
type: path
|
||||
state:
|
||||
description:
|
||||
- If C(present) will ensure the mapped drive exists.
|
||||
- If C(absent) will ensure the mapped drive does not exist.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
username:
|
||||
description:
|
||||
- The username that is used when testing the initial connection.
|
||||
- This is never saved with a mapped drive, the M(win_credential) module
|
||||
to persist a username and password for a host.
|
||||
- This is required if the mapped drive requires authentication with
|
||||
custom credentials and become, or CredSSP cannot be used.
|
||||
- If become or CredSSP is used, any credentials saved with
|
||||
M(win_credential) will automatically be used instead.
|
||||
type: str
|
||||
notes:
|
||||
- You cannot use this module to access a mapped drive in another Ansible task,
|
||||
drives mapped with this module are only accessible when logging in
|
||||
interactively with the user through the console or RDP.
|
||||
- It is recommend to run this module with become or CredSSP when the remote
|
||||
path requires authentication.
|
||||
- When using become or CredSSP, the task will have access to any local
|
||||
credentials stored in the user's vault.
|
||||
- If become or CredSSP is not available, the I(username) and I(password)
|
||||
options can be used for the initial authentication but these are not
|
||||
persisted.
|
||||
- WebDAV paths must have the WebDAV client feature installed for this module to
|
||||
map those paths. This is installed by default on desktop Windows editions but
|
||||
Windows Server hosts need to install the C(WebDAV-Redirector) feature using
|
||||
M(win_feature).
|
||||
seealso:
|
||||
- module: win_credential
|
||||
author:
|
||||
- Jordan Borean (@jborean93)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create a mapped drive under Z
|
||||
win_mapped_drive:
|
||||
letter: Z
|
||||
path: \\domain\appdata\accounting
|
||||
|
||||
- name: Delete any mapped drives under Z
|
||||
win_mapped_drive:
|
||||
letter: Z
|
||||
state: absent
|
||||
|
||||
- name: Only delete the mapped drive Z if the paths match (error is thrown otherwise)
|
||||
win_mapped_drive:
|
||||
letter: Z
|
||||
path: \\domain\appdata\accounting
|
||||
state: absent
|
||||
|
||||
- name: Create mapped drive with credentials and save the username and password
|
||||
block:
|
||||
- name: Save the network credentials required for the mapped drive
|
||||
win_credential:
|
||||
name: server
|
||||
type: domain_password
|
||||
username: username@DOMAIN
|
||||
secret: Password01
|
||||
state: present
|
||||
|
||||
- name: Create a mapped drive that requires authentication
|
||||
win_mapped_drive:
|
||||
letter: M
|
||||
path: \\SERVER\C$
|
||||
state: present
|
||||
vars:
|
||||
# become is required to save and retrieve the credentials in the tasks
|
||||
ansible_become: yes
|
||||
ansible_become_method: runas
|
||||
ansible_become_user: '{{ ansible_user }}'
|
||||
ansible_become_pass: '{{ ansible_password }}'
|
||||
|
||||
- name: Create mapped drive with credentials that do not persist on the next logon
|
||||
win_mapped_drive:
|
||||
letter: M
|
||||
path: \\SERVER\C$
|
||||
state: present
|
||||
username: '{{ ansible_user }}'
|
||||
password: '{{ ansible_password }}'
|
||||
|
||||
# This should only be required for Windows Server OS'
|
||||
- name: Ensure WebDAV client feature is installed
|
||||
win_feature:
|
||||
name: WebDAV-Redirector
|
||||
state: present
|
||||
register: webdav_feature
|
||||
|
||||
- name: Reboot after installing WebDAV client feature
|
||||
win_reboot:
|
||||
when: webdav_feature.reboot_required
|
||||
|
||||
- name: Map the HTTPS WebDAV location
|
||||
win_mapped_drive:
|
||||
letter: W
|
||||
path: \\live.sysinternals.com@SSL\tools # https://live.sysinternals.com/tools
|
||||
state: present
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
@ -1,52 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2016, Jon Hawkesworth (@jhawkesworth) <figs@unity.demon.co.uk>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#
|
||||
$stopwatch = [system.diagnostics.stopwatch]::startNew()
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
|
||||
$display_seconds = Get-AnsibleParam -obj $params -name "display_seconds" -type "int" -default "10"
|
||||
$msg = Get-AnsibleParam -obj $params -name "msg" -type "str" -default "Hello world!"
|
||||
$to = Get-AnsibleParam -obj $params -name "to" -type "str" -default "*"
|
||||
$wait = Get-AnsibleParam -obj $params -name "wait" -type "bool" -default $false
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
display_seconds = $display_seconds
|
||||
msg = $msg
|
||||
wait = $wait
|
||||
}
|
||||
|
||||
if ($msg.Length -gt 255) {
|
||||
Fail-Json -obj $result -message "msg length must be less than 256 characters, current length: $($msg.Length)"
|
||||
}
|
||||
|
||||
$msg_args = @($to, "/TIME:$display_seconds")
|
||||
|
||||
if ($wait) {
|
||||
$msg_args += "/W"
|
||||
}
|
||||
|
||||
$msg_args += $msg
|
||||
if (-not $check_mode) {
|
||||
$output = & msg.exe $msg_args 2>&1
|
||||
$result.rc = $LASTEXITCODE
|
||||
}
|
||||
|
||||
$endsend_at = Get-Date| Out-String
|
||||
$stopwatch.Stop()
|
||||
|
||||
$result.changed = $true
|
||||
$result.runtime_seconds = $stopwatch.Elapsed.TotalSeconds
|
||||
$result.sent_localtime = $endsend_at.Trim()
|
||||
|
||||
if ($result.rc -ne 0 ) {
|
||||
Fail-Json -obj $result -message "$output"
|
||||
}
|
||||
|
||||
Exit-Json $result
|
@ -1,96 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Jon Hawkesworth (@jhawkesworth) <figs@unity.demon.co.uk>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_msg
|
||||
version_added: "2.3"
|
||||
short_description: Sends a message to logged in users on Windows hosts
|
||||
description:
|
||||
- Wraps the msg.exe command in order to send messages to Windows hosts.
|
||||
options:
|
||||
to:
|
||||
description:
|
||||
- Who to send the message to. Can be a username, sessionname or sessionid.
|
||||
type: str
|
||||
default: '*'
|
||||
display_seconds:
|
||||
description:
|
||||
- How long to wait for receiver to acknowledge message, in seconds.
|
||||
type: int
|
||||
default: 10
|
||||
wait:
|
||||
description:
|
||||
- Whether to wait for users to respond. Module will only wait for the number of seconds specified in display_seconds or 10 seconds if not specified.
|
||||
However, if I(wait) is C(yes), the message is sent to each logged on user in turn, waiting for the user to either press 'ok' or for
|
||||
the timeout to elapse before moving on to the next user.
|
||||
type: bool
|
||||
default: 'no'
|
||||
msg:
|
||||
description:
|
||||
- The text of the message to be displayed.
|
||||
- The message must be less than 256 characters.
|
||||
type: str
|
||||
default: Hello world!
|
||||
notes:
|
||||
- This module must run on a windows host, so ensure your play targets windows
|
||||
hosts, or delegates to a windows host.
|
||||
- Messages are only sent to the local host where the module is run.
|
||||
- The module does not support sending to users listed in a file.
|
||||
- Setting wait to C(yes) can result in long run times on systems with many logged in users.
|
||||
seealso:
|
||||
- module: win_say
|
||||
- module: win_toast
|
||||
author:
|
||||
- Jon Hawkesworth (@jhawkesworth)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Warn logged in users of impending upgrade
|
||||
win_msg:
|
||||
display_seconds: 60
|
||||
msg: Automated upgrade about to start. Please save your work and log off before {{ deployment_start_time }}
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
msg:
|
||||
description: Test of the message that was sent.
|
||||
returned: changed
|
||||
type: str
|
||||
sample: Automated upgrade about to start. Please save your work and log off before 22 July 2016 18:00:00
|
||||
display_seconds:
|
||||
description: Value of display_seconds module parameter.
|
||||
returned: success
|
||||
type: str
|
||||
sample: 10
|
||||
rc:
|
||||
description: The return code of the API call.
|
||||
returned: always
|
||||
type: int
|
||||
sample: 0
|
||||
runtime_seconds:
|
||||
description: How long the module took to run on the remote windows host.
|
||||
returned: success
|
||||
type: str
|
||||
sample: 22 July 2016 17:45:51
|
||||
sent_localtime:
|
||||
description: local time from windows host when the message was sent.
|
||||
returned: success
|
||||
type: str
|
||||
sample: 22 July 2016 17:45:51
|
||||
wait:
|
||||
description: Value of wait module parameter.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: false
|
||||
'''
|
@ -1,72 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2019, Thomas Moore (@tmmruk) <hi@tmmr.uk>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
state = @{ type = "str"; choices = "enabled", "disabled", "default"; required = $true }
|
||||
adapter_names = @{ type = "list"; required = $false }
|
||||
}
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
$module.Result.reboot_required = $false
|
||||
|
||||
$state = $module.Params.state
|
||||
$adapter_names = $module.Params.adapter_names
|
||||
|
||||
switch ( $state )
|
||||
{
|
||||
'default'{ $netbiosoption = 0 }
|
||||
enabled { $netbiosoption = 1 }
|
||||
disabled { $netbiosoption = 2 }
|
||||
}
|
||||
|
||||
if(-not $adapter_names)
|
||||
{
|
||||
# Target all network adapters on the system
|
||||
$get_params = @{
|
||||
ClassName = 'Win32_NetworkAdapterConfiguration'
|
||||
Filter = 'IPEnabled=true'
|
||||
Property = @('MacAddress', 'TcpipNetbiosOptions')
|
||||
}
|
||||
$target_adapters_config = Get-CimInstance @get_params
|
||||
}
|
||||
else
|
||||
{
|
||||
$get_params = @{
|
||||
Class = 'Win32_NetworkAdapter'
|
||||
Filter = ($adapter_names | ForEach-Object -Process { "NetConnectionId='$_'" }) -join " OR "
|
||||
KeyOnly = $true
|
||||
}
|
||||
$target_adapters_config = Get-CimInstance @get_params | Get-CimAssociatedInstance -ResultClass 'Win32_NetworkAdapterConfiguration'
|
||||
if(($target_adapters_config | Measure-Object).Count -ne $adapter_names.Count)
|
||||
{
|
||||
$module.FailJson("Not all of the target adapter names could be found on the system. No configuration changes have been made. $adapter_names")
|
||||
}
|
||||
}
|
||||
|
||||
foreach($adapter in $target_adapters_config)
|
||||
{
|
||||
if($adapter.TcpipNetbiosOptions -ne $netbiosoption)
|
||||
{
|
||||
if(-not $module.CheckMode)
|
||||
{
|
||||
$result = Invoke-CimMethod -InputObject $adapter -MethodName SetTcpipNetbios -Arguments @{TcpipNetbiosOptions=$netbiosoption}
|
||||
switch ( $result.ReturnValue )
|
||||
{
|
||||
0 { <# Success no reboot required #> }
|
||||
1 { $module.Result.reboot_required = $true }
|
||||
100 { $module.Warn("DHCP not enabled on adapter $($adapter.MacAddress). Unable to set default. Try using disabled or enabled options instead.") }
|
||||
default { $module.FailJson("An error occurred while setting TcpipNetbios options on adapter $($adapter.MacAddress). Return code $($result.ReturnValue).") }
|
||||
}
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
@ -1,80 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Thomas Moore (@tmmruk)
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_netbios
|
||||
version_added: '2.9'
|
||||
short_description: Manage NetBIOS over TCP/IP settings on Windows.
|
||||
description:
|
||||
- Enables or disables NetBIOS on Windows network adapters.
|
||||
- Can be used to protect a system against NBT-NS poisoning and avoid NBNS broadcast storms.
|
||||
- Settings can be applied system wide or per adapter.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether NetBIOS should be enabled, disabled, or default (use setting from DHCP server or if static IP address is assigned enable NetBIOS).
|
||||
choices:
|
||||
- enabled
|
||||
- disabled
|
||||
- default
|
||||
required: yes
|
||||
type: str
|
||||
adapter_names:
|
||||
description:
|
||||
- List of adapter names for which to manage NetBIOS settings. If this option is omitted then configuration is applied to all adapters on the system.
|
||||
- The adapter name used is the connection caption in the Network Control Panel or via C(Get-NetAdapter), eg C(Ethernet 2).
|
||||
type: list
|
||||
required: no
|
||||
|
||||
author:
|
||||
- Thomas Moore (@tmmruk)
|
||||
notes:
|
||||
- Changing NetBIOS settings does not usually require a reboot and will take effect immediately.
|
||||
- UDP port 137/138/139 will no longer be listening once NetBIOS is disabled.
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Disable NetBIOS system wide
|
||||
win_netbios:
|
||||
state: disabled
|
||||
|
||||
- name: Disable NetBIOS on Ethernet2
|
||||
win_netbios:
|
||||
state: disabled
|
||||
adapter_names:
|
||||
- Ethernet2
|
||||
|
||||
- name: Enable NetBIOS on Public and Backup adapters
|
||||
win_netbios:
|
||||
state: enabled
|
||||
adapter_names:
|
||||
- Public
|
||||
- Backup
|
||||
|
||||
- name: Set NetBIOS to system default on all adapters
|
||||
win_netbios:
|
||||
state: default
|
||||
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
reboot_required:
|
||||
description: Boolean value stating whether a system reboot is required.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
'''
|
@ -1,498 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2015, George Frank <george@georgefrank.net>
|
||||
# Copyright: (c) 2015, Adam Keech <akeech@chathamfinancial.com>
|
||||
# Copyright: (c) 2015, Hans-Joachim Kliemeck <git@kliemeck.de>
|
||||
# Copyright: (c) 2019, Kevin Subileau (@ksubileau)
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#Requires -Module Ansible.ModuleUtils.ArgvParser
|
||||
#Requires -Module Ansible.ModuleUtils.CommandUtil
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$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 "path"
|
||||
$appDirectory = Get-AnsibleParam -obj $params -name "working_directory" -aliases "app_directory","chdir" -type "path"
|
||||
$appParameters = Get-AnsibleParam -obj $params -name "app_parameters"
|
||||
$appArguments = Get-AnsibleParam -obj $params -name "arguments" -aliases "app_parameters_free_form"
|
||||
|
||||
$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"
|
||||
|
||||
$app_rotate_bytes = Get-AnsibleParam -obj $params -name "app_rotate_bytes" -type "int" -default 104858
|
||||
$app_rotate_online = Get-AnsibleParam -obj $params -name "app_rotate_online" -type "int" -default 0 -validateset 0,1
|
||||
$app_stop_method_console = Get-AnsibleParam -obj $params -name "app_stop_method_console" -type "int"
|
||||
$app_stop_method_skip = Get-AnsibleParam -obj $params -name "app_stop_method_skip" -type "int" -validateset 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
|
||||
|
||||
# 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"
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
$diff_text = $null
|
||||
|
||||
function Invoke-NssmCommand {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true,ValueFromRemainingArguments=$true)]
|
||||
[string[]]$arguments
|
||||
)
|
||||
|
||||
$command = Argv-ToString -arguments (@($executable) + $arguments)
|
||||
$result = Run-Command -command $command
|
||||
|
||||
$result.arguments = $command
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
function Get-NssmServiceStatus {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$service
|
||||
)
|
||||
|
||||
return Invoke-NssmCommand -arguments @("status", $service)
|
||||
}
|
||||
|
||||
function Get-NssmServiceParameter {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$service,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[Alias("param")]
|
||||
[string]$parameter,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$subparameter
|
||||
)
|
||||
|
||||
$arguments = @("get", $service, $parameter)
|
||||
if($subparameter -ne "") {
|
||||
$arguments += $subparameter
|
||||
}
|
||||
return Invoke-NssmCommand -arguments $arguments
|
||||
}
|
||||
|
||||
function Set-NssmServiceParameter {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$service,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$parameter,
|
||||
[Parameter(Mandatory=$true,ValueFromRemainingArguments=$true)]
|
||||
[Alias("value")]
|
||||
[string[]]$arguments
|
||||
)
|
||||
|
||||
return Invoke-NssmCommand -arguments (@("set", $service, $parameter) + $arguments)
|
||||
}
|
||||
|
||||
function Reset-NssmServiceParameter {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$service,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[Alias("param")]
|
||||
[string]$parameter
|
||||
)
|
||||
|
||||
return Invoke-NssmCommand -arguments @("reset", $service, $parameter)
|
||||
}
|
||||
|
||||
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]$service,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$parameter,
|
||||
|
||||
[Parameter(Mandatory=$true,ValueFromRemainingArguments=$true)]
|
||||
[AllowEmptyString()]
|
||||
[AllowNull()]
|
||||
[Alias("value")]
|
||||
[string[]]$arguments,
|
||||
|
||||
[Parameter()]
|
||||
[scriptblock]$compare = {param($actual,$expected) @(Compare-Object -ReferenceObject $actual -DifferenceObject $expected).Length -eq 0}
|
||||
)
|
||||
|
||||
if($null -eq $arguments) { return }
|
||||
$arguments = @($arguments | Where-Object { $_ -ne '' })
|
||||
|
||||
$nssm_result = Get-NssmServiceParameter -service $service -parameter $parameter
|
||||
|
||||
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 retrieving $parameter for service ""$service"""
|
||||
}
|
||||
|
||||
$current_values = @($nssm_result.stdout.split("`n`r") | Where-Object { $_ -ne '' })
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 setting $parameter for service ""$service"""
|
||||
}
|
||||
}
|
||||
|
||||
$script:diff_text += "-$parameter = $($current_values -join ', ')`n+$parameter = $($arguments -join ', ')`n"
|
||||
$result.changed_by = $parameter
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
function Test-NssmServiceExists {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$service
|
||||
)
|
||||
|
||||
return [bool](Get-Service -Name $service -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Invoke-NssmStart {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$service
|
||||
)
|
||||
|
||||
$nssm_result = Invoke-NssmCommand -arguments @("start", $service)
|
||||
|
||||
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 starting service ""$service"""
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-NssmStop {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$service
|
||||
)
|
||||
|
||||
$nssm_result = Invoke-NssmCommand -arguments @("stop", $service)
|
||||
|
||||
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 Start-NssmService {
|
||||
[CmdletBinding(SupportsShouldProcess=$true)]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$service
|
||||
)
|
||||
|
||||
$currentStatus = Get-NssmServiceStatus -service $service
|
||||
|
||||
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"""
|
||||
}
|
||||
|
||||
if ($currentStatus.stdout -notlike "*SERVICE_RUNNING*") {
|
||||
if ($PSCmdlet.ShouldProcess($service, "Start service")) {
|
||||
switch -wildcard ($currentStatus.stdout) {
|
||||
"*SERVICE_STOPPED*" { Invoke-NssmStart -service $service }
|
||||
|
||||
"*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 = "start_service"
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-NssmService {
|
||||
[CmdletBinding(SupportsShouldProcess=$true)]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$service
|
||||
)
|
||||
|
||||
$currentStatus = Get-NssmServiceStatus -service $service
|
||||
|
||||
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"""
|
||||
}
|
||||
|
||||
if ($currentStatus.stdout -notlike "*SERVICE_STOPPED*") {
|
||||
if ($PSCmdlet.ShouldProcess($service, "Stop service")) {
|
||||
Invoke-NssmStop -service $service
|
||||
}
|
||||
|
||||
$result.changed_by = "stop_service"
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
if (($null -ne $appParameters) -and ($null -ne $appArguments)) {
|
||||
Fail-Json $result "'app_parameters' and 'arguments' are mutually exclusive but have both been set."
|
||||
}
|
||||
|
||||
# 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 ($appParameters -isnot [string]) {
|
||||
Fail-Json -obj $result -message "The app_parameters parameter must be a string representing a dictionary."
|
||||
}
|
||||
|
||||
# Convert dict-as-string form to list
|
||||
$escapedAppParameters = $appParameters.TrimStart("@").TrimStart("{").TrimEnd("}").Replace("; ","`n").Replace("\","\\")
|
||||
$appParametersHash = ConvertFrom-StringData -StringData $escapedAppParameters
|
||||
|
||||
$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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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."
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $application -PathType Leaf)) {
|
||||
Fail-Json -obj $result -message "The application specified ""$application"" does not exist on the host."
|
||||
}
|
||||
|
||||
if($null -eq $appDirectory) {
|
||||
$appDirectory = (Get-Item -LiteralPath $application).DirectoryName
|
||||
}
|
||||
|
||||
if ($user -and -not $password) {
|
||||
Fail-Json -obj $result -message "User without password is informed for service ""$name"""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$service_exists = Test-NssmServiceExists -service $name
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$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
|
||||
}
|
||||
|
||||
$diff_text_added_prefix = '+'
|
||||
$result.changed_by = "install_service"
|
||||
$result.changed = $true
|
||||
}
|
||||
|
||||
$diff_text += "$diff_text_added_prefix[$name]`n"
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
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 ($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
|
||||
|
||||
Update-NssmServiceParameter -parameter "AppParameters" -value $singleLineParams @common_params
|
||||
}
|
||||
|
||||
|
||||
Update-NssmServiceParameter -parameter "AppStdout" -value $stdoutFile @common_params
|
||||
Update-NssmServiceParameter -parameter "AppStderr" -value $stderrFile @common_params
|
||||
|
||||
###
|
||||
# Setup file rotation so we don't accidentally consume too much disk
|
||||
###
|
||||
|
||||
#set files to overwrite
|
||||
Update-NssmServiceParameter -parameter "AppStdoutCreationDisposition" -value 2 @common_params
|
||||
Update-NssmServiceParameter -parameter "AppStderrCreationDisposition" -value 2 @common_params
|
||||
|
||||
#enable file rotation
|
||||
Update-NssmServiceParameter -parameter "AppRotateFiles" -value 1 @common_params
|
||||
|
||||
#don't rotate until the service restarts
|
||||
Update-NssmServiceParameter -parameter "AppRotateOnline" -value $app_rotate_online @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 $app_rotate_bytes @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
|
||||
}
|
||||
$mappedMode = $start_modes_map.$startMode
|
||||
Update-NssmServiceParameter -parameter "Start" -value $mappedMode @common_params
|
||||
if ($state -in "stopped","restarted") {
|
||||
Stop-NssmService @common_params
|
||||
}
|
||||
|
||||
if($state -in "started","restarted") {
|
||||
Start-NssmService @common_params
|
||||
}
|
||||
########################################################################
|
||||
|
||||
# Added per users` requests
|
||||
if ($null -ne $app_stop_method_console)
|
||||
{
|
||||
Update-NssmServiceParameter -parameter "AppStopMethodConsole" -value $app_stop_method_console @common_params
|
||||
}
|
||||
|
||||
if ($null -ne $app_stop_method_skip)
|
||||
{
|
||||
Update-NssmServiceParameter -parameter "AppStopMethodSkip" -value $app_stop_method_skip @common_params
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($diff_mode -and $result.changed -eq $true) {
|
||||
$result.diff = @{
|
||||
prepared = $diff_text
|
||||
}
|
||||
}
|
||||
|
||||
Exit-Json $result
|
@ -1,217 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2015, Heyo
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_nssm
|
||||
version_added: "2.0"
|
||||
short_description: Install a service using NSSM
|
||||
description:
|
||||
- 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:
|
||||
name:
|
||||
description:
|
||||
- Name of the service to operate on.
|
||||
type: str
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- State of the service on the system.
|
||||
- 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: present
|
||||
application:
|
||||
description:
|
||||
- The application binary to run as a service
|
||||
- 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: path
|
||||
stderr_file:
|
||||
description:
|
||||
- Path to receive error output.
|
||||
type: path
|
||||
app_parameters:
|
||||
description:
|
||||
- A string representing a dictionary of parameters to be passed to the application when it starts.
|
||||
- DEPRECATED since v2.8, please use I(arguments) instead.
|
||||
- This is mutually exclusive with I(arguments).
|
||||
type: str
|
||||
arguments:
|
||||
description:
|
||||
- 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:
|
||||
- If C(auto) is selected, the service will start at bootup.
|
||||
- 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
|
||||
app_rotate_bytes:
|
||||
description:
|
||||
- NSSM will not rotate any file which is smaller than the configured number of bytes.
|
||||
type: int
|
||||
default: 104858
|
||||
version_added: "2.10"
|
||||
app_rotate_online:
|
||||
description:
|
||||
- If set to 1, nssm can rotate files which grow to the configured file size limit while the service is running.
|
||||
type: int
|
||||
choices:
|
||||
- 0
|
||||
- 1
|
||||
default: 0
|
||||
version_added: "2.10"
|
||||
app_stop_method_console:
|
||||
description:
|
||||
- Time to wait after sending Control-C.
|
||||
type: int
|
||||
version_added: "2.10"
|
||||
app_stop_method_skip:
|
||||
description:
|
||||
- To disable service shutdown methods, set to the sum of one or more of the numbers
|
||||
- 1 - Don't send Control-C to the console.
|
||||
- 2 - Don't send WM_CLOSE to windows.
|
||||
- 4 - Don't send WM_QUIT to threads.
|
||||
- 8 - Don't call TerminateProcess().
|
||||
type: int
|
||||
choices:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
- 8
|
||||
- 9
|
||||
- 10
|
||||
- 11
|
||||
- 12
|
||||
- 13
|
||||
- 14
|
||||
- 15
|
||||
version_added: "2.10"
|
||||
seealso:
|
||||
- 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)
|
||||
- Shachaf Goldstein (@Shachaf92)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Install the foo service
|
||||
win_nssm:
|
||||
name: foo
|
||||
application: C:\windows\foo.exe
|
||||
|
||||
# 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
|
||||
|
||||
# This is strictly equivalent to the previous example
|
||||
- name: Install the Consul service with an arbitrary string of parameters
|
||||
win_nssm:
|
||||
name: Consul
|
||||
application: C:\consul\consul.exe
|
||||
arguments: agent -config-dir=C:\consul\config
|
||||
|
||||
|
||||
# Install the foo service, and 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
|
||||
|
||||
- name: Configure and start the foo service using win_service
|
||||
win_service:
|
||||
name: foo
|
||||
dependencies: [ adf, tcpip ]
|
||||
username: foouser
|
||||
password: secret
|
||||
start_mode: manual
|
||||
state: started
|
||||
|
||||
- name: Remove the foo service
|
||||
win_nssm:
|
||||
name: foo
|
||||
state: absent
|
||||
'''
|
@ -1,202 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Liran Nisanov <lirannis@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
########
|
||||
|
||||
Function Remove-Pagefile($path, $whatif)
|
||||
{
|
||||
Get-CIMInstance Win32_PageFileSetting | Where-Object { $_.Name -eq $path } | Remove-CIMInstance -WhatIf:$whatif
|
||||
}
|
||||
|
||||
Function Get-Pagefile($path)
|
||||
{
|
||||
Get-CIMInstance Win32_PageFileSetting | Where-Object { $_.Name -eq $path }
|
||||
}
|
||||
|
||||
########
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$check_mode = Get-AnsibleParam -obj $params -name '_ansible_check_mode' -type 'bool' -default $false
|
||||
|
||||
$automatic = Get-AnsibleParam -obj $params -name "automatic" -type "bool"
|
||||
$drive = Get-AnsibleParam -obj $params -name "drive" -type "str"
|
||||
$fullPath = $drive + ":\pagefile.sys"
|
||||
$initialSize = Get-AnsibleParam -obj $params -name "initial_size" -type "int"
|
||||
$maximumSize = Get-AnsibleParam -obj $params -name "maximum_size" -type "int"
|
||||
$override = Get-AnsibleParam -obj $params -name "override" -type "bool" -default $true
|
||||
$removeAll = Get-AnsibleParam -obj $params -name "remove_all" -type "bool" -default $false
|
||||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "query" -validateset "present","absent","query"
|
||||
$systemManaged = Get-AnsibleParam -obj $params -name "system_managed" -type "bool" -default $false
|
||||
$testPath = Get-AnsibleParam -obj $params -name "test_path" -type "bool" -default $true
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
|
||||
if ($removeAll) {
|
||||
$currentPageFiles = Get-CIMInstance Win32_PageFileSetting
|
||||
if ($null -ne $currentPageFiles) {
|
||||
$currentPageFiles | Remove-CIMInstance -WhatIf:$check_mode > $null
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -ne $automatic) {
|
||||
# change autmoatic managed pagefile
|
||||
try {
|
||||
$computerSystem = Get-CIMInstance -Class win32_computersystem
|
||||
} catch {
|
||||
Fail-Json $result "Failed to query WMI computer system object $($_.Exception.Message)"
|
||||
}
|
||||
if ($computerSystem.AutomaticManagedPagefile -ne $automatic) {
|
||||
if (-not $check_mode) {
|
||||
try {
|
||||
$computerSystem | Set-CimInstance -Property @{automaticmanagedpagefile="$automatic"} > $null
|
||||
} catch {
|
||||
Fail-Json $result "Failed to set AutomaticManagedPagefile $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($state -eq "absent") {
|
||||
# Remove pagefile
|
||||
if ($null -ne (Get-Pagefile $fullPath))
|
||||
{
|
||||
try {
|
||||
Remove-Pagefile $fullPath -whatif:$check_mode
|
||||
} catch {
|
||||
Fail-Json $result "Failed to remove pagefile $($_.Exception.Message)"
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
} elseif ($state -eq "present") {
|
||||
# Remove current pagefile
|
||||
if ($override) {
|
||||
if ($null -ne (Get-Pagefile $fullPath))
|
||||
{
|
||||
try {
|
||||
Remove-Pagefile $fullPath -whatif:$check_mode
|
||||
} catch {
|
||||
Fail-Json $result "Failed to remove current pagefile $($_.Exception.Message)"
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Make sure drive is accessible
|
||||
if (($test_path) -and (-not (Test-Path "${drive}:"))) {
|
||||
Fail-Json $result "Unable to access '${drive}:' drive"
|
||||
}
|
||||
|
||||
$curPagefile = Get-Pagefile $fullPath
|
||||
|
||||
# Set pagefile
|
||||
if ($null -eq $curPagefile) {
|
||||
try {
|
||||
$pagefile = New-CIMInstance -Class Win32_PageFileSetting -Arguments @{name = $fullPath;} -WhatIf:$check_mode
|
||||
} catch {
|
||||
Fail-Json $result "Failed to create pagefile $($_.Exception.Message)"
|
||||
}
|
||||
if (-not ($systemManaged -or $check_mode)) {
|
||||
try {
|
||||
$pagefile | Set-CimInstance -Property @{ InitialSize = $initialSize; MaximumSize = $maximumSize}
|
||||
} catch {
|
||||
$originalExceptionMessage = $($_.Exception.Message)
|
||||
# Try workaround before failing
|
||||
try {
|
||||
Remove-Pagefile $fullPath -whatif:$check_mode
|
||||
} catch {
|
||||
Fail-Json $result "Failed to remove pagefile before workaround $($_.Exception.Message) Original exception: $originalExceptionMessage"
|
||||
}
|
||||
try {
|
||||
$pagingFilesValues = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management").PagingFiles
|
||||
} catch {
|
||||
Fail-Json $result "Failed to get pagefile settings from the registry for workaround $($_.Exception.Message) Original exception: $originalExceptionMessage"
|
||||
}
|
||||
$pagingFilesValues += "$fullPath $initialSize $maximumSize"
|
||||
try {
|
||||
Set-ItemProperty -LiteralPath "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" "PagingFiles" $pagingFilesValues
|
||||
} catch {
|
||||
Fail-Json $result "Failed to set pagefile settings to the registry for workaround $($_.Exception.Message) Original exception: $originalExceptionMessage"
|
||||
}
|
||||
}
|
||||
}
|
||||
$result.changed = $true
|
||||
}else
|
||||
{
|
||||
$CurPageFileSystemManaged = (Get-CimInstance -ClassName win32_Pagefile -Property 'System' -Filter "name='$($fullPath.Replace('\','\\'))'").System
|
||||
if ((-not $check_mode) -and
|
||||
-not ($systemManaged -or $CurPageFileSystemManaged) -and
|
||||
( ($curPagefile.InitialSize -ne $initialSize) -or
|
||||
($curPagefile.maximumSize -ne $maximumSize)))
|
||||
{
|
||||
$curPagefile.InitialSize = $initialSize
|
||||
$curPagefile.MaximumSize = $maximumSize
|
||||
try {
|
||||
$curPagefile.Put() | out-null
|
||||
} catch {
|
||||
$originalExceptionMessage = $($_.Exception.Message)
|
||||
# Try workaround before failing
|
||||
try {
|
||||
Remove-Pagefile $fullPath -whatif:$check_mode
|
||||
} catch {
|
||||
Fail-Json $result "Failed to remove pagefile before workaround $($_.Exception.Message) Original exception: $originalExceptionMessage"
|
||||
}
|
||||
try {
|
||||
$pagingFilesValues = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management").PagingFiles
|
||||
} catch {
|
||||
Fail-Json $result "Failed to get pagefile settings from the registry for workaround $($_.Exception.Message) Original exception: $originalExceptionMessage"
|
||||
}
|
||||
$pagingFilesValues += "$fullPath $initialSize $maximumSize"
|
||||
try {
|
||||
Set-ItemProperty -LiteralPath "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" -Name "PagingFiles" -Value $pagingFilesValues
|
||||
} catch {
|
||||
Fail-Json $result "Failed to set pagefile settings to the registry for workaround $($_.Exception.Message) Original exception: $originalExceptionMessage"
|
||||
}
|
||||
}
|
||||
$result.changed = $true
|
||||
}
|
||||
}
|
||||
} elseif ($state -eq "query") {
|
||||
$result.pagefiles = @()
|
||||
|
||||
if ($null -eq $drive) {
|
||||
try {
|
||||
$pagefiles = Get-CIMInstance Win32_PageFileSetting
|
||||
} catch {
|
||||
Fail-Json $result "Failed to query all pagefiles $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$pagefiles = Get-Pagefile $fullPath
|
||||
} catch {
|
||||
Fail-Json $result "Failed to query specific pagefile $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# Get all pagefiles
|
||||
foreach ($currentPagefile in $pagefiles) {
|
||||
$currentPagefileObject = @{
|
||||
name = $currentPagefile.Name
|
||||
initial_size = $currentPagefile.InitialSize
|
||||
maximum_size = $currentPagefile.MaximumSize
|
||||
caption = $currentPagefile.Caption
|
||||
description = $currentPagefile.Description
|
||||
}
|
||||
$result.pagefiles += ,$currentPagefileObject
|
||||
}
|
||||
|
||||
# Get automatic managed pagefile state
|
||||
try {
|
||||
$result.automatic_managed_pagefiles = (Get-CIMInstance -Class win32_computersystem).AutomaticManagedPagefile
|
||||
} catch {
|
||||
Fail-Json $result "Failed to query automatic managed pagefile state $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
Exit-Json $result
|
@ -1,139 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Liran Nisanov <lirannis@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# this is a windows documentation stub. actual code lives in the .ps1
|
||||
# file of the same name
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_pagefile
|
||||
version_added: "2.4"
|
||||
short_description: Query or change pagefile configuration
|
||||
description:
|
||||
- Query current pagefile configuration.
|
||||
- Enable/Disable AutomaticManagedPagefile.
|
||||
- Create new or override pagefile configuration.
|
||||
options:
|
||||
drive:
|
||||
description:
|
||||
- The drive of the pagefile.
|
||||
type: str
|
||||
initial_size:
|
||||
description:
|
||||
- The initial size of the pagefile in megabytes.
|
||||
type: int
|
||||
maximum_size:
|
||||
description:
|
||||
- The maximum size of the pagefile in megabytes.
|
||||
type: int
|
||||
override:
|
||||
description:
|
||||
- Override the current pagefile on the drive.
|
||||
type: bool
|
||||
default: yes
|
||||
system_managed:
|
||||
description:
|
||||
- Configures current pagefile to be managed by the system.
|
||||
type: bool
|
||||
default: no
|
||||
automatic:
|
||||
description:
|
||||
- Configures AutomaticManagedPagefile for the entire system.
|
||||
type: bool
|
||||
remove_all:
|
||||
description:
|
||||
- Remove all pagefiles in the system, not including automatic managed.
|
||||
type: bool
|
||||
default: no
|
||||
test_path:
|
||||
description:
|
||||
- Use Test-Path on the drive to make sure the drive is accessible before creating the pagefile.
|
||||
type: bool
|
||||
default: yes
|
||||
state:
|
||||
description:
|
||||
- State of the pagefile.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: query
|
||||
notes:
|
||||
- There is difference between automatic managed pagefiles that configured once for the entire system and system managed pagefile that configured per pagefile.
|
||||
- InitialSize 0 and MaximumSize 0 means the pagefile is managed by the system.
|
||||
- Value out of range exception may be caused by several different issues, two common problems - No such drive, Pagefile size is too small.
|
||||
- Setting a pagefile when AutomaticManagedPagefile is on will disable the AutomaticManagedPagefile.
|
||||
author:
|
||||
- Liran Nisanov (@LiranNis)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Query pagefiles configuration
|
||||
win_pagefile:
|
||||
|
||||
- name: Query C pagefile
|
||||
win_pagefile:
|
||||
drive: C
|
||||
|
||||
- name: Set C pagefile, don't override if exists
|
||||
win_pagefile:
|
||||
drive: C
|
||||
initial_size: 1024
|
||||
maximum_size: 1024
|
||||
override: no
|
||||
state: present
|
||||
|
||||
- name: Set C pagefile, override if exists
|
||||
win_pagefile:
|
||||
drive: C
|
||||
initial_size: 1024
|
||||
maximum_size: 1024
|
||||
state: present
|
||||
|
||||
- name: Remove C pagefile
|
||||
win_pagefile:
|
||||
drive: C
|
||||
state: absent
|
||||
|
||||
- name: Remove all current pagefiles, enable AutomaticManagedPagefile and query at the end
|
||||
win_pagefile:
|
||||
remove_all: yes
|
||||
automatic: yes
|
||||
|
||||
- name: Remove all pagefiles disable AutomaticManagedPagefile and set C pagefile
|
||||
win_pagefile:
|
||||
drive: C
|
||||
initial_size: 2048
|
||||
maximum_size: 2048
|
||||
remove_all: yes
|
||||
automatic: no
|
||||
state: present
|
||||
|
||||
- name: Set D pagefile, override if exists
|
||||
win_pagefile:
|
||||
drive: d
|
||||
initial_size: 1024
|
||||
maximum_size: 1024
|
||||
state: present
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
automatic_managed_pagefiles:
|
||||
description: Whether the pagefiles is automatically managed.
|
||||
returned: When state is query.
|
||||
type: bool
|
||||
sample: true
|
||||
pagefiles:
|
||||
description: Contains caption, description, initial_size, maximum_size and name for each pagefile in the system.
|
||||
returned: When state is query.
|
||||
type: list
|
||||
sample:
|
||||
[{"caption": "c:\\ 'pagefile.sys'", "description": "'pagefile.sys' @ c:\\", "initial_size": 2048, "maximum_size": 2048, "name": "c:\\pagefile.sys"},
|
||||
{"caption": "d:\\ 'pagefile.sys'", "description": "'pagefile.sys' @ d:\\", "initial_size": 1024, "maximum_size": 1024, "name": "d:\\pagefile.sys"}]
|
||||
|
||||
'''
|
@ -1,326 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2018, Varun Chopra (@chopraaa) <v@chopraaa.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#AnsibleRequires -OSVersion 6.2
|
||||
|
||||
Set-StrictMode -Version 2
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
state = @{ type = "str"; choices = "absent", "present"; default = "present" }
|
||||
drive_letter = @{ type = "str" }
|
||||
disk_number = @{ type = "int" }
|
||||
partition_number = @{ type = "int" }
|
||||
partition_size = @{ type = "str" }
|
||||
read_only = @{ type = "bool" }
|
||||
active = @{ type = "bool" }
|
||||
hidden = @{ type = "bool" }
|
||||
offline = @{ type = "bool" }
|
||||
mbr_type = @{ type = "str"; choices = "fat12", "fat16", "extended", "huge", "ifs", "fat32" }
|
||||
gpt_type = @{ type = "str"; choices = "system_partition", "microsoft_reserved", "basic_data", "microsoft_recovery" }
|
||||
}
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$state = $module.Params.state
|
||||
$drive_letter = $module.Params.drive_letter
|
||||
$disk_number = $module.Params.disk_number
|
||||
$partition_number = $module.Params.partition_number
|
||||
$partition_size = $module.Params.partition_size
|
||||
$read_only = $module.Params.read_only
|
||||
$active = $module.Params.active
|
||||
$hidden = $module.Params.hidden
|
||||
$offline = $module.Params.offline
|
||||
$mbr_type = $module.Params.mbr_type
|
||||
$gpt_type = $module.Params.gpt_type
|
||||
|
||||
$size_is_maximum = $false
|
||||
$ansible_partition = $false
|
||||
$ansible_partition_size = $null
|
||||
$partition_style = $null
|
||||
|
||||
$gpt_styles = @{
|
||||
system_partition = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"
|
||||
microsoft_reserved = "e3c9e316-0b5c-4db8-817d-f92df00215ae"
|
||||
basic_data = "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7"
|
||||
microsoft_recovery = "de94bba4-06d1-4d40-a16a-bfd50179d6ac"
|
||||
}
|
||||
|
||||
$mbr_styles = @{
|
||||
fat12 = 1
|
||||
fat16 = 4
|
||||
extended = 5
|
||||
huge = 6
|
||||
ifs = 7
|
||||
fat32 = 12
|
||||
}
|
||||
|
||||
function Convert-SizeToBytes {
|
||||
param(
|
||||
$Size,
|
||||
$Units
|
||||
)
|
||||
|
||||
switch ($Units) {
|
||||
"B" { return $Size }
|
||||
"KB" { return 1000 * $Size }
|
||||
"KiB" { return 1024 * $Size }
|
||||
"MB" { return [Math]::Pow(1000, 2) * $Size }
|
||||
"MiB" { return [Math]::Pow(1024, 2) * $Size }
|
||||
"GB" { return [Math]::Pow(1000, 3) * $Size }
|
||||
"GiB" { return [Math]::Pow(1024, 3) * $Size }
|
||||
"TB" { return [Math]::Pow(1000, 4) * $Size }
|
||||
"TiB" { return [Math]::Pow(1024, 4) * $Size }
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -ne $partition_size) {
|
||||
if ($partition_size -eq -1) {
|
||||
$size_is_maximum = $true
|
||||
}
|
||||
elseif ($partition_size -match '^(?<Size>[0-9]+)[ ]*(?<Units>b|kb|kib|mb|mib|gb|gib|tb|tib)$') {
|
||||
$ansible_partition_size = Convert-SizeToBytes -Size $Matches.Size -Units $Matches.Units
|
||||
}
|
||||
else {
|
||||
$module.FailJson("Invalid partition size. B, KB, KiB, MB, MiB, GB, GiB, TB, TiB are valid partition size units")
|
||||
}
|
||||
}
|
||||
|
||||
# If partition_exists, we can change or delete it; otherwise we only need the disk to create a new partition
|
||||
if ($null -ne $disk_number -and $null -ne $partition_number) {
|
||||
$ansible_partition = Get-Partition -DiskNumber $disk_number -PartitionNumber $partition_number -ErrorAction SilentlyContinue
|
||||
}
|
||||
# Check if drive_letter is either auto-assigned or a character from A-Z
|
||||
elseif ($drive_letter -and -not ($disk_number -and $partition_number)) {
|
||||
if ($drive_letter -eq "auto" -or $drive_letter -match "^[a-zA-Z]$") {
|
||||
$ansible_partition = Get-Partition -DriveLetter $drive_letter -ErrorAction SilentlyContinue
|
||||
}
|
||||
else {
|
||||
$module.FailJson("Incorrect usage of drive_letter: specify a drive letter from A-Z or use 'auto' to automatically assign a drive letter")
|
||||
}
|
||||
}
|
||||
elseif ($disk_number) {
|
||||
try {
|
||||
Get-Disk -Number $disk_number | Out-Null
|
||||
} catch {
|
||||
$module.FailJson("Specified disk does not exist")
|
||||
}
|
||||
}
|
||||
else {
|
||||
$module.FailJson("You must provide disk_number, partition_number")
|
||||
}
|
||||
|
||||
# Partition can't have two partition styles
|
||||
if ($null -ne $gpt_type -and $null -ne $mbr_type) {
|
||||
$module.FailJson("Cannot specify both GPT and MBR partition styles. Check which partition style is supported by the disk")
|
||||
}
|
||||
|
||||
function New-AnsiblePartition {
|
||||
param(
|
||||
$DiskNumber,
|
||||
$Letter,
|
||||
$SizeMax,
|
||||
$Size,
|
||||
$MbrType,
|
||||
$GptType,
|
||||
$Style
|
||||
)
|
||||
|
||||
$parameters = @{
|
||||
DiskNumber = $DiskNumber
|
||||
}
|
||||
|
||||
if ($null -ne $Letter) {
|
||||
switch ($Letter) {
|
||||
"auto" {
|
||||
$parameters.Add("AssignDriveLetter", $True)
|
||||
}
|
||||
default {
|
||||
$parameters.Add("DriveLetter", $Letter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -ne $Size) {
|
||||
$parameters.Add("Size", $Size)
|
||||
}
|
||||
|
||||
if ($null -ne $MbrType) {
|
||||
$parameters.Add("MbrType", $Style)
|
||||
}
|
||||
|
||||
if ($null -ne $GptType) {
|
||||
$parameters.Add("GptType", $Style)
|
||||
}
|
||||
|
||||
try {
|
||||
$new_partition = New-Partition @parameters
|
||||
} catch {
|
||||
$module.FailJson("Unable to create a new partition: $($_.Exception.Message)", $_)
|
||||
}
|
||||
|
||||
return $new_partition
|
||||
}
|
||||
|
||||
|
||||
function Set-AnsiblePartitionState {
|
||||
param(
|
||||
$hidden,
|
||||
$read_only,
|
||||
$active,
|
||||
$partition
|
||||
)
|
||||
|
||||
$parameters = @{
|
||||
DiskNumber = $partition.DiskNumber
|
||||
PartitionNumber = $partition.PartitionNumber
|
||||
}
|
||||
|
||||
if ($hidden -NotIn ($null, $partition.IsHidden)) {
|
||||
$parameters.Add("IsHidden", $hidden)
|
||||
}
|
||||
|
||||
if ($read_only -NotIn ($null, $partition.IsReadOnly)) {
|
||||
$parameters.Add("IsReadOnly", $read_only)
|
||||
}
|
||||
|
||||
if ($active -NotIn ($null, $partition.IsActive)) {
|
||||
$parameters.Add("IsActive", $active)
|
||||
}
|
||||
|
||||
try {
|
||||
Set-Partition @parameters
|
||||
} catch {
|
||||
$module.FailJson("Error changing state of partition: $($_.Exception.Message)", $_)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($ansible_partition) {
|
||||
if ($state -eq "absent") {
|
||||
try {
|
||||
Remove-Partition -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber -Confirm:$false -WhatIf:$module.CheckMode
|
||||
} catch {
|
||||
$module.FailJson("There was an error removing the partition: $($_.Exception.Message)", $_)
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
else {
|
||||
|
||||
if ($null -ne $gpt_type -and $gpt_styles.$gpt_type -ne $ansible_partition.GptType) {
|
||||
$module.FailJson("gpt_type is not a valid parameter for existing partitions")
|
||||
}
|
||||
if ($null -ne $mbr_type -and $mbr_styles.$mbr_type -ne $ansible_partition.MbrType) {
|
||||
$module.FailJson("mbr_type is not a valid parameter for existing partitions")
|
||||
}
|
||||
|
||||
if ($partition_size) {
|
||||
try {
|
||||
$max_supported_size = (Get-PartitionSupportedSize -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber).SizeMax
|
||||
} catch {
|
||||
$module.FailJson("Unable to get maximum supported partition size: $($_.Exception.Message)", $_)
|
||||
}
|
||||
if ($size_is_maximum) {
|
||||
$ansible_partition_size = $max_supported_size
|
||||
}
|
||||
if ($ansible_partition_size -ne $ansible_partition.Size -and ($ansible_partition_size - $ansible_partition.Size -gt 1049000 -or $ansible_partition.Size - $ansible_partition_size -gt 1049000)) {
|
||||
if ($ansible_partition.IsReadOnly) {
|
||||
$module.FailJson("Unable to resize partition: Partition is read only")
|
||||
} else {
|
||||
try {
|
||||
Resize-Partition -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber -Size $ansible_partition_size -WhatIf:$module.CheckMode
|
||||
} catch {
|
||||
$module.FailJson("Unable to change partition size: $($_.Exception.Message)", $_)
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
} elseif ($ansible_partition_size -gt $max_supported_size) {
|
||||
$module.FailJson("Specified partition size exceeds size supported by the partition")
|
||||
}
|
||||
}
|
||||
|
||||
if ($drive_letter -NotIn ("auto", $null, $ansible_partition.DriveLetter)) {
|
||||
if (-not $module.CheckMode) {
|
||||
try {
|
||||
Set-Partition -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber -NewDriveLetter $drive_letter
|
||||
} catch {
|
||||
$module.FailJson("Unable to change drive letter: $($_.Exception.Message)", $_)
|
||||
}
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($state -eq "present") {
|
||||
if ($null -eq $disk_number) {
|
||||
$module.FailJson("Missing required parameter: disk_number")
|
||||
}
|
||||
if ($null -eq $ansible_partition_size -and -not $size_is_maximum){
|
||||
$module.FailJson("Missing required parameter: partition_size")
|
||||
}
|
||||
if (-not $size_is_maximum) {
|
||||
try {
|
||||
$max_supported_size = (Get-Disk -Number $disk_number).LargestFreeExtent
|
||||
} catch {
|
||||
$module.FailJson("Unable to get maximum size supported by disk: $($_.Exception.Message)", $_)
|
||||
}
|
||||
|
||||
if ($ansible_partition_size -gt $max_supported_size) {
|
||||
$module.FailJson("Partition size is not supported by disk. Use partition_size: -1 to get maximum size")
|
||||
}
|
||||
} else {
|
||||
$ansible_partition_size = (Get-Disk -Number $disk_number).LargestFreeExtent
|
||||
}
|
||||
|
||||
$supp_part_type = (Get-Disk -Number $disk_number).PartitionStyle
|
||||
if ($null -ne $mbr_type) {
|
||||
if ($supp_part_type -eq "MBR" -and $mbr_styles.ContainsKey($mbr_type)) {
|
||||
$partition_style = $mbr_styles.$mbr_type
|
||||
} else {
|
||||
$module.FailJson("Incorrect partition style specified")
|
||||
}
|
||||
}
|
||||
if ($null -ne $gpt_type) {
|
||||
if ($supp_part_type -eq "GPT" -and $gpt_styles.ContainsKey($gpt_type)) {
|
||||
$partition_style = $gpt_styles.$gpt_type
|
||||
} else {
|
||||
$module.FailJson("Incorrect partition style specified")
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $module.CheckMode) {
|
||||
$ansible_partition = New-AnsiblePartition -DiskNumber $disk_number -Letter $drive_letter -Size $ansible_partition_size -MbrType $mbr_type -GptType $gpt_type -Style $partition_style
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($state -eq "present" -and $ansible_partition) {
|
||||
if ($offline -NotIn ($null, $ansible_partition.IsOffline)) {
|
||||
if (-not $module.CheckMode) {
|
||||
try {
|
||||
Set-Partition -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber -IsOffline $offline
|
||||
} catch {
|
||||
$module.FailJson("Error setting partition offline: $($_.Exception.Message)", $_)
|
||||
}
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
|
||||
if ($hidden -NotIn ($null, $ansible_partition.IsHidden) -or $read_only -NotIn ($null, $ansible_partition.IsReadOnly) -or $active -NotIn ($null, $ansible_partition.IsActive)) {
|
||||
if (-not $module.CheckMode) {
|
||||
Set-AnsiblePartitionState -hidden $hidden -read_only $read_only -active $active -partition $ansible_partition
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
@ -1,117 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Varun Chopra (@chopraaa) <v@chopraaa.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_partition
|
||||
version_added: '2.8'
|
||||
short_description: Creates, changes and removes partitions on Windows Server
|
||||
description:
|
||||
- The M(win_partition) module can create, modify or delete a partition on a disk
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Used to specify the state of the partition. Use C(absent) to specify if a partition should be removed
|
||||
and C(present) to specify if the partition should be created or updated.
|
||||
type: str
|
||||
choices: [ absent, present]
|
||||
default: present
|
||||
drive_letter:
|
||||
description:
|
||||
- Used for accessing partitions if I(disk_number) and I(partition_number) are not provided.
|
||||
- Use C(auto) for automatically assigning a drive letter, or a letter A-Z for manually assigning a drive letter to a new partition.
|
||||
If not specified, no drive letter is assigned when creating a new partition.
|
||||
type: str
|
||||
disk_number:
|
||||
description:
|
||||
- Disk number is mandatory for creating new partitions.
|
||||
- A combination of I(disk_number) and I(partition_number) can be used to specify the partition instead of I(drive_letter) if required.
|
||||
type: int
|
||||
partition_number:
|
||||
description:
|
||||
- Used in conjunction with I(disk_number) to uniquely identify a partition.
|
||||
type: int
|
||||
partition_size:
|
||||
description:
|
||||
- Specify size of the partition in B, KB, KiB, MB, MiB, GB, GiB, TB or TiB. Use -1 to specify maximum supported size.
|
||||
- Partition size is mandatory for creating a new partition but not for updating or deleting a partition.
|
||||
- The decimal SI prefixes kilo, mega, giga, tera, etc., are powers of 10^3 = 1000. The binary prefixes kibi, mebi, gibi, tebi, etc.
|
||||
respectively refer to the corresponding power of 2^10 = 1024.
|
||||
Thus, a gigabyte (GB) is 1000000000 (1000^3) bytes while 1 gibibyte (GiB) is 1073741824 (1024^3) bytes.
|
||||
type: str
|
||||
read_only:
|
||||
description:
|
||||
- Make the partition read only, restricting changes from being made to the partition.
|
||||
type: bool
|
||||
active:
|
||||
description:
|
||||
- Specifies if the partition is active and can be used to start the system. This property is only valid when the disk's partition style is MBR.
|
||||
type: bool
|
||||
hidden:
|
||||
description:
|
||||
- Hides the target partition, making it undetectable by the mount manager.
|
||||
type: bool
|
||||
offline:
|
||||
description:
|
||||
- Sets the partition offline.
|
||||
- Adding a mount point (such as a drive letter) will cause the partition to go online again.
|
||||
type: bool
|
||||
required: no
|
||||
mbr_type:
|
||||
description:
|
||||
- Specify the partition's MBR type if the disk's partition style is MBR.
|
||||
- This only applies to new partitions.
|
||||
- This does not relate to the partitions file system formatting.
|
||||
type: str
|
||||
choices: [ fat12, fat16, extended, huge, ifs, fat32 ]
|
||||
gpt_type:
|
||||
description:
|
||||
- Specify the partition's GPT type if the disk's partition style is GPT.
|
||||
- This only applies to new partitions.
|
||||
- This does not relate to the partitions file system formatting.
|
||||
type: str
|
||||
choices: [ system_partition, microsoft_reserved, basic_data, microsoft_recovery ]
|
||||
|
||||
notes:
|
||||
- A minimum Operating System Version of 6.2 is required to use this module. To check if your OS is compatible, see
|
||||
U(https://docs.microsoft.com/en-us/windows/desktop/sysinfo/operating-system-version).
|
||||
- This module cannot be used for removing the drive letter associated with a partition, initializing a disk or, file system formatting.
|
||||
- Idempotence works only if you're specifying a drive letter or other unique attributes such as a combination of disk number and partition number.
|
||||
- For more information, see U(https://msdn.microsoft.com/en-us/library/windows/desktop/hh830524.aspx).
|
||||
author:
|
||||
- Varun Chopra (@chopraaa) <v@chopraaa.com>
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create a partition with drive letter D and size 5 GiB
|
||||
win_partition:
|
||||
drive_letter: D
|
||||
partition_size: 5 GiB
|
||||
disk_number: 1
|
||||
|
||||
- name: Resize previously created partition to it's maximum size and change it's drive letter to E
|
||||
win_partition:
|
||||
drive_letter: E
|
||||
partition_size: -1
|
||||
partition_number: 1
|
||||
disk_number: 1
|
||||
|
||||
- name: Delete partition
|
||||
win_partition:
|
||||
disk_number: 1
|
||||
partition_number: 1
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
#
|
||||
'''
|
@ -1,116 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Erwan Quelin (@equelin) <erwan.quelin@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
output_file = @{ type = "str" }
|
||||
output_format = @{ type = "str"; default = "NunitXML" }
|
||||
path = @{ type = "str"; required = $true }
|
||||
tags = @{ type = "list"; elements = "str" }
|
||||
test_parameters = @{ type = "dict" }
|
||||
version = @{ type = "str"; aliases = @(,"minimum_version") }
|
||||
}
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$output_file = $module.Params.output_file
|
||||
$output_format = $module.Params.output_format
|
||||
$path = $module.Params.path
|
||||
$tags = $module.Params.tags
|
||||
$test_parameters = $module.Params.test_parameters
|
||||
$version = $module.Params.version
|
||||
|
||||
Try {
|
||||
$version = [version]$version
|
||||
}
|
||||
Catch {
|
||||
$module.FailJson("Value '$version' for parameter 'minimum_version' is not a valid version format")
|
||||
}
|
||||
|
||||
# Make sure path is a real path
|
||||
Try {
|
||||
$path = $path.TrimEnd("\")
|
||||
$path = (Get-item -LiteralPath $path).FullName
|
||||
}
|
||||
Catch {
|
||||
$module.FailJson("Cannot find file or directory: '$path' as it does not exist")
|
||||
}
|
||||
|
||||
# Import Pester module if available
|
||||
$Pester = 'Pester'
|
||||
|
||||
If (-not (Get-Module -Name $Pester -ErrorAction SilentlyContinue)) {
|
||||
If (Get-Module -Name $Pester -ListAvailable -ErrorAction SilentlyContinue) {
|
||||
Import-Module $Pester
|
||||
} else {
|
||||
$module.FailJson("Cannot find module: $Pester. Check if pester is installed, and if it is not, install using win_psmodule or win_chocolatey.")
|
||||
}
|
||||
}
|
||||
|
||||
# Add actual pester's module version in the ansible's result variable
|
||||
$Pester_version = (Get-Module -Name $Pester).Version.ToString()
|
||||
$module.Result.pester_version = $Pester_version
|
||||
|
||||
# Test if the Pester module is available with a version greater or equal than the one specified in the $version parameter
|
||||
If ((-not (Get-Module -Name $Pester -ErrorAction SilentlyContinue | Where-Object {$_.Version -ge $version})) -and ($version)) {
|
||||
$module.FailJson("$Pester version is not greater or equal to $version")
|
||||
}
|
||||
|
||||
#Prepare Invoke-Pester parameters depending of the Pester's version.
|
||||
#Invoke-Pester output deactivation behave differently depending on the Pester's version
|
||||
If ($module.Result.pester_version -ge "4.0.0") {
|
||||
$Parameters = @{
|
||||
"show" = "none"
|
||||
"PassThru" = $True
|
||||
}
|
||||
} else {
|
||||
$Parameters = @{
|
||||
"quiet" = $True
|
||||
"PassThru" = $True
|
||||
}
|
||||
}
|
||||
|
||||
if($tags.count){
|
||||
$Parameters.Tag = $tags
|
||||
}
|
||||
|
||||
if($output_file){
|
||||
$Parameters.OutputFile = $output_file
|
||||
$Parameters.OutputFormat = $output_format
|
||||
}
|
||||
|
||||
# Run Pester tests
|
||||
If (Test-Path -LiteralPath $path -PathType Leaf) {
|
||||
$test_parameters_check_mode_msg = ''
|
||||
if ($test_parameters.keys.count) {
|
||||
$Parameters.Script = @{Path = $Path ; Parameters = $test_parameters }
|
||||
$test_parameters_check_mode_msg = " with $($test_parameters.keys -join ',') parameters"
|
||||
}
|
||||
else {
|
||||
$Parameters.Script = $Path
|
||||
}
|
||||
|
||||
if ($module.CheckMode) {
|
||||
$module.Result.output = "Run pester test in the file: $path$test_parameters_check_mode_msg"
|
||||
} else {
|
||||
$module.Result.output = Invoke-Pester @Parameters
|
||||
}
|
||||
} else {
|
||||
$Parameters.Script = $path
|
||||
|
||||
if ($module.CheckMode) {
|
||||
$module.Result.output = "Run Pester test(s): $path"
|
||||
} else {
|
||||
$module.Result.output = Invoke-Pester @Parameters
|
||||
}
|
||||
}
|
||||
|
||||
$module.Result.changed = $true
|
||||
|
||||
$module.ExitJson()
|
@ -1,113 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_pester
|
||||
short_description: Run Pester tests on Windows hosts
|
||||
version_added: "2.6"
|
||||
description:
|
||||
- Run Pester tests on Windows hosts.
|
||||
- Test files have to be available on the remote host.
|
||||
requirements:
|
||||
- Pester
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- Path to a pester test file or a folder where tests can be found.
|
||||
- If the path is a folder, the module will consider all ps1 files as Pester tests.
|
||||
type: str
|
||||
required: true
|
||||
tags:
|
||||
description:
|
||||
- Runs only tests in Describe blocks with specified Tags values.
|
||||
- Accepts multiple comma separated tags.
|
||||
type: list
|
||||
version_added: '2.9'
|
||||
output_file:
|
||||
description:
|
||||
- Generates an output test report.
|
||||
type: str
|
||||
version_added: '2.10'
|
||||
output_format:
|
||||
description:
|
||||
- Format of the test report to be generated.
|
||||
- This parameter is to be used with output_file option.
|
||||
type: str
|
||||
default: NunitXML
|
||||
version_added: '2.10'
|
||||
test_parameters:
|
||||
description:
|
||||
- Allows to specify parameters to the test script.
|
||||
type: dict
|
||||
version_added: '2.9'
|
||||
version:
|
||||
description:
|
||||
- Minimum version of the pester module that has to be available on the remote host.
|
||||
type: str
|
||||
aliases:
|
||||
- minimum_version
|
||||
author:
|
||||
- Erwan Quelin (@equelin)
|
||||
- Prasoon Karunan V (@prasoonkarunan)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Get facts
|
||||
setup:
|
||||
|
||||
- name: Add Pester module
|
||||
action:
|
||||
module_name: "{{ 'win_psmodule' if ansible_powershell_version >= 5 else 'win_chocolatey' }}"
|
||||
name: Pester
|
||||
state: present
|
||||
|
||||
- name: Run the pester test provided in the path parameter.
|
||||
win_pester:
|
||||
path: C:\Pester
|
||||
|
||||
- name: Run the pester tests only for the tags specified.
|
||||
win_pester:
|
||||
path: C:\Pester\TestScript.tests
|
||||
tags: CI,UnitTests
|
||||
|
||||
# Run pesters tests files that are present in the specified folder
|
||||
# ensure that the pester module version available is greater or equal to the version parameter.
|
||||
- name: Run the pester test present in a folder and check the Pester module version.
|
||||
win_pester:
|
||||
path: C:\Pester\test01.test.ps1
|
||||
version: 4.1.0
|
||||
|
||||
- name: Run the pester test present in a folder with given script parameters.
|
||||
win_pester:
|
||||
path: C:\Pester\test04.test.ps1
|
||||
test_parameters:
|
||||
Process: lsass
|
||||
Service: bits
|
||||
|
||||
- name: Run the pester test present in a folder and generate NunitXML test result..
|
||||
win_pester:
|
||||
path: C:\Pester\test04.test.ps1
|
||||
output_file: c:\Pester\resullt\testresult.xml
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
pester_version:
|
||||
description: Version of the pester module found on the remote host.
|
||||
returned: always
|
||||
type: str
|
||||
sample: 4.3.1
|
||||
output:
|
||||
description: Results of the Pester tests.
|
||||
returned: success
|
||||
type: list
|
||||
sample: false
|
||||
'''
|
@ -1,207 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#Requires -Module Ansible.ModuleUtils.AddType
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
name = @{ type = "str"; required = $true }
|
||||
}
|
||||
supports_check_mode = $true
|
||||
}
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$name = $module.Params.name
|
||||
|
||||
$module.Result.power_plan_name = $name
|
||||
$module.Result.power_plan_enabled = $null
|
||||
$module.Result.all_available_plans = $null
|
||||
|
||||
Add-CSharpType -References @"
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ansible.WinPowerPlan
|
||||
{
|
||||
public enum AccessFlags : uint
|
||||
{
|
||||
AccessScheme = 16,
|
||||
AccessSubgroup = 17,
|
||||
AccessIndividualSetting = 18
|
||||
}
|
||||
|
||||
public class NativeMethods
|
||||
{
|
||||
[DllImport("Kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr LocalFree(
|
||||
IntPtr hMen);
|
||||
|
||||
[DllImport("PowrProf.dll")]
|
||||
public static extern UInt32 PowerEnumerate(
|
||||
IntPtr RootPowerKey,
|
||||
IntPtr SchemeGuid,
|
||||
IntPtr SubGroupOfPowerSettingsGuid,
|
||||
AccessFlags AccessFlags,
|
||||
UInt32 Index,
|
||||
IntPtr Buffer,
|
||||
ref UInt32 BufferSize);
|
||||
|
||||
[DllImport("PowrProf.dll")]
|
||||
public static extern UInt32 PowerGetActiveScheme(
|
||||
IntPtr UserRootPowerKey,
|
||||
out IntPtr ActivePolicyGuid);
|
||||
|
||||
[DllImport("PowrProf.dll")]
|
||||
public static extern UInt32 PowerReadFriendlyName(
|
||||
IntPtr RootPowerKey,
|
||||
Guid SchemeGuid,
|
||||
IntPtr SubGroupOfPowerSettingsGuid,
|
||||
IntPtr PowerSettingGuid,
|
||||
IntPtr Buffer,
|
||||
ref UInt32 BufferSize);
|
||||
|
||||
[DllImport("PowrProf.dll")]
|
||||
public static extern UInt32 PowerSetActiveScheme(
|
||||
IntPtr UserRootPowerKey,
|
||||
Guid SchemeGuid);
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
Function Get-LastWin32ErrorMessage {
|
||||
param([Int]$ErrorCode)
|
||||
$exp = New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $ErrorCode
|
||||
$error_msg = "{0} - (Win32 Error Code {1} - 0x{1:X8})" -f $exp.Message, $ErrorCode
|
||||
return $error_msg
|
||||
}
|
||||
|
||||
Function Get-PlanName {
|
||||
param([Guid]$Plan)
|
||||
|
||||
$buffer_size = 0
|
||||
$buffer = [IntPtr]::Zero
|
||||
[Ansible.WinPowerPlan.NativeMethods]::PowerReadFriendlyName([IntPtr]::Zero, $Plan, [IntPtr]::Zero, [IntPtr]::Zero,
|
||||
$buffer, [ref]$buffer_size) > $null
|
||||
|
||||
$buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($buffer_size)
|
||||
try {
|
||||
$res = [Ansible.WinPowerPlan.NativeMethods]::PowerReadFriendlyName([IntPtr]::Zero, $Plan, [IntPtr]::Zero,
|
||||
[IntPtr]::Zero, $buffer, [ref]$buffer_size)
|
||||
|
||||
if ($res -ne 0) {
|
||||
$err_msg = Get-LastWin32ErrorMessage -ErrorCode $res
|
||||
$module.FailJson("Failed to get name for power scheme $Plan - $err_msg")
|
||||
}
|
||||
|
||||
return [System.Runtime.InteropServices.Marshal]::PtrToStringUni($buffer)
|
||||
} finally {
|
||||
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($buffer)
|
||||
}
|
||||
}
|
||||
|
||||
Function Get-PowerPlans {
|
||||
$plans = @{}
|
||||
|
||||
$i = 0
|
||||
while ($true) {
|
||||
$buffer_size = 0
|
||||
$buffer = [IntPtr]::Zero
|
||||
$res = [Ansible.WinPowerPlan.NativeMethods]::PowerEnumerate([IntPtr]::Zero, [IntPtr]::Zero, [IntPtr]::Zero,
|
||||
[Ansible.WinPowerPlan.AccessFlags]::AccessScheme, $i, $buffer, [ref]$buffer_size)
|
||||
|
||||
if ($res -eq 259) {
|
||||
# 259 == ERROR_NO_MORE_ITEMS, there are no more power plans to enumerate
|
||||
break
|
||||
} elseif ($res -notin @(0, 234)) {
|
||||
# 0 == ERROR_SUCCESS and 234 == ERROR_MORE_DATA
|
||||
$err_msg = Get-LastWin32ErrorMessage -ErrorCode $res
|
||||
$module.FailJson("Failed to get buffer size on local power schemes at index $i - $err_msg")
|
||||
}
|
||||
|
||||
$buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($buffer_size)
|
||||
try {
|
||||
$res = [Ansible.WinPowerPlan.NativeMethods]::PowerEnumerate([IntPtr]::Zero, [IntPtr]::Zero, [IntPtr]::Zero,
|
||||
[Ansible.WinPowerPlan.AccessFlags]::AccessScheme, $i, $buffer, [ref]$buffer_size)
|
||||
|
||||
if ($res -eq 259) {
|
||||
# Server 2008 does not return 259 in the first call above so we do an additional check here
|
||||
break
|
||||
} elseif ($res -notin @(0, 234, 259)) {
|
||||
$err_msg = Get-LastWin32ErrorMessage -ErrorCode $res
|
||||
$module.FailJson("Failed to enumerate local power schemes at index $i - $err_msg")
|
||||
}
|
||||
$scheme_guid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($buffer, [Type][Guid])
|
||||
} finally {
|
||||
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($buffer)
|
||||
}
|
||||
$scheme_name = Get-PlanName -Plan $scheme_guid
|
||||
$plans.$scheme_name = $scheme_guid
|
||||
|
||||
$i += 1
|
||||
}
|
||||
|
||||
return $plans
|
||||
}
|
||||
|
||||
Function Get-ActivePowerPlan {
|
||||
$buffer = [IntPtr]::Zero
|
||||
$res = [Ansible.WinPowerPlan.NativeMethods]::PowerGetActiveScheme([IntPtr]::Zero, [ref]$buffer)
|
||||
if ($res -ne 0) {
|
||||
$err_msg = Get-LastWin32ErrorMessage -ErrorCode $res
|
||||
$module.FailJson("Failed to get the active power plan - $err_msg")
|
||||
}
|
||||
|
||||
try {
|
||||
$active_guid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($buffer, [Type][Guid])
|
||||
} finally {
|
||||
[Ansible.WinPowerPlan.NativeMethods]::LocalFree($buffer) > $null
|
||||
}
|
||||
|
||||
return $active_guid
|
||||
}
|
||||
|
||||
Function Set-ActivePowerPlan {
|
||||
[CmdletBinding(SupportsShouldProcess=$true)]
|
||||
param([Guid]$Plan)
|
||||
|
||||
$res = 0
|
||||
if ($PSCmdlet.ShouldProcess($Plan, "Set Power Plan")) {
|
||||
$res = [Ansible.WinPowerPlan.NativeMethods]::PowerSetActiveScheme([IntPtr]::Zero, $Plan)
|
||||
}
|
||||
|
||||
if ($res -ne 0) {
|
||||
$err_msg = Get-LastWin32ErrorMessage -ErrorCode $res
|
||||
$module.FailJson("Failed to set the active power plan to $Plan - $err_msg")
|
||||
}
|
||||
}
|
||||
|
||||
# Get all local power plans and the current active plan
|
||||
$plans = Get-PowerPlans
|
||||
$active_plan = Get-ActivePowerPlan
|
||||
$module.Result.all_available_plans = @{}
|
||||
foreach ($plan_info in $plans.GetEnumerator()) {
|
||||
$module.Result.all_available_plans.($plan_info.Key) = $plan_info.Value -eq $active_plan
|
||||
}
|
||||
|
||||
if ($name -notin $plans.Keys) {
|
||||
$module.FailJson("Defined power_plan: ($name) is not available")
|
||||
}
|
||||
$plan_guid = $plans.$name
|
||||
$is_active = $active_plan -eq $plans.$name
|
||||
$module.Result.power_plan_enabled = $is_active
|
||||
|
||||
if (-not $is_active) {
|
||||
Set-ActivePowerPlan -Plan $plan_guid -WhatIf:$module.CheckMode
|
||||
$module.Result.changed = $true
|
||||
$module.Result.power_plan_enabled = $true
|
||||
foreach ($plan_info in $plans.GetEnumerator()) {
|
||||
$is_active = $plan_info.Value -eq $plan_guid
|
||||
$module.Result.all_available_plans.($plan_info.Key) = $is_active
|
||||
}
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
||||
|
@ -1,59 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_power_plan
|
||||
short_description: Changes the power plan of a Windows system
|
||||
description:
|
||||
- This module will change the power plan of a Windows system to the defined string.
|
||||
- Windows defaults to C(balanced) which will cause CPU throttling. In some cases it can be preferable
|
||||
to change the mode to C(high performance) to increase CPU performance.
|
||||
version_added: "2.4"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- String value that indicates the desired power plan.
|
||||
- The power plan must already be present on the system.
|
||||
- Commonly there will be options for C(balanced) and C(high performance).
|
||||
type: str
|
||||
required: yes
|
||||
author:
|
||||
- Noah Sparks (@nwsparks)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Change power plan to high performance
|
||||
win_power_plan:
|
||||
name: high performance
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
power_plan_name:
|
||||
description: Value of the intended power plan.
|
||||
returned: always
|
||||
type: str
|
||||
sample: balanced
|
||||
power_plan_enabled:
|
||||
description: State of the intended power plan.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: true
|
||||
all_available_plans:
|
||||
description: The name and enabled state of all power plans.
|
||||
returned: always
|
||||
type: dict
|
||||
sample: |
|
||||
{
|
||||
"High performance": false,
|
||||
"Balanced": true,
|
||||
"Power saver": false
|
||||
}
|
||||
'''
|
@ -1,85 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
|
||||
# This modules does not accept any options
|
||||
$spec = @{
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
# First try to find the product key from ACPI
|
||||
try {
|
||||
$product_key = (Get-CimInstance -Class SoftwareLicensingService).OA3xOriginalProductKey
|
||||
} catch {
|
||||
$product_key = $null
|
||||
}
|
||||
|
||||
if (-not $product_key) {
|
||||
# Else try to get it from the registry instead
|
||||
try {
|
||||
$data = Get-ItemPropertyValue -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion" -Name DigitalProductId
|
||||
} catch {
|
||||
$data = $null
|
||||
}
|
||||
|
||||
# And for Windows 2008 R2
|
||||
if (-not $data) {
|
||||
try {
|
||||
$data = Get-ItemPropertyValue -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion" -Name DigitalProductId4
|
||||
} catch {
|
||||
$data = $null
|
||||
}
|
||||
}
|
||||
|
||||
if ($data) {
|
||||
$product_key = $null
|
||||
$hexdata = $data[52..66]
|
||||
$chardata = "B","C","D","F","G","H","J","K","M","P","Q","R","T","V","W","X","Y","2","3","4","6","7","8","9"
|
||||
|
||||
# Decode base24 binary data
|
||||
for ($i = 24; $i -ge 0; $i--) {
|
||||
$k = 0
|
||||
for ($j = 14; $j -ge 0; $j--) {
|
||||
$k = $k * 256 -bxor $hexdata[$j]
|
||||
$hexdata[$j] = [math]::truncate($k / 24)
|
||||
$k = $k % 24
|
||||
}
|
||||
$product_key = $chardata[$k] + $product_key
|
||||
if (($i % 5 -eq 0) -and ($i -ne 0)) {
|
||||
$product_key = "-" + $product_key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Retrieve license information
|
||||
$license_info = Get-CimInstance SoftwareLicensingProduct | Where-Object PartialProductKey
|
||||
|
||||
$winlicense_status = switch ($license_info.LicenseStatus) {
|
||||
0 { "Unlicensed" }
|
||||
1 { "Licensed" }
|
||||
2 { "OOBGrace" }
|
||||
3 { "OOTGrace" }
|
||||
4 { "NonGenuineGrace" }
|
||||
5 { "Notification" }
|
||||
6 { "ExtendedGrace" }
|
||||
default { $null }
|
||||
}
|
||||
|
||||
$winlicense_edition = $license_info.Name
|
||||
$winlicense_channel = $license_info.ProductKeyChannel
|
||||
|
||||
$module.Result.ansible_facts = @{
|
||||
ansible_os_product_id = (Get-CimInstance Win32_OperatingSystem).SerialNumber
|
||||
ansible_os_product_key = $product_key
|
||||
ansible_os_license_edition = $winlicense_edition
|
||||
ansible_os_license_channel = $winlicense_channel
|
||||
ansible_os_license_status = $winlicense_status
|
||||
}
|
||||
|
||||
$module.ExitJson()
|
@ -1,69 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_product_facts
|
||||
short_description: Provides Windows product and license information
|
||||
description:
|
||||
- Provides Windows product and license information.
|
||||
version_added: '2.5'
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Get product id and product key
|
||||
win_product_facts:
|
||||
|
||||
- name: Display Windows edition
|
||||
debug:
|
||||
var: ansible_os_license_edition
|
||||
|
||||
- name: Display Windows license status
|
||||
debug:
|
||||
var: ansible_os_license_status
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
ansible_facts:
|
||||
description: Dictionary containing all the detailed information about the Windows product and license.
|
||||
returned: always
|
||||
type: complex
|
||||
contains:
|
||||
ansible_os_license_channel:
|
||||
description: The Windows license channel.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Volume:MAK
|
||||
version_added: '2.8'
|
||||
ansible_os_license_edition:
|
||||
description: The Windows license edition.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Windows(R) ServerStandard edition
|
||||
version_added: '2.8'
|
||||
ansible_os_license_status:
|
||||
description: The Windows license status.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Licensed
|
||||
version_added: '2.8'
|
||||
ansible_os_product_id:
|
||||
description: The Windows product ID.
|
||||
returned: always
|
||||
type: str
|
||||
sample: 00326-10000-00000-AA698
|
||||
ansible_os_product_key:
|
||||
description: The Windows product key.
|
||||
returned: always
|
||||
type: str
|
||||
sample: T49TD-6VFBW-VV7HY-B2PXY-MY47H
|
||||
'''
|
@ -1,152 +0,0 @@
|
||||
#!powershell
|
||||
|
||||
# Copyright: (c) 2017, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#Requires -Module Ansible.ModuleUtils.ArgvParser
|
||||
#Requires -Module Ansible.ModuleUtils.CommandUtil
|
||||
|
||||
# See also: https://technet.microsoft.com/en-us/sysinternals/pxexec.aspx
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
command = @{ type='str'; required=$true }
|
||||
executable = @{ type='path'; default='psexec.exe' }
|
||||
hostnames = @{ type='list' }
|
||||
username = @{ type='str' }
|
||||
password = @{ type='str'; no_log=$true }
|
||||
chdir = @{ type='path' }
|
||||
wait = @{ type='bool'; default=$true }
|
||||
nobanner = @{ type='bool'; default=$false }
|
||||
noprofile = @{ type='bool'; default=$false }
|
||||
elevated = @{ type='bool'; default=$false }
|
||||
limited = @{ type='bool'; default=$false }
|
||||
system = @{ type='bool'; default=$false }
|
||||
interactive = @{ type='bool'; default=$false }
|
||||
session = @{ type='int' }
|
||||
priority = @{ type='str'; choices=@( 'background', 'low', 'belownormal', 'abovenormal', 'high', 'realtime' ) }
|
||||
timeout = @{ type='int' }
|
||||
}
|
||||
}
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$command = $module.Params.command
|
||||
$executable = $module.Params.executable
|
||||
$hostnames = $module.Params.hostnames
|
||||
$username = $module.Params.username
|
||||
$password = $module.Params.password
|
||||
$chdir = $module.Params.chdir
|
||||
$wait = $module.Params.wait
|
||||
$nobanner = $module.Params.nobanner
|
||||
$noprofile = $module.Params.noprofile
|
||||
$elevated = $module.Params.elevated
|
||||
$limited = $module.Params.limited
|
||||
$system = $module.Params.system
|
||||
$interactive = $module.Params.interactive
|
||||
$session = $module.Params.session
|
||||
$priority = $module.Params.Priority
|
||||
$timeout = $module.Params.timeout
|
||||
|
||||
$module.Result.changed = $true
|
||||
|
||||
If (-Not (Get-Command $executable -ErrorAction SilentlyContinue)) {
|
||||
$module.FailJson("Executable '$executable' was not found.")
|
||||
}
|
||||
|
||||
$arguments = [System.Collections.Generic.List`1[String]]@($executable)
|
||||
|
||||
If ($nobanner -eq $true) {
|
||||
$arguments.Add("-nobanner")
|
||||
}
|
||||
|
||||
# Support running on local system if no hostname is specified
|
||||
If ($hostnames) {
|
||||
$hostname_argument = ($hostnames | sort -Unique) -join ','
|
||||
$arguments.Add("\\$hostname_argument")
|
||||
}
|
||||
|
||||
# Username is optional
|
||||
If ($null -ne $username) {
|
||||
$arguments.Add("-u")
|
||||
$arguments.Add($username)
|
||||
}
|
||||
|
||||
# Password is optional
|
||||
If ($null -ne $password) {
|
||||
$arguments.Add("-p")
|
||||
$arguments.Add($password)
|
||||
}
|
||||
|
||||
If ($null -ne $chdir) {
|
||||
$arguments.Add("-w")
|
||||
$arguments.Add($chdir)
|
||||
}
|
||||
|
||||
If ($wait -eq $false) {
|
||||
$arguments.Add("-d")
|
||||
}
|
||||
|
||||
If ($noprofile -eq $true) {
|
||||
$arguments.Add("-e")
|
||||
}
|
||||
|
||||
If ($elevated -eq $true) {
|
||||
$arguments.Add("-h")
|
||||
}
|
||||
|
||||
If ($system -eq $true) {
|
||||
$arguments.Add("-s")
|
||||
}
|
||||
|
||||
If ($interactive -eq $true) {
|
||||
$arguments.Add("-i")
|
||||
If ($null -ne $session) {
|
||||
$arguments.Add($session)
|
||||
}
|
||||
}
|
||||
|
||||
If ($limited -eq $true) {
|
||||
$arguments.Add("-l")
|
||||
}
|
||||
|
||||
If ($null -ne $priority) {
|
||||
$arguments.Add("-$priority")
|
||||
}
|
||||
|
||||
If ($null -ne $timeout) {
|
||||
$arguments.Add("-n")
|
||||
$arguments.Add($timeout)
|
||||
}
|
||||
|
||||
$arguments.Add("-accepteula")
|
||||
|
||||
$argument_string = Argv-ToString -arguments $arguments
|
||||
|
||||
# Add the command at the end of the argument string, we don't want to escape
|
||||
# that as psexec doesn't expect it to be one arg
|
||||
$argument_string += " $command"
|
||||
|
||||
$start_datetime = [DateTime]::UtcNow
|
||||
$module.Result.psexec_command = $argument_string
|
||||
|
||||
$command_result = Run-Command -command $argument_string
|
||||
|
||||
$end_datetime = [DateTime]::UtcNow
|
||||
|
||||
$module.Result.stdout = $command_result.stdout
|
||||
$module.Result.stderr = $command_result.stderr
|
||||
|
||||
If ($wait -eq $true) {
|
||||
$module.Result.rc = $command_result.rc
|
||||
} else {
|
||||
$module.Result.rc = 0
|
||||
$module.Result.pid = $command_result.rc
|
||||
}
|
||||
|
||||
$module.Result.start = $start_datetime.ToString("yyyy-MM-dd hh:mm:ss.ffffff")
|
||||
$module.Result.end = $end_datetime.ToString("yyyy-MM-dd hh:mm:ss.ffffff")
|
||||
$module.Result.delta = $($end_datetime - $start_datetime).ToString("h\:mm\:ss\.ffffff")
|
||||
|
||||
$module.ExitJson()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue