How can I search json output from tasks

Viewed 94

I have a playbook pulling users and groups from windows 2012 using powershell and I convert it to json.

I get the following output

[
    {
        "Group": "Appsupp",
        "Members": [
            "WIN-U97DIQUENUM\\appsuport",
            "WIN-U97DIQUENUM\\userfirst"
        ]
    },
    {
        "Group": "DBAdministrators",
        "Members": {}
    },
    {
        "Group": "Techsupp",
        "Members": {}
    },
    {
        "Group": "Access Control Assistance Operators",
        "Members": {}
    },
    {
        "Group": "Administrators",
        "Members": "WIN-U97DIQUENUM\\Administrator"
    },
    {
        "Group": "Backup Operators",
        "Members": {}
    },
    {
        "Group": "Certificate Service DCOM Access",
        "Members": {}
    },
    {
        "Group": "Cryptographic Operators",
        "Members": {}
    },
    {
        "Group": "Distributed COM Users",
        "Members": {}
    },
    {
        "Group": "Event Log Readers",
        "Members": {}
    },
    {
        "Group": "Guests",
        "Members": "WIN-U97DIQUENUM\\Guest"
    },
    {
        "Group": "Hyper-V Administrators",
        "Members": {}
    },
    {
        "Group": "IIS_IUSRS",
        "Members": "NT AUTHORITY\\IUSR"
    },
    {
        "Group": "Network Configuration Operators",
        "Members": {}
    },
    {
        "Group": "Performance Log Users",
        "Members": {}
    },
    {
        "Group": "Performance Monitor Users",
        "Members": {}
    },
    {
        "Group": "Power Users",
        "Members": {}
    },
    {
        "Group": "Print Operators",
        "Members": {}
    },
    {
        "Group": "RDS Endpoint Servers",
        "Members": {}
    },
    {
        "Group": "RDS Management Servers",
        "Members": {}
    },
    {
        "Group": "RDS Remote Access Servers",
        "Members": {}
    },
    {
        "Group": "Remote Desktop Users",
        "Members": {}
    },
    {
        "Group": "Remote Management Users",
        "Members": {}
    },
    {
        "Group": "Replicator",
        "Members": {}
    },
    {
        "Group": "Users",
        "Members": [
            "NT AUTHORITY\\Authenticated Users",
            "NT AUTHORITY\\INTERACTIVE",
            "WIN-U97DIQUENUM\\userfirst",
            "WIN-U97DIQUENUM\\usersecond",
            "WIN-U97DIQUENUM\\appsupport",
            "WIN-U97DIQUENUM\\techsupport",
            "WIN-U97DIQUENUM\\sqlserveruser"
        ]
    }
]

I would like to have user compared to that data I pulled from the server. For example, I want to know if appsupport user exists and which group it belongs to.

I have tried the below json_query. FYI, getgroup.stdout is the registered result from the task pulling info from the server.

    - debug:
        msg: "{{ getgroup.stdout | from_json | json_query('guser') }}"
      vars:
        guser: "[?Members.contains(@, `appsupport`)].Group"

What I get is an empty result (while this method worked with other json). What am I doing wrong?

I assume that the problem is the Members key in my object as it my contain an empty dictionnary, a single string or a list of strings as recapped in the chosen examples below.

Single string:

{
    "Group": "IIS_IUSRS",
    "Members": "NT AUTHORITY\\IUSR"
},

List:

{
    "Group": "Users",
    "Members": [
        "NT AUTHORITY\\Authenticated Users",
        "NT AUTHORITY\\INTERACTIVE",
        "WIN-U97DIQUENUM\\userfirst",
        "WIN-U97DIQUENUM\\usersecond",
        "WIN-U97DIQUENUM\\appsupport",
        "WIN-U97DIQUENUM\\techsupport",
        "WIN-U97DIQUENUM\\sqlserveruser"
    ]
}

Empty dict:

{
    "Group": "Replicator",
    "Members": {}
}

When I use json_query('[].Members[*]') it only returns the latest member, not all Members.

How can I select the objects from my json result having a particular user in the Members field in this kind of situation?

2 Answers

Without json_query.

For testing, given the output in the file, read it and convert the Members to lists, e.g.

    - set_fact:
        output: "{{ lookup('file', 'test.data')|from_yaml }}"
    - set_fact:
        mlist: "{{ mlist|d([]) +
                   [item|combine({'Members': _Members|from_yaml})] }}"
      loop: "{{ output }}"
      vars:
        _Members: |-
          {% if item.Members is mapping %}
          {{ item.Members.keys()|list }}
          {% elif item.Members is string %}
          [{{ item.Members }}]
          {% else %}
          {{ item.Members }}
          {% endif %}
    - debug:
        var: mlist

