summaryrefslogtreecommitdiffstats
path: root/ansible_collections/microsoft/ad/docs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/microsoft/ad/docs
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/microsoft/ad/docs')
-rw-r--r--ansible_collections/microsoft/ad/docs/docsite/extra-docs.yml13
-rw-r--r--ansible_collections/microsoft/ad/docs/docsite/links.yml25
-rw-r--r--ansible_collections/microsoft/ad/docs/docsite/rst/guide_attributes.rst312
-rw-r--r--ansible_collections/microsoft/ad/docs/docsite/rst/guide_ldap_connection.rst236
-rw-r--r--ansible_collections/microsoft/ad/docs/docsite/rst/guide_ldap_inventory.rst452
-rw-r--r--ansible_collections/microsoft/ad/docs/docsite/rst/guide_list_values.rst118
-rw-r--r--ansible_collections/microsoft/ad/docs/docsite/rst/guide_migration.rst189
7 files changed, 1345 insertions, 0 deletions
diff --git a/ansible_collections/microsoft/ad/docs/docsite/extra-docs.yml b/ansible_collections/microsoft/ad/docs/docsite/extra-docs.yml
new file mode 100644
index 000000000..e69755e97
--- /dev/null
+++ b/ansible_collections/microsoft/ad/docs/docsite/extra-docs.yml
@@ -0,0 +1,13 @@
+---
+# Copyright (c) Ansible Project
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+sections:
+- title: Scenario Guides
+ toctree:
+ - guide_attributes
+ - guide_ldap_connection
+ - guide_ldap_inventory
+ - guide_list_values
+ - guide_migration \ No newline at end of file
diff --git a/ansible_collections/microsoft/ad/docs/docsite/links.yml b/ansible_collections/microsoft/ad/docs/docsite/links.yml
new file mode 100644
index 000000000..7349c5f6b
--- /dev/null
+++ b/ansible_collections/microsoft/ad/docs/docsite/links.yml
@@ -0,0 +1,25 @@
+---
+# Copyright (c) Ansible Project
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+edit_on_github:
+ repository: ansible-collections/microsoft.ad
+ branch: main
+ path_prefix: ''
+
+extra_links:
+- description: Report an issue
+ url: https://github.com/ansible-collections/microsoft.ad/issues/new/choose
+
+communication:
+ matrix_rooms:
+ - topic: General usage and support questions
+ room: '#windows:ansible.im'
+ irc_channels:
+ - topic: General usage and support questions
+ network: Libera
+ channel: '#ansible-windows'
+ mailing_lists:
+ - topic: Ansible Project List
+ url: https://groups.google.com/g/ansible-project
diff --git a/ansible_collections/microsoft/ad/docs/docsite/rst/guide_attributes.rst b/ansible_collections/microsoft/ad/docs/docsite/rst/guide_attributes.rst
new file mode 100644
index 000000000..ee53dce64
--- /dev/null
+++ b/ansible_collections/microsoft/ad/docs/docsite/rst/guide_attributes.rst
@@ -0,0 +1,312 @@
+.. _ansible_collections.microsoft.ad.docsite.guide_attributes:
+
+****************
+Attributes guide
+****************
+
+A common use case for modules in this collection is to manage various Active Directory objects, such as users, groups, computers, and more. Some of these options are exposed as direct module options but other attributes might need to be set through the ``attributes`` option common to most modules in this collection.
+
+.. contents::
+ :local:
+ :depth: 1
+
+.. _ansible_collections.microsoft.ad.docsite.guide_attributes.ldap_attributes:
+
+LDAP Attributes
+===============
+
+One core component of Microsoft's Active Directory (``AD``) is a Lightweight Directory Access Protocol (``LDAP``) database. This database contains all the information relevant to an AD environment such as users, computers, organizational units, and more. Each object contains a dynamic set of attributes to describe the object and conform to a schema. For example users contain attributes like ``firstName``, ``country``, ``sAMAccountName`` to describe the object itself. Microsoft document all the builtin attributes in AD in their `AD Attribute Schema <https://learn.microsoft.com/en-us/windows/win32/adschema/attributes-all>`_. For example the `SAM-Account-Name attribute <https://learn.microsoft.com/en-us/windows/win32/adschema/a-samaccountname>`_ contains the metadata around this attribute. It includes fields like:
+
+* ``Ldap-Display-Name`` - The LDAP display name
+* ``Syntax`` - The underlying value type that the attribute stores
+* ``System-Only`` - Whether the attribute is set by the system, effectively making it read only
+* ``Is-Single-Value`` - Whether the attribute value is a single value or an array/list of values
+
+The ``Ldap-Display-Name`` is the attribute name/key that is referenced by the Ansible module. For example to manage the ``SAM-Account-Name`` attribute, it would be referenced by the key ``sAMAccountName``. Each attribute has at least 1 value associated with it, but some attributes can have multiple values. For example ``sAMAccountName`` is a ``Is-Single-Value`` attribute so only has one value but ``userCert`` can contain multiple values. The ``Active Directory Users and Computers`` snap-in (or ``dsa.msc``) can be used to view these LDAP attributes in the advanced mode. This is useful for seeing existing values as well as what attributes can be set on an object.
+
+The LDAP schema in AD can also be extended to add custom attributes for an organization. These custom attributes are also supported in the modules in this collection. To get the LDAP schema information for attributes, the following can be run in PowerShell:
+
+.. code-block:: PowerShell
+
+ Function Get-AttributeMetadata {
+ [CmdletBinding()]
+ param ([Parameter(ValueFromPipeline)][string[]]$Name)
+
+ begin {
+ $schema = (Get-ADRootDSE -Properties subschemaSubentry).subschemaSubentry
+ $getParams = @{
+ SearchBase = $schema
+ LDAPFilter = '(objectClass=*)'
+ Properties = 'attributeTypes'
+ }
+ $attributes = (Get-ADObject @getParams).attributeTypes
+ $queried = $false
+ }
+
+ process {
+ foreach ($n in $Name) {
+ $queried = $true
+ $attributes | Where-Object {
+ $_ -like "*Name '$n'*"
+ }
+ }
+ }
+
+ end {
+ if (-not $queried) {
+ $attributes
+ }
+ }
+ }
+
+ # Display all attributes
+ Get-AttributeMetadata
+
+ # Get specific attributes
+ Get-AttributeMetadata -Name sAMAccountName, o, objectGuid
+
+The output is in the format::
+
+ ( $ATTRIBUTE_OID NAME '$ATTRIBUTE_NAME' SYNTAX '$TYPE_OID' [SINGLE-VALUE|NO-USER-MODIFICATION] )
+
+The ``$TYPE_OID`` specifies the value type that can be used for this attribute. search the OID online for more information. The ``SINGLE-VALUE`` specifies if the attribute can only store 1 value. The ``NO-USER-MODIFICATION`` specifies if the attribute is read only and cannot be set.
+
+The last example outputs::
+
+ ( 1.2.840.113556.1.4.221 NAME 'sAMAccountName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+ ( 2.5.4.10 NAME 'o' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+ ( 1.2.840.113556.1.4.2 NAME 'objectGUID' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE NO-USER-MODIFICATION )
+
+This shows the ``sAMAccountName`` is a string that can only have 1 value. The ``o`` attribute is also a string but can store multiple values. The ``objectGUID`` is a byte array value that can only have 1 value and is also read only.
+
+.. _ansible_collections.microsoft.ad.docsite.guide_attributes.setting_attributes:
+
+Setting Attributes
+==================
+
+Each module that manages an Active Directory object will have an ``attributes`` option which is used to configure LDAP attributes directly. The dictionary accepts three keys:
+
+* ``add`` - Adds the attribute values if not present
+* ``remove`` - Removes the attribute values if present
+* ``set`` - Replaces the existing attribute values with the ones specified.
+
+Each of these keys contain a dictionary value where the keys are the LDAP attribute names by ``ldapDisplayName`` and their values to set. As an LDAP attribute can contain multiple values, the values specified can either be a single value or a list of values, for example:
+
+.. code-block:: yaml
+
+ - microsoft.ad.user:
+ name: MyUser
+ state: present
+ attributes:
+ set:
+ comment: My Comment
+ extensionName:
+ - Extension Value 1
+ - Extension Value 2
+ - Extension Value 3
+
+The above example will set the ``comment`` LDAP attribute of the ``MyUser`` object to the value specified. It will also ensure the ``extensionName`` attribute is set to those three values, removing any other value if present.
+
+The ``add`` key can be used to ensure the LDAP attribute values specified are added to the Attribute value list. The opposite is true for attributes under the ``remove`` key. Any attributes there will have the values specified removed if they are present on the attribute in question. For example:
+
+.. code-block:: yaml
+
+ - microsoft.ad.user:
+ name: MyUser
+ state: present
+ attributes:
+ add:
+ extensionName:
+ - Extension Value 1
+ - Extension Value 3
+ remove:
+ extensionName:
+ - Extension Value 2
+
+The above example will ensure the ``extensionName`` has the values ``Extension Value 1``, ``Extension Value 3`` and remove ``Extension Value 2`` if it is set. Because ``set`` was not used, any existing values will not be touched unless they are in the ``remove`` entry.
+
+.. note::
+ Only use LDAP attributes that can contain multiple values with ``add`` or ``remove``. Using a ``Is-Single-Value`` attribute will result in undefined behaviour.
+
+To clear an attribute value, define the attribute under ``set`` and set the value to either null (``~``) or an empty list. For example
+
+.. code-block:: yaml
+
+ - microsoft.ad.user:
+ name: MyUser
+ state: present
+ attributes:
+ set:
+ # Null can either be represented by no value
+ # or with tilde (~)
+ comment: ~
+ company:
+ extensionName: []
+
+This task will ensure the ``comment``, ``company``, and ``extensionName`` attributes are cleared of any value.
+
+.. _ansible_collections.microsoft.ad.docsite.guide_attributes.attribute_types:
+
+Attribute Types
+===============
+
+There are a few different value types that can be stored in an attribute.
+The common types are:
+
+* Strings
+* Integers
+* Booleans
+* Byte Arrays
+* Dates
+* Security Descriptors
+
+Setting a string, integer, or boolean value through an Ansible task is simply done through the YAML syntax, for example:
+
+.. code-block:: yaml
+
+ string: This is a string
+ integer: 1
+ boolean: true
+
+.. note::
+ Strings are compared in a case sensitive operation, that is ``"String" != "string"``.
+
+These simple types can also be represented by a dictionary with the keys ``type`` and ``value``. The type key can be set to one of the following:
+
+* ``bool`` - Value is casted to a boolean
+* ``bytes`` - Value is decoded as a base64 string
+* ``date_time`` - Value is decoded as an ISO 8601 datetime string
+* ``int`` - Value is decoded as an integer
+* ``security_descriptor`` - Value is decoded as a SDDL string
+* ``string`` - Value is casted to a string
+* ``raw`` - Value is used as is - this is the default type used
+
+This looks like the following:
+
+.. code-block:: yaml
+
+ - microsoft.ad.user:
+ name: MyUser
+ state: present
+ attributes:
+ set:
+ # comment: A raw value that is a string
+ comment:
+ type: raw
+ value: A string
+
+ # userAccountControl: 1234
+ userAccountControl:
+ type: int
+ value: 1234
+
+ # extensionName: ['Value 1', 'Value 2']
+ extensionName:
+ - type: raw
+ value: Value 1
+ - type: raw
+ value: Value 2
+
+The complex dictionary value with the ``type`` and ``value`` structure is only really needed for the more complex types listed below. If omitted the value is treated as ``type: raw``.
+
+Byte Arrays
+-----------
+
+As raw bytes cannot be expressed in YAML, to set an attribute with a byte array value the following format is used:
+
+.. code-block:: yaml
+
+ - microsoft.ad.user:
+ name: MyUser
+ state: present
+ attributes:
+ set:
+ # Attribute with single value
+ dsaSignature:
+ type: bytes
+ value: YmluYXJ5
+ # Attribute with multiple values
+ userCertificate:
+ - type: bytes
+ value: Zm9vYmFy
+ - type: bytes
+ value: YmFyZm9v
+
+The value specified here is the bytes encoded as a base64 string.
+
+The :ref:`ansible.builtin.b64encode filter <ansible_collections.ansible.builtin.b64encode_filter>` can be used to encode strings on the fly, and the :ref:`ansible.builtin.file lookup <ansible_collections.ansible.builtin.file_lookup>` could be used to read data from a file.
+
+
+.. code-block:: yaml
+
+ - vars:
+ sig_data: "{{ lookup('ansible.builtin.file', '/path/to/my/sig') }}"
+ microsoft.ad.user:
+ name: MyUser
+ state: present
+ attributes:
+ set:
+ # Attribute with single value
+ dsaSignature:
+ type: bytes
+ value: "{{ sig_data | ansible.builtin.b64encode }}"
+
+
+Dates
+-----
+
+Attributes with datetime values are technically integer values but represent a point in time. For ease of use, these entries can be represented as an ISO 8601 extended format datetime and will be internally represented by the integer value. To specify an attribute value in the datetime format, use the same dictionary value structure as above but set the ``type`` to ``date_time``. For example:
+
+.. code-block:: yaml
+
+ - microsoft.ad.user:
+ name: MyUser
+ state: present
+ attributes:
+ set:
+ dateAttributeSingleValue:
+ type: date_time
+ value: '2019-09-07T15:50:00+00:00'
+ dateAttributeMultipleValue:
+ - type: date_time
+ value: '2019-09-07T15:50:00Z'
+ - type: date_time
+ value: '2019-09-07T11:50:00-04:00'
+
+Internally the datetime is converted to the UTC time and converted to the number of 100 nanosecond increments since 1601-01-01. This PowerShell snippet shows what is happening internally to get the integer value:
+
+.. code-block:: PowerShell
+
+ $dt = '2019-09-07T15:50:00Z'
+ $dtVal = [DateTimeOffset]::ParseExact(
+ $dt,
+ [string[]]@("yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK"),
+ [System.Globalization.CultureInfo]::InvariantCulture,
+ [System.Globalization.DateTimeStyles]::AssumeUniversal)
+ $dtVal.UtcDateTime.ToFileTimeUtc()
+
+.. note:: If no timezone is specified, it is assumed to be in UTC.
+
+Security Descriptors
+--------------------
+
+A security descriptor is stored as a byte array in the attribute but the ``security_descriptor`` type can be used to more conveniently represent this value in a playbook. The value specified is the `Security Descriptor Definition Language <https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-definition-language>`_ (``SDDL``). This string is internally converted to the byte array needed to set the SDDL. An example of setting an attribute of this type is:
+
+.. code-block:: yaml
+
+ - microsoft.ad.user:
+ name: MyUser
+ state: present
+ attributes:
+ set:
+ nTSecurityDescriptor:
+ type: security_descriptor
+ value: O:DAG:DAD:PAI(A;CI;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)
+
+SDDL strings can be quite complex so building them manually is ill-advised. It is recommended to build a test object in the ``Active Directory Users and Computers`` snap-in (or ``dsa.msc``) and set the security as needed in the ``Security`` tab. From there the SDDL string can be retrieved by doing the following:
+
+.. code-block:: PowerShell
+
+ $dn = 'CN=ObjectName,DC=domain,DC=test'
+ $obj = Get-ADObject -Identity $dn -Properties nTSecurityDescriptor
+ $obj.nTSecurityDescriptor.GetSecurityDescriptorSddlForm('All')
diff --git a/ansible_collections/microsoft/ad/docs/docsite/rst/guide_ldap_connection.rst b/ansible_collections/microsoft/ad/docs/docsite/rst/guide_ldap_connection.rst
new file mode 100644
index 000000000..60755f00c
--- /dev/null
+++ b/ansible_collections/microsoft/ad/docs/docsite/rst/guide_ldap_connection.rst
@@ -0,0 +1,236 @@
+.. _ansible_collections.microsoft.ad.docsite.guide_ldap_connection:
+
+*********************
+LDAP Connection guide
+*********************
+
+This guide covers information about communicating with an LDAP server, like Microsoft Active Directory, from the Ansible host. Unlike Windows hosts, there are no builtin mechanisms to communicate and authenticate with an LDAP server, so the plugins that run on the Ansible host require some extra configuration to get working.
+
+.. note::
+ This guide covers LDAP communication from the Ansible host. This does not apply to the modules that run on the remote Windows hosts.
+
+.. contents::
+ :local:
+ :depth: 1
+
+.. _ansible_collections.microsoft.ad.docsite.guide_ldap_connection.requirements:
+
+Requirements
+============
+
+The LDAP connection code requires the `sansldap <https://pypi.org/project/sansldap/>`_ and `pyspnego <https://pypi.org/project/pyspnego/>`_ libraries. They can be installed using ``pip`` with:
+
+.. code-block:: shell-session
+
+ $ python3 -m pip install --user \
+ 'pyspnego >= 0.8.0'
+ sansldap
+
+.. note::
+ This guide assumes ``python3`` is the same Python that Ansible uses, see ``ansible --version`` for details on the Python version/location.
+
+There are also optional dependencies to provide extra features
+
++-------------------------+-----------------------------+
+| Feature | Package |
++=========================+=============================+
+| Kerberos Authentication | pyspnego[kerberos] >= 0.8.0 |
++-------------------------+-----------------------------+
+| Server Lookups | dnspython |
++-------------------------+-----------------------------+
+| LAPS Decryption | dpapi-ng |
++-------------------------+-----------------------------+
+
+To install all the optional features run:
+
+.. code-block:: shell-session
+
+ $ python3 -m pip install --user \
+ dnspython \
+ dpapi-ng \
+ 'pyspnego[kerberos] >= 0.8.0'
+
+The Kerberos authentication components require the Kerberos system libraries to be present. For RPM based systems, these are:
+
+.. code-block:: shell-session
+
+ $ dnf install gcc python3-devel krb5-libs krb5-devel
+
+Other Linux distributions require the same packages listed above but they are likely listed under different names than what ``dnf`` uses.
+
+The :ref:`microsoft.ad.debug_ldap_client <ansible_collections.microsoft.ad.debug_ldap_client_module>`. action plugin can be used to debug the Ansible host setup and its LDAP capabilities. It includes details such as:
+
+* The Python packages related to LDAP that are installed, or import failure messages if not installed
+* The Kerberos host and credential cache information if the Kerberos extras are installed
+* The SRV lookup information if ``dnspython`` and Kerberos extras are installed
+
+To use this module simply run
+
+.. code-block:: shell-session
+
+ $ ansible localhost -m microsoft.ad.debug_ldap_client
+
+
+.. _ansible_collections.microsoft.ad.docsite.guide_ldap_connection.connection_options:
+
+Connection options
+==================
+
+Connecting to a Microsoft Active Directory or LDAP server requires information like the domain controller hostname, port, whether to use LDAPS or StartTLS, and authentication information. Some of this information can be retrieved based on the Ansible host environment but can also be manually specified through the plugin options. These options include:
+
++---------------+--------------------------------+---------------------------------------------+
+| Option | Default | Purpose |
++===============+================================+=============================================+
+| server | Server lookup through Kerberos | The LDAP server hostname |
++---------------+--------------------------------+---------------------------------------------+
+| port | 389 or 686 if tls_mode=ldaps | The LDAP port |
++---------------+--------------------------------+---------------------------------------------+
+| tls_mode | LDAPS if port=686 else None | TLS details - LDAP, LDAP + StartTLS, LDAPS |
++---------------+--------------------------------+---------------------------------------------+
+| auth_protocol | Negotiate | Authentication protocol |
++---------------+--------------------------------+---------------------------------------------+
+| username | None | Attempts to use Kerberos cache if available |
++---------------+--------------------------------+---------------------------------------------+
+| password | None | Attempts to use Kerberos cache if available |
++---------------+--------------------------------+---------------------------------------------+
+
+The server lookup details are described below. The port defaults to ``389`` unless ``tls_mode: ldaps`` is specified. The TLS mode defaults to ``ldaps`` if the port is explicitly set to ``686`` otherwise it defaults to ``389``. The authentication protocol defaults to ``negotiate`` while attempting to use the implicit credential if it's available.
+
+
+.. _ansible_collections.microsoft.ad.docsite.guide_ldap_connection.server_lookup:
+
+Server lookup
+-------------
+
+If no server option was explicitly set, the plugin will attempt to lookup the LDAP server based on the current environment configuration. This is only possible if:
+
+* The ``dnspython`` Python package is installed
+* The ``pyspnego[kerberos]`` Python package for Kerberos is installed
+* The underlying Kerberos library has a ``default_realm`` set in the `MIT krb5.conf <https://web.mit.edu/kerberos/krb5-latest/doc/admin/host_config.html#default-realm>`_
+
+If none of the above are true, the connection will fail and an explicit server must be supplied. If all the requirements are satisfied this is the server lookup workflow:
+
+* The ``default_realm`` of the local Kerberos configuration is retrieved
+* A DNS SRV lookup is done for the record ``_ldap._tcp.dc._msdcs.{{ default_realm }}``
+* The DNS records are sorted by priority and weight and the first is selected
+* The hostname and port on the selected SRV record are used for the lookup
+
+.. note::
+ If an explicit port is specified, it will take priority over the port returned by the SRV record.
+
+
+.. _ansible_collections.microsoft.ad.docsite.guide_ldap_connection.authentication:
+
+Authentication
+==============
+
+A critical component of LDAP connections is how the user authenticates itself to the server. The following authentication mechanisms are supported:
+
++----------------+---------------------+----------------------------+
+| Authentication | Supports Encryption | Implicit Credential |
++================+=====================+============================+
+| simple | No - TLS needed | Yes - Appears as Anonymous |
++----------------+---------------------+----------------------------+
+| certificate | Yes | No |
++----------------+---------------------+----------------------------+
+| negotiate | Yes | Yes - With Kerberos |
++----------------+---------------------+----------------------------+
+| kerberos | Yes | Yes |
++----------------+---------------------+----------------------------+
+| ntlm | Yes | No |
++----------------+---------------------+----------------------------+
+
+Unless otherwise specified, the default authentication protocol used is ``negotiate`` which relies on the ``pyspnego`` library. See :ref:`requirements <ansible_collections.microsoft.ad.docsite.guide_ldap_connection.requirements>` for more information on how to install this requirement.
+
+Any protocol that does not support encryption must either be used with LDAPS, StartTLS, or they must explicitly disable the encryption checks with the ``encrypt: false`` option. Disabling encryption is not recommended as it will send the credentials without any protection and any of the data exchanged can be seen by anyone. It also requires the target server to allow unencrypted connections as they can reject such connections.
+
+Implicit credential support documents whether the authentication protocol can authenticate without an explicit ``username`` and ``password`` specified. Currently only ``simple`` and ``negotiate/kerberos`` supports implicit credentials. See each protocol section for more details.
+
+Simple
+------
+
+Simple authentication is the most basic authentication protocol supported. It works by sending the username and password in plaintext to the server, similar to HTTP Basic authentication. Microsoft AD requires the username to be the ``sAMAccountName`` or ``userPrincipalName`` of the account but other LDAP implementations require the LDAP ``distinguishedName``. While it is possible to do an anonymous bind when no username or password is specified, it is likely the server will reject any search operations unless it is authenticated with an actual users credentials. Simple authentication is not allowed over a connection that is not protected by TLS. It is possible to allow simple authentication over such connections by disabling the encryption check but this is not recommended.
+
+.. warning::
+ Simple authentication should be avoided unless TLS is used, either through LDAPS or StartTLS. Failure to use use LDAPS will expose the credentials used during the authentication and the subsequent data unprotected from eavesdropping or tampering.
+
+
+Certificate
+-----------
+
+Certificate authentication uses TLS client authentication as part of the TLS handshake to authenticate the user to the host. As it is part of the TLS handshake, it can only be used over an LDAPS connection or with StartTLS. It uses a certificate and certificate key of the user to authenticate as. There are three options that can be used to specify a client certificate and key to use for authentication:
+
+* ``certificate`` - The certificate, and optionally bundled key
+* ``certificate_key`` - The certificate key if not bundled in ``certificate``
+* ``certificate_password`` - The password used to decrypt the certificate key
+
+The ``certificate`` and ``certificate_key`` can either be a file path to the certificate and key or they can be a string of the PEM encoded certificate/key. The ``certificate`` file path can be a PEM, DER, or PKCS12/PFX encoded certificate with optional key bundle whereas the ``certificate_key`` file path can be a PEM or DER encoded key. If the key inside the PEM, DER, or PKCS12/PFX content is encrypted, the ``certificate_password`` can be used to specify the password used to decrypt the key.
+
+.. note::
+ Setting these options are dependent on the plugin itself, the keys here reflect the option name and not necessarily Ansible variables that can be set and read automatically by a plugin.
+
+
+Negotiate
+---------
+
+Negotiate authentication is the default authentication protocol used by LDAP connections. It is a combination of both ``kerberos`` and ``ntlm`` with the client negotiating which one to use. It will favor ``kerberos`` if it is available and fallback to ``ntlm`` if not. The ``pyspnego`` Python package provides ``negotiate`` with just ``ntlm`` support, ``kerberos`` support is provided by the ``pyspnego[kerberos]`` extras option. See :ref:`requirements <ansible_collections.microsoft.ad.docsite.guide_ldap_connection.requirements>` for more information on how to install this requirement.
+
+Kerberos
+--------
+
+Kerberos authentication is a modern authentication protocol supported by Microsoft AD servers and is the preferred protocol for authentication. It is only available if the ``pyspnego[kerberos]`` extras package is installed and the host has been configured properly. Typically this configuration is done through the `/etc/krb5.conf <https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html>`_ file on the system. This guide will not go into configuring the host's Kerberos settings as it is environment specific.
+
+A good way to ensure the host has been configured to use Kerberos correctly is to ensure the following commands work:
+
+.. code-block:: shell-session
+
+ $ python -c "import krb5"
+ $ kinit username@DOMAIN.REALM
+ $ kvno ldap/dc.domain.realm
+
+.. note::
+ The ``kvno`` command is an MIT krb5 specific command, it is not available on hosts that use Heimdal krb5 like macOS.
+
+The ``python`` command ensures the required Python libraries have been installed. The ``kinit`` command will retrieve a Kerberos ticket for the user specified and the ``kvno`` command will attempt to retrieve a service ticket for the service principal name (SPN) requested. If both commands work then there is a good chance Kerberos authentication will work with the LDAP connection.
+
+Using the ``kinit`` command it is possible to set up a credential cache for Ansible to use for authentication. By having a credential retrieved using ``kinit``, it is possible to authenticate with the LDAP server without any explicit username and password set in Ansible. It is still possible to use Kerberos with explicit credentials.
+
+NTLM
+----
+
+NTLM authentication is a simple authentication protocol that can be used by itself or as part of the ``negotiate`` fallback if ``kerberos`` is unavailable. Unlike ``kerberos`` support, it does not normally support implicit credentials so typically needs an explicit username and password specified to be used. It requires no extra host configuration and should work once ``pyspnego`` has been installed.
+
+.. warning::
+ While NTLM does support encryption it is considered weak by modern standards. It is recommended to only use NTLM with an LDAPS or StartTLS connection where the stronger encryption and server checks provided by TLS mitigate the weaknesses in NTLM.
+
+
+.. _ansible_collections.microsoft.ad.docsite.guide_ldap_connection.cert_validation:
+
+Certificate validation
+======================
+
+Using LDAPS or LDAP over StartTLS will perform a TLS handshake which by default has the client attempting to validate the certificate presented by the server. If the certificate chain cannot be trusted, or the hostname does not match the one being requested the connection will fail with an error indicating why. The default trust store location is dependent on the Python configuration and what SSL library it has been linked to. Typically it would be the OS' default trust store but when in doubt the following Python code can be used to verify the LDAPS certificate. Make sure to change ``hostname`` to the hostname of the LDAP server that should be tested.
+
+.. code-block:: python
+
+ import socket
+ import ssl
+
+ hostname = 'dc.domain.com'
+ port = 636
+ context = ssl.create_default_context()
+
+ with socket.create_connection((hostname, port)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+The ``ca_cert`` connection option can be used to set an explicit CA bundle to use for verification. This is useful if the CA bundle is not part of the OS store but located somewhere else on the filesystem. The value can be in the form of:
+
+* a file path to a PEM or DER encoded bundle of certificates
+* A directory path that contains several CA certificates in the PEM format following an OpenSSL specific layout as document by `CApath <https://www.openssl.org/docs/manmaster/man3/SSL_CTX_load_verify_locations.html>`_
+* A string containing PEM encoded certificates
+
+It is also possible to disable certificate verification using the ``cert_validation`` connection option. The default is ``always`` but can be set to ``ignore`` to disable all checks or ``ignore_hostname`` to disable just the hostname check. This can be useful for test environments that use self signed certificates but it should not be used in a production environment.
+
+.. warning::
+ Disabling certificate validation removes a lot of the benefits that TLS offers. There is no way to verify the target server is who it says that it is.
diff --git a/ansible_collections/microsoft/ad/docs/docsite/rst/guide_ldap_inventory.rst b/ansible_collections/microsoft/ad/docs/docsite/rst/guide_ldap_inventory.rst
new file mode 100644
index 000000000..798775b21
--- /dev/null
+++ b/ansible_collections/microsoft/ad/docs/docsite/rst/guide_ldap_inventory.rst
@@ -0,0 +1,452 @@
+.. _ansible_collections.microsoft.ad.docsite.guide_ldap_inventory:
+
+********************
+LDAP Inventory guide
+********************
+
+This guide covers information about the LDAP inventory plugin included in this collection. This inventory plugin can be used to build an inventory from an LDAP server source, like Microsoft Active Directory.
+
+.. contents::
+ :local:
+ :depth: 1
+
+.. _ansible_collections.microsoft.ad.docsite.guide_ldap_inventory.connection_info:
+
+Connection info
+===============
+
+Details on how to configure an LDAP connection can be found under :ref:`the LDAP connection guide <ansible_collections.microsoft.ad.docsite.guide_ldap_connection>`. Each of the connection options described by the plugin documentation are specified in the inventory yml configuration file like the below.
+
+.. code-block:: yaml
+
+ plugin: microsoft.ad.ldap
+
+ # LDAP connection options can be defined in the yaml config.
+ auth_protocol: simple
+ username: UserName
+ password: MyPassword123
+ tls_mode: ldaps
+
+
+.. _ansible_collections.microsoft.ad.docsite.guide_ldap_inventory.attributes:
+
+Attributes
+==========
+
+The LDAP inventory plugin can be used to set custom facts for each host it retrieves based on the computer object's LDAP attributes. Retrieving custom attributes is done through the ``attributes`` option in the inventory plugin definition. The value is set to one of the three following types:
+
+* Empty string or null
+* A template string
+* A dictionary
+
+.. note::
+ While an individual attribute can only be set to one of these types, it is possible to use the different value types for different attributes.
+
+It is also possible to use the ``compose`` inventory option to use the builtin compose templating provided by inventory plugins but the LDAP attributes must first be requested through the ``attributes`` option and referenced in the ``compose`` template through the host fact the ``attributes`` set it on.
+
+Empty string or null
+--------------------
+
+.. code-block:: yaml
+
+ attributes:
+ comment:
+ objectSid: ''
+ ms-Mcs-AdmPwd:
+
+In this case each of the attribute values will be set as a host fact as they are coerced by the LDAP schema, see :ref:`value types and templating <ansible_collections.microsoft.ad.docsite.guide_ldap_inventory.value_types>`. The name of each fact will be based on the attribute name with ``-`` being replaced by ``_``. In the above example the host facts ``comment``, ``objectSid``, and ``ms_Mcs_AdmPwd`` will be set to the coerced values.
+
+.. code-block:: yaml
+
+ # ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
+
+ ansible_host: MYHOST.domain.com
+ comment: test comment
+ ms_Mcs_AdmPwd: Password123!
+ objectSid: S-1-5-21-1234-1108
+ microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
+
+
+Template string
+---------------
+
+.. code-block:: yaml
+
+ attributes:
+ comment: this
+ objectSid: raw | microsoft.ad.as_sid
+ ms-Mcs-AdmPwd: raw | first
+
+This format will set the host fact based on the template value specified. Each template is implicitly wrapped with ``{{ ... }}`` and processed through Jinja2 to produce a result. This means the template string can contain filters provided by Ansible and other collections to convert the raw LDAP value into something more useful. The ``this`` variable refers to the coerced LDAP attribute value and ``raw`` refers to a list of base64 encoded byte strings of the raw LDAP attribute value. See :ref:`value types and templating <ansible_collections.microsoft.ad.docsite.guide_ldap_inventory.value_types>` for more information around what can be done inside the templates. Each host fact will be named after the attribute name with ``-`` being replaced by ``_``. In the above example the host facts ``command``, ``objectSid``, and ``ms_Mcs_AdmPwd`` will be set to the template results.
+
+.. code-block:: yaml
+
+ # ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
+
+ ansible_host: MYHOST.domain.com
+ comment: test comment
+ ms_Mcs_AdmPwd: UGFzc3dvcmQxMjMh
+ objectSid:
+ - S-1-5-21-1234-1108
+ microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
+
+
+Dictionary
+----------
+
+.. code-block:: yaml
+
+ attributes:
+ comment:
+ # Jinja2 native types will automatically convert this to a dict as
+ # the value is a json string.
+ my_comment:
+ other_var: this | from_json
+ objectSid:
+ sid: raw | microsoft.ad.as_sid | first
+ ms-Mcs-AdmPwd:
+ ansible_password: this
+
+The final value that can be set on each attribute values is a dictionary where the keys are the host facts to set and the value is the template used to dervice the final value. It can be null or an empty string to refer to the LDAP coerced value of that attribute (``this``) or a template string to template a new value based on the requirements at hand. See the above two formats for more info on null/empty string vs a string template value. In the above example there are 4 host facts set:
+
+* ``my_command`` - the coerced value for the ``comment`` attribute
+* ``other_var`` - a dictionary created from the coerced value of ``comment`` if it was a json string
+* ``sid`` - the computer SID value as a string derived from ``objectSid``
+* ``ansible_password`` - the LAPS password coerced value derived from ``ms-Mcs-AdmPwd``
+
+.. code-block:: yaml
+
+ # ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
+
+ ansible_host: MYHOST.domain.com
+ ansible_password: Password123!
+ my_comment:
+ foo: bar
+ other_var:
+ foo: bar
+ sid: S-1-5-21-1234-1108
+ microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
+
+.. note::
+ The host fact names are used literally, there are no conversions from ``-`` to ``_`` when using this format.
+
+
+.. _ansible_collections.microsoft.ad.docsite.guide_ldap_inventory.inventory_hostname:
+
+Inventory hostname
+==================
+
+By default the ``inventory_hostname`` for a found host will be based on the ``name`` LDAP attribute value. If the ``dNSHostName`` attribute is set for the computer account found, it will be set as the ``ansible_host`` fact. To define a custom ``inventory_hostname`` or ``ansible_host`` either set it in the ``attributes`` or ``compose`` plugin option under that key. For example this will set the ``inventory_hostname`` to the value of ``sAMAccountName`` without the ending ``$`` rather than the computer account LDAP ``name`` attribute.
+
+.. code-block:: yaml
+
+ attributes:
+ sAMAccountName:
+ inventory_hostname: sAMAccountName[:-1]
+
+.. code-block:: yaml
+
+ # ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
+
+ ansible_host: MYHOST.domain.com
+ microsoft_ad_distinguished_name: CN=OtherName,CN=Computers,DC=domain,DC=com
+ sAMAccountName: MYHOST$
+
+
+It is also possible to set ``inventory_hostname`` under the ``compose`` key. The following will produce the same output as the above.
+
+.. code-block:: yaml
+
+ attributes:
+ sAMAccountName:
+
+ compose:
+ inventory_hostname: sAMAccountName[:-1]
+
+
+An example of setting a custom ``ansible_host`` fact that is used as the connection host but leaving the default ``inventory_hostname`` of the computer account name is:
+
+.. code-block:: yaml
+
+ attributes:
+ sAMAccountName:
+ ansible_host: sAMAccountName[:-1]
+
+
+.. _ansible_collections.microsoft.ad.docsite.guide_ldap_inventory.value_types:
+
+Value types and templating
+==========================
+
+Each LDAP attribute value is stored as a list of bytes but the schema supplied in the LDAP database can describe how those raw list of bytes are represented as a proper type, like a string, integer, boolean, etc. Currently only these four types are used when coercing LDAP attribute values
+
+* Booleans
+* Integers
+* Bytes
+* Strings
+
+Booleans, integers, and strings are coerced into those specific Python types but bytes are coerced into a base64 string encoding of those bytes.
+
+.. note::
+ The ``objectGuid`` and ``objectSid`` attributes are always coerced into strings representing the security identifier and guid respectively. These are the only attributes that have special coercion rules outside of the LDAP schema syntax.
+
+LDAP attribute values may also be marked as a a single or multi valued attribute. A single value contains just the coerced value, or ``None/null`` if it has not been set while a multi valued attribute will be set as a list of coerced values. For example the ``comment`` is a single valued string while ``servicePrincipalName`` is a multi valued string. Using this inventory configuration that requests ``comment``, and ``servicePrincipalName`` we get the following inventory host definition:
+
+.. code-block:: yaml
+
+ plugin: microsoft.ad.ldap
+
+ attributes:
+ comment:
+ servicePrincipalName:
+
+
+.. code-block:: yaml
+
+ # ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
+
+ ansible_host: MYHOST.domain.com
+ comment: test comment
+ servicePrincipalName:
+ - WSMAN/MYHOST
+ - WSMAN/MYHOST.domain.com
+ - TERMSRV/MYHOST
+ - TERMSRV/MYHOST.domain.com
+ - RestrictedKrbHost/MYHOST
+ - HOST/MYHOST
+ - RestrictedKrbHost/MYHOST.domain.com
+ - HOST/MYHOST.domain.com
+ microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
+
+Some attributes like ``pwdLastSet`` are typically represented as a datetime value but internally are stored as integers. As there is no metadata in the LDAP schema to denote these integer values as datetime objects they will only be coerced into integer values by default.
+
+The following filters can be used as an easy way to further convert the coerced values into something more readable:
+
+* :ref:`microsoft.ad.as_datetime <ansible_collections.microsoft.ad.as_datetime_filter>`
+* :ref:`microsoft.ad.as_guid <ansible_collections.microsoft.ad.as_guid_filter>`
+* :ref:`microsoft.ad.as_sid <ansible_collections.microsoft.ad.as_sid_filter>`
+
+An example of these filters being used in the ``attributes`` option can be seen below:
+
+.. code-block:: yaml
+
+ plugin: microsoft.ad.ldap
+
+ attributes:
+ pwdLastSet:
+ password_last_set_int: this
+ password_last_set_datetime: this | microsoft.ad.as_datetime
+
+.. code-block:: yaml
+
+ # ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
+
+ ansible_host: MYHOST.domain.com
+ password_last_set_datetime: 2023-02-06T07:39:09.195321+0000
+ password_last_set_int: 133201427491953218
+ microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
+
+The templates can also reference other filters that exist outside the collection, like the Ansible builtin ``from_json`` and more. The value is simply what would be placed inside ``{{ ... }}`` during a normal template operation.
+
+.. note::
+ Lookups cannot be used in the attribute value templates, only filters.
+
+Each template used in the ``attributes`` inventory option can reference the following variables:
+
+* ``this``
+* ``raw``
+* Any previously defined attributes
+
+The ``this`` variable refers to the coerced LDAP attribute value while ``raw`` refers to the list of base64 encoded strings representing the raw LDAP value that hasn't been coerced. As each attribute host fact is processed, it is also available in the subsequent templates under that host fact name. Here is an example of a more complex set of attributes:
+
+.. code-block:: yaml
+
+ plugin: microsoft.ad.ldap
+
+ attributes:
+ objectSid:
+ sid: this
+ sid_raw: raw
+ sid_raw_filtered: raw | microsoft.ad.as_sid | first
+ objectGuid:
+ sAMAccountName:
+ computer_name:
+ comment:
+ comment: this
+ # Can refer to previously set attributes above
+ description: computer_name ~ " - " ~ sid ~ " - " ~ objectGuid ~ " - " ~ this
+
+ # Can also be used as a template and refer to the vars retrieved above
+ compose:
+ comment2: comment
+
+.. code-block:: yaml
+
+ # ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
+
+ ansible_host: MYHOST.domain.com
+ comment: test comment
+ comment2: test comment
+ computer_name: MYHOST$
+ description: MYHOST$ - S-1-5-21-1234-1108 - 51cc490f-1de0-41ae-98ad-dc065d5b33e2 - test comment
+ objectGuid: 51cc490f-1de0-41ae-98ad-dc065d5b33e2
+ sid: S-1-5-21-1234-1108
+ sid_raw:
+ - AQMAAAAAAAUVAAAA0gQAAFQEAAA=
+ sid_raw_filtered: S-1-5-21-1234-1108
+ microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
+
+
+.. _ansible_collections.microsoft.ad.docsite.guide_ldap_inventory.laps:
+
+LAPS
+====
+
+Local Administrator Administrator Password Solution (LAPS) can be used to automatically change the password of the local administrator account on domain joined hosts. The LDAP connection plugin can retrieve the LAPS-managed value and assign it as the connection password for the target host.
+
+There are three different attributes that can be used by LAPS to store the password information:
+
+* ``ms-Mcs-AdmPwd`` - The legacy LAPS attribute containing the password
+* ``msLAPS-Password`` - The Windows LAPS attribute containing the username and password
+* ``msLAPS-EncryptedPassword`` - The Windows LAPS attribute containing the encrypted username and password
+
+If using the legacy LAPS setup, the following will retrieve and assign the connection username and password to the LAPS-managed value:
+
+.. code-block:: yaml
+
+ plugin: microsoft.ad.ldap
+
+ attributes:
+ ms-Mcs-AdmPwd:
+ ansible_user: '"Administrator"'
+ ansible_password: this
+
+.. code-block:: yaml
+
+ # ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
+
+ ansible_host: MYHOST.domain.com
+ ansible_password: aR$lmrqK1l622H
+ ansible_user: Administrator
+ microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
+
+.. note::
+ Legacy LAPS does not store the username, the above example hardcodes the user name ``Administrator``.
+
+If using Windows LAPS without encryption, the following will assign the connection username and password to the LAPS-managed values:
+
+.. code-block:: yaml
+
+ plugin: microsoft.ad.ldap
+
+ attributes:
+ msLAPS-Password:
+ ansible_user: (this | from_json).n
+ ansible_password: (this | from_json).p
+ raw_example: raw
+ this_example: this
+
+.. code-block:: yaml
+
+ # ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
+
+ ansible_host: MYHOST.domain.com
+ ansible_password: AWznso@ZJ+J6p9
+ ansible_user: Administrator
+ microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
+ raw_example:
+ - eyJuIjoiQWRtaW5pc3RyYXRvciIsInQiOiIxZDk4MmI0MzdiN2E1YzYiLCJwIjoiQVd6bnNvQFpKK0o2cDkifQ==
+ this_example:
+ n: Administrator
+ p: AWznso@ZJ+J6p9
+ t: 1d982b437b7a5c6
+
+Unlike Legacy LAPS, the attribute value is a json string that contains the keys:
+
+* ``n`` - The account name the password was encrypted for
+* ``p`` - The password for the account
+* ``t`` - The time the password was set encoded as a FILETIME in base16
+
+.. note::
+ It is recommended to use the ``from_json`` filter (as shown in the example above) on the ``this`` value to ensure consistent behavior in the presence or absence of Jinja2 native type support.
+
+Getting an encrypted Windows LAPS value requires the ``dpapi-ng`` Python library to be installed. See :ref:`the LDAP connection requirements <ansible_collections.microsoft.ad.docsite.guide_ldap_connection.requirements>` for more information on this optional package and how to debug whether it's installed or not.
+
+.. note::
+ Using Windows LAPS encrypted password is currently an experimental feature.
+
+With the ``dpapi-ng`` package installed, an authorized LDAP user can decrypt and assign the LAPS-managed username and password to the target host connection as follows:
+
+.. code-block:: yaml
+
+ plugin: microsoft.ad.ldap
+
+ attributes:
+ msLAPS-EncryptedPassword:
+ ansible_user: (this.value | from_json).n
+ ansible_password: (this.value | from_json).p
+ raw_example: raw
+ this_example: this
+
+.. code-block:: yaml
+
+ # ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
+
+ ansible_host: MYHOST.domain.com
+ ansible_password: 6jr&}yK++{0Q}&
+ ansible_user: Administrator
+ microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
+ raw_example:
+ - toLZAWR7rgfk...
+ this_example:
+ encrypted_value: MIIETgYJKoZI...
+ flags: 0
+ info: ''
+ update_timestamp: 133281382308674404
+ value: '{"n":"Administrator","t":"1d982b607ae7b64","p":"6jr&}yK++{0Q}&"}'
+
+The ``raw`` value contains the raw base64 encoded value as stored in AD. The ``this`` value contains a dictionary with the following keys:
+
+* ``encrypted_value``: The encrypted password blob as a base64 string
+* ``flags``: The flags set as a bitwise int value, currently these are undocumented by Microsoft
+* ``update_timestamp``: The FILETIME value of when the
+* ``value``: The decrypted value containing the username and password as a JSON string
+* ``debug``: Debug information that indicates why it failed to decrypt the value
+
+The ``value`` key will only be present if the decryption process was successful. If it failed, the ``debug`` key will be present and contain the reason why it failed to be decrypted.
+
+If the ``dpapi-ng`` library is not installed this is what the output would look like:
+
+.. code-block:: yaml
+
+ # ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
+
+ ansible_host: MYHOST.domain.com
+ microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
+ raw_example:
+ - toLZAWR7rgfk...
+ this_example:
+ debug: Cannot decrypt value as the Python library dpapi-ng is not installed
+ encrypted_value: MIIETgYJKoZI...
+ flags: 0
+ update_timestamp: 133281382308674404
+
+The ``value`` key is no longer present and ``debug`` contains the message that ``dpapi-ng`` is not installed.
+
+If ``dpapi-ng`` library was installed but the connection user is not authorized to decrypt the value this is what the output would look like:
+
+.. code-block:: yaml
+
+ # ansible-inventory -i microsoft.ad.ldap.yml --host MYHOST --vars --yaml
+
+ ansible_host: MYHOST.domain.com
+ microsoft_ad_distinguished_name: CN=MYHOST,CN=Computers,DC=domain,DC=com
+ raw_example:
+ - toLZAWR7rgfk...
+ this_example:
+ debug: Failed to decrypt value due to error - ValueError GetKey failed 0x80070005
+ encrypted_value: MIIETgYJKoZI...
+ flags: 0
+ update_timestamp: 133281382308674404
+
+A simple way to test that the connection user is able to decrypt the password is to run ``Get-LapsADPassword -Identity MYHOST`` on a Windows host as that user.
diff --git a/ansible_collections/microsoft/ad/docs/docsite/rst/guide_list_values.rst b/ansible_collections/microsoft/ad/docs/docsite/rst/guide_list_values.rst
new file mode 100644
index 000000000..642e748bd
--- /dev/null
+++ b/ansible_collections/microsoft/ad/docs/docsite/rst/guide_list_values.rst
@@ -0,0 +1,118 @@
+.. _ansible_collections.microsoft.ad.docsite.guide_list_values:
+
+********************************
+Setting list option values guide
+********************************
+
+Some AD options accept multiple values which require special rules when it comes to checking for idempotency in Ansible. This collection has been designed so that each of the modules which manage AD objects follow the same style when it comes to their options. In particular, they should all follow the style documented in this guide when it comes to options that contain multiple values like ``spn``, ``delegates``, etc.
+
+.. contents::
+ :local:
+ :depth: 1
+
+.. _ansible_collections.microsoft.ad.docsite.guide_list_values.something:
+
+Add, remove, and set
+====================
+
+For each module option that manage a multi valued LDAP attribute there exists three actions:
+
+* ``add``
+* ``remove``
+* ``set``
+
+The ``add`` and ``remove`` option will add or remove the specified value(s) from the existing value. The ``set`` option will replace the existing values with what was specified in the task.
+Using an example of an AD object with the following ``servicePrincipalNames`` values:
+
+* ``HTTP/host1``
+* ``HTTP/host1.domain.com``
+* ``HTTP/host1.domain.com:443``
+
+Doing ``add: ['HTTP/host1','HTTP/host2']`` will add ``HTTP/host2`` to the existing values bringing it to:
+
+* ``HTTP/host1``
+* ``HTTP/host1.domain.com``
+* ``HTTP/host1.domain.com:443``
+* ``HTTP/host2``
+
+Doing ``remove: ['HTTP/host1','HTTP/host3']`` will remove ``HTTP/host1`` from the existing values bringing it to:
+
+* ``HTTP/host1.domain.com``
+* ``HTTP/host1.domain.com:443``
+
+Doing ``set: ['HTTP/host1', 'HTTP/host2']`` will remove any values not in that list and add values in that list but not set bringing it to:
+
+* ``HTTP/host1``
+* ``HTTP/host2``
+
+It is possible to use ``add`` and ``remove`` together but setting ``set`` will always take precedence over the others.
+It is also possible to clear all the existing values by setting the ``set`` value to an empty list, for example ``set: []``.
+
+Examples
+========
+
+The ``add``, ``remove``, and ``set`` options are subkeys of the module option it controls. For example the :ref:`microsoft.ad.user <ansible_collections.microsoft.ad.user_module>` has an option called ``groups`` which control the list of groups the user is a member of. To add a group to the user, simply use the ``add`` key like so:
+
+.. code-block:: yaml
+
+ - name: add a user to a group
+ microsoft.ad.user:
+ name: MyUser
+ groups:
+ add:
+ - Group 1
+ - Group 2
+
+This will ensure the user is added to the groups ``Group 1`` and ``Group 2`` while also preserving the existing membership. To remove a user from a user, simple use the ``remove`` key like so:
+
+.. code-block:: yaml
+
+ - name: remove a user from a group
+ microsoft.ad.user:
+ name: MyUser
+ groups:
+ remove:
+ - Group 1
+ - Group 2
+
+This does the opposite to add and will remove the user from ``Group 1`` and ``Group 2`` but it will still preserve any existing group memberships of that user. It is also possible to combine ``add`` and ``remove`` together:
+
+.. code-block:: yaml
+
+ - name: add and remove user groups
+ microsoft.ad.user:
+ name: MyUser
+ groups:
+ add:
+ - Group 1
+ remove:
+ - Group 2
+
+This will ensure the user is a member of ``Group 1`` and is not a member of ``Group 2``. Like before it will not touch the existing group membership if they are not specified.
+
+The set option following the same format like so:
+
+.. code-block:: yaml
+
+ - name: set user groups
+ microsoft.ad.user:
+ name: MyUser
+ groups:
+ set:
+ - Group 1
+ - Group 2
+
+This will ensure the user is only members of ``Group 1`` and ``Group 2``, removing any other group not in that list. While it is possible to combine ``set`` with either ``add`` or ``remove``, the module will completely ignore the values in ``add`` or ``remove``.
+
+Finally to remove a user from all groups, use an empty list for the ``set`` option like so:
+
+.. code-block:: yaml
+
+ - name: remove user groups
+ microsoft.ad.user:
+ name: MyUser
+ groups:
+ set: []
+
+.. note::
+ This is not actually possible for user groups as it will always be a member of its primary group, it is just used for demonstration purposes.
diff --git a/ansible_collections/microsoft/ad/docs/docsite/rst/guide_migration.rst b/ansible_collections/microsoft/ad/docs/docsite/rst/guide_migration.rst
new file mode 100644
index 000000000..c0b01ca5f
--- /dev/null
+++ b/ansible_collections/microsoft/ad/docs/docsite/rst/guide_migration.rst
@@ -0,0 +1,189 @@
+.. _ansible_collections.microsoft.ad.docsite.guide_migration:
+
+***************
+Migration guide
+***************
+
+Some of the modules in this collection have come from the `ansible.windows collection <https://galaxy.ansible.com/ansible/windows>`_ or the `community.windows collection <https://galaxy.ansible.com/community/windows>`_. This document will go through some of the changes made to help ease the transition from the older modules to the ones in this collection.
+
+.. contents::
+ :local:
+ :depth: 1
+
+.. _ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules:
+
+Migrated Modules
+================
+
+The following modules have been migrated in some shape or form into this collection
+
+* ``ansible.windows.win_domain`` -> ``microsoft.ad.domain`` - :ref:`details <ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain>`
+* ``ansible.windows.win_domain_controller`` -> ``microsoft.ad.domain_controller`` - :ref:`details <ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_controller>`
+* ``ansible.windows.win_domain_membership`` -> ``microsoft.ad.membership`` - :ref:`details <ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_membership>`
+* ``community.windows.win_domain_computer`` -> ``microsoft.ad.computer`` - :ref:`details <ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_computer>`
+* ``community.windows.win_domain_group`` -> ``microsoft.ad.group`` - :ref:`details <ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_group>`
+* ``community.windows.win_domain_group_membership`` -> ``microsoft.ad.group`` - :ref:`details <ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_group_membership>`
+* ``community.windows.win_domain_object_info`` -> ``microsoft.ad.object_info`` - :ref:`details <ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_object_info>`
+* ``community.windows.win_domain_ou`` -> ``microsoft.ad.ou`` - :ref:`details <ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_ou>`
+* ``community.windows.win_domain_user`` -> ``microsoft.ad.user`` - :ref:`details <ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_user>`
+
+While these modules are mostly drop in place compatible there are some breaking changes that need to be considered. See each module entry for more information.
+
+.. _ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain:
+
+Module ``win_domain``
+---------------------
+
+Migrated to :ref:`microsoft.ad.domain <ansible_collections.microsoft.ad.domain_module>`.
+
+There are no known breaking changes and should work as a drop in replacement. The ``reboot`` option has been added to have the module handle any reboots that are needed instead of a separate ``ansible.windows.win_reboot`` task. Due to the operations involved with promoting a domain controller, it is highly recommended to use this option.
+
+.. _ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_controller:
+
+Module ``win_domain_controller``
+--------------------------------
+
+Migrated to :ref:`microsoft.ad.domain_controller <ansible_collections.microsoft.ad.domain_controller_module>`.
+
+The following options have been removed:
+
+* ``log_path`` - Creating a debug log of module actions is not supported
+
+The ``reboot`` option has been added to have the module handle any reboots instead of a separate ``ansible.windows.win_reboot`` task. Due to the operations involved with promoting a domain controller, it is highly recommended to use this option.
+
+.. _ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_membership:
+
+Module ``win_domain_membership``
+--------------------------------
+
+Migrated to :ref:`microsoft.ad.membership <ansible_collections.microsoft.ad.membership_module>`.
+
+The following options have been removed:
+
+* ``log_path`` - Creating a debug log of module actions is not supported
+
+The ``reboot`` option has been added to have the module handle any reboots instead of a separate ``ansible.windows.win_reboot`` task.
+
+.. _ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_computer:
+
+Module ``win_domain_computer``
+------------------------------
+
+Migrated to :ref:`microsoft.ad.computer <ansible_collections.microsoft.ad.computer_module>`.
+
+The option ``dns_host_name`` is not required when ``state: present``, the computer object is created without the ``dnsHostName`` LDAP attribute set if it is not defined.
+
+The default for ``enabled`` is nothing, the group will still be enabled when created but it will use the existing status if the option is omitted.
+
+The option ``ou`` is now named ``path`` to match the standard set by other modules.
+
+The options ``offline_domain_join`` and ``odj_blob_path`` has been removed. Use the new module ``microsoft.ad.offline_join`` to generate the offline join blob. For example:
+
+.. code-block:: yaml
+
+ - name: create computer object
+ microsoft.ad.computer:
+ name: MyComputer
+ state: present
+ register: computer_obj
+
+ - name: create offline blob
+ microsoft.ad.offline_join:
+ identity: '{{ computer_obj.object_guid }}'
+ when: computer_obj is changed
+ register: offline_blob
+
+ - name: display offline blob
+ debug:
+ var: offline_blob.blob
+ when: computer_obj is changed
+
+.. _ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_group:
+
+Module ``win_domain_group``
+---------------------------
+
+Migrated to :ref:`microsoft.ad.group <ansible_collections.microsoft.ad.group_module>`.
+
+The following options have changed:
+
+* ``attributes`` - changed format as outlined in :ref:`Attributes guid <ansible_collections.microsoft.ad.docsite.guide_attributes>`
+* ``ignore_protection`` - Has been removed and ``state: absent`` will also remove objects regardless of the protection status
+* ``organizational_unit`` and ``ou`` - Have been removed, use ``path`` instead
+* ``protect`` - Has been renamed to ``protect_from_deletion`` and is now not needed to be unset for ``state: absent`` to remove the group
+
+The return values for ``win_domain_group`` have also been simplified to only return:
+
+* ``distinguished_name`` - The Distinguished Name (``DN``) of the managed OU
+* ``object_guid`` - The Object GUID of the managed OU
+* ``sid`` - The Security Identifier of the managed user
+
+All other return values have been removed, use ``microsoft.ad.object_info`` to get extra values if needed.
+
+.. _ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_group_membership:
+
+Module ``win_domain_group_membership``
+--------------------------------------
+
+Migrated to :ref:`microsoft.ad.group <ansible_collections.microsoft.ad.group_module>`.
+
+The functionality of this module has been merged with ``microsoft.ad.group``. Use the ``members`` option to ``add``, ``remove``, or ``set`` to add, remove, or set group members respectively.
+
+.. _ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_object_info:
+
+Module ``win_domain_object_info``
+---------------------------------
+
+Migrated to :ref:`microsoft.ad.object_info <ansible_collections.microsoft.ad.object_info_module>`.
+
+There are no known breaking changes and should work as a drop in replacement.
+
+.. _ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_ou:
+
+Module ``win_domain_ou``
+------------------------
+
+Migrated to :ref:`microsoft.ad.ou <ansible_collections.microsoft.ad.ou_module>`.
+
+The following options have changed:
+
+* ``protected`` - Has been renamed to ``protect_from_deletion`` and is now not needed to be unset for ``state: absent`` to remove the OU
+* ``recursive`` - Has been removed and ``state: absent`` will also remove objects recursively
+* ``filter`` - Has been removed, the ``name`` object refers to the OU name and ``identity`` can be used to select the OU by ``DistinguishedName`` or ``ObjectGUID`` if a rename or move is needed
+* ``properties`` - Has been removed, use the new ``attributes`` option
+
+The return values for ``win_domain_ou`` have also been simplified to only return:
+
+* ``distinguished_name`` - The Distinguished Name (``DN``) of the managed OU
+* ``object_guid`` - The Object GUID of the managed OU
+
+All other return values have been removed, use ``microsoft.ad.object_info`` to get extra values if needed.
+
+.. _ansible_collections.microsoft.ad.docsite.guide_migration.migrated_modules.win_domain_user:
+
+Module ``win_domain_user``
+--------------------------
+
+Migrated to :ref:`microsoft.ad.user <ansible_collections.microsoft.ad.user_module>`.
+
+The following options have changed:
+
+* ``attributes`` - changed format as outlined in :ref:`Attributes guid <ansible_collections.microsoft.ad.docsite.guide_attributes>`
+* ``delegates`` - changed format as outlined in :ref:`Setting list values <ansible_collections.microsoft.ad.docsite.guide_list_values>`
+* ``groups`` - changed format as outlined in :ref:`Setting list values <ansible_collections.microsoft.ad.docsite.guide_list_values>`
+* ``groups_action`` - has been removed in favour of the new ``groups`` format
+* ``groups_missing_behaviour`` - has been moved into the ``group`` dictionary value as ``missing_behaviour``
+* ``spn``- changed format as outlined in :ref:`Setting list values <ansible_collections.microsoft.ad.docsite.guide_list_values>`
+* ``spn_action`` - has been removed in favour of the new ``spn`` format
+* ``state`` - No query option - use ``microsoft.ad.object_info`` instead
+* ``enabled`` - Does not default to ``true``. Creating a new user without a password will use ``enabled=false`` but setting a password will use ``enabled=true``
+
+The ``groups_action`` and ``spn_action`` ``set`` value was renamed to align with common practice. The ``state=query`` functionality has been removed to simplify the module and favour ``microsoft.ad.object_info`` which is designed to return information about AD objects. The ``enabled`` default was removed to allow setting other attributes on an existing AD object without always having to specify ``enabled``.
+
+The return values for ``win_domain_user`` have also been simplified to only return:
+
+* ``distinguished_name`` - The Distinguished Name (``DN``) of the managed user
+* ``object_guid`` - The Object GUID of the managed user
+* ``sid`` - The Security Identifier of the managed user
+
+All other return values have been removed, use ``microsoft.ad.object_info`` to get extra values if needed.