gives

  mlist:
  - Group: Appsupp
    Members:
    - WIN-U97DIQUENUM\appsuport
    - WIN-U97DIQUENUM\userfirst
  - Group: DBAdministrators
    Members: []
  - Group: Techsupp
    Members: []
  - Group: Access Control Assistance Operators
    Members: []
  - Group: Administrators
    Members:
    - WIN-U97DIQUENUM\Administrator
  - Group: Backup Operators
    Members: []
  - Group: Certificate Service DCOM Access
    Members: []
  - Group: Cryptographic Operators
    Members: []
  - Group: Distributed COM Users
    Members: []
  - Group: Event Log Readers
    Members: []
  - Group: Guests
    Members:
    - WIN-U97DIQUENUM\Guest
  - Group: Hyper-V Administrators
    Members: []
  - Group: IIS_IUSRS
    Members:
    - NT AUTHORITY\IUSR
  - Group: Network Configuration Operators
    Members: []
  - Group: Performance Log Users
    Members: []
  - Group: Performance Monitor Users
    Members: []
  - Group: Power Users
    Members: []
  - Group: Print Operators
    Members: []
  - Group: RDS Endpoint Servers
    Members: []
  - Group: RDS Management Servers
    Members: []
  - Group: RDS Remote Access Servers
    Members: []
  - Group: Remote Desktop Users
    Members: []
  - Group: Remote Management Users
    Members: []
  - Group: Replicator
    Members: []
  - Group: Users
    Members:
    - NT AUTHORITY\Authenticated Users
    - NT AUTHORITY\INTERACTIVE
    - WIN-U97DIQUENUM\userfirst
    - WIN-U97DIQUENUM\usersecond
    - WIN-U97DIQUENUM\appsupport
    - WIN-U97DIQUENUM\techsupport
    - WIN-U97DIQUENUM\sqlserveruser

Now, you can search for the members, e.g.

    - debug:
        msg: "{{ mlist|selectattr('Members', 'contains', my_user) }}"
      vars:
        my_user: 'WIN-U97DIQUENUM\Administrator'

gives

  msg:
  - Group: Administrators
    Members:
    - WIN-U97DIQUENUM\Administrator

Next, create a dictionary of all members and the groups they belong to

    - set_fact:
        members: "{{ members|d({})|combine({item: _groups}) }}"
      loop: "{{ mlist|map(attribute='Members')|flatten|unique }}"
      vars:
        _groups: "{{ mlist|selectattr('Members', 'contains', item)|
                          map(attribute='Group')|
                          list }}"
    - debug:
        var: members

gives

  members:
    NT AUTHORITY\Authenticated Users:
    - Users
    NT AUTHORITY\INTERACTIVE:
    - Users
    NT AUTHORITY\IUSR:
    - IIS_IUSRS
    WIN-U97DIQUENUM\Administrator:
    - Administrators
    WIN-U97DIQUENUM\Guest:
    - Guests
    WIN-U97DIQUENUM\appsuport:
    - Appsupp
    WIN-U97DIQUENUM\appsupport:
    - Users
    WIN-U97DIQUENUM\sqlserveruser:
    - Users
    WIN-U97DIQUENUM\techsupport:
    - Users
    WIN-U97DIQUENUM\userfirst:
    - Appsupp
    - Users
    WIN-U97DIQUENUM\usersecond:
    - Users

Then, the searching is trivial, e.g.

    - debug:
        msg: "{{ my_user }} is member of the group(s): {{ members[my_user] }}"
      vars:
        my_user: 'WIN-U97DIQUENUM\Administrator'

gives

  msg: 'WIN-U97DIQUENUM\Administrator is member of the group(s): [''Administrators'']'

It's possible to search among the members, e.g.

    - debug:
        msg: "{{ my_user }} is member of the group(s): {{ _groups }}"
      vars:
        my_user: appsuport
        _keys: "{{ members.keys()|list|select('search', my_user)|list }}"
        _groups: "{{ _keys|map('extract', members)|flatten }}"

gives

  msg: 'appsuport is member of the group(s): [''Appsupp'']'

I see multiple problems here:

Problem 1:
Your data has three different types for Members, depending on what is in there:

  1. An empty dict ({}) if there are no members
  2. A string, if there is exactly one member
  3. A list of strings, if there are multiple members

As far as I can see, this will not break, but it is not particularly good style. You should always return a list. An empty one, if there is no member, or a list with one element if there is exactly one member or a list of multiple strings, as you already do, if there are multiple members.

Problem 2:
In group Appsupp, the user is misspelled: WIN-U97DIQUENUM\\appsuport (there is a 'p' missing in 'support'), so this will not match.

Problem 3:
This is what actually breaks here. Your query has several problems, it should look like this: [?Members[?contains(@, 'appsupport')]].Group

The result is

[
  "Users"
]

Appsupp is not in there due to the spelling mistake mentioned earlier.

You can test your json-path on this website.

Related