Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature group report #97

Merged
merged 31 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b669251
Create assignment_report.py
almenscorner Mar 6, 2023
0cfe00a
Update backup_appConfiguration.py
almenscorner Mar 6, 2023
0a53152
Add groupTypes and membershipRule
almenscorner Mar 6, 2023
96cbe2f
Add assignment report creation
almenscorner Mar 6, 2023
b37d89e
Add --create-groups argument
almenscorner Mar 6, 2023
58e68fb
Change indentation for json
almenscorner Mar 6, 2023
bc28a38
Add create_groups check
almenscorner Mar 6, 2023
b7fe905
Add create_groups check
almenscorner Mar 6, 2023
9cd0c1c
Add create_groups check
almenscorner Mar 6, 2023
467da67
Add create_groups check
almenscorner Mar 6, 2023
378e83e
Add create_groups check
almenscorner Mar 6, 2023
00889ab
Add create_groups check
almenscorner Mar 6, 2023
78a9cdc
Add create_groups check
almenscorner Mar 6, 2023
4cf8c59
Add create_groups check
almenscorner Mar 6, 2023
c80ca53
Add create_groups check
almenscorner Mar 6, 2023
0536d00
Add create_groups check
almenscorner Mar 6, 2023
8f9daa5
Add create_groups check
almenscorner Mar 6, 2023
b847449
Add create_groups check
almenscorner Mar 6, 2023
87955b6
Add create_groups check and bug fix for updating assignment
almenscorner Mar 6, 2023
e88b3dc
Add step to create non existing groups
almenscorner Mar 6, 2023
4a730d3
Add platform check for windows split and abs path
almenscorner Mar 6, 2023
64c0f29
Bug fix for getting powershell assignment
almenscorner Mar 7, 2023
07fa5c1
Bug fix for getting shell script
almenscorner Mar 7, 2023
767d245
Get assignments expand url
almenscorner Mar 7, 2023
39debab
Add update frontend with assignment report
almenscorner Mar 7, 2023
95e62d0
Remove intents as separate collect
almenscorner Mar 8, 2023
f48054d
Update with new group params
almenscorner Mar 8, 2023
a1c8355
Create test_group_report.py
almenscorner Mar 8, 2023
861ae38
Add create groups arg
almenscorner Mar 8, 2023
fee30f0
Update with update_assignment value
almenscorner Mar 8, 2023
7b62f21
Remove beta flag
almenscorner Mar 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = IntuneCD
version = 1.3.5
version = 1.4.0
author = Tobias Almén
author_email = [email protected]
description = Tool to backup and update configurations in Intune
Expand Down
98 changes: 98 additions & 0 deletions src/IntuneCD/assignment_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python3

"""
This module creates a report of all groups found and their assginment.
"""

import os
import platform

from .save_output import save_output
from .check_file import check_file
from .load_file import load_file


def get_group_report(path, output):
"""
This function is used to get a report of all groups and what they are assigned to.

Args:
path (str): Path to save the report to
output (str): Output format for the report
"""

report_path = f"{path}/Assignment Report/"

def process_file(path, name, payload_type, groups):
file_check = check_file(path, name)
if file_check:
with open(os.path.join(path, name), "r") as f:
data = load_file(name, f)
if type(data) is dict and data.get("assignments"):
for assignment in data["assignments"]:
if assignment["target"].get("groupName"):
if data.get("displayName"):
name = data["displayName"]
elif data.get("name"):
name = data["name"]
data = {
"groupName": assignment["target"]["groupName"],
"groupType": assignment["target"].get("groupType"),
"membershipRule": assignment["target"].get(
"membershipRule", None
),
"assignedTo": {},
}

payload_added = False # flag to track whether payload_type has been added

if not groups:
groups.append(data)
data["assignedTo"][payload_type] = [name]
payload_added = True
else:
for item in groups:
if item["groupName"] == data["groupName"]:
if not payload_added and item["assignedTo"].get(
payload_type
):
item["assignedTo"][payload_type].append(
name
)
payload_added = True
elif not payload_added and not item[
"assignedTo"
].get(payload_type):
item["assignedTo"][payload_type] = [name]
payload_added = True

if not payload_added:
data["assignedTo"][payload_type] = [name]
groups.append(data)

def collect_groups(path):
groups = []
slash = "/"
run_os = platform.uname().system
if run_os == "Windows":
slash = "\\"
abs_path = os.path.abspath(path)
for root, dirs, files in os.walk(path, topdown=True):
abs_root = os.path.abspath(root)
for file in files:
os.path.abspath(root)
payload_type = abs_root.replace(abs_path, "").split(slash)
if len(payload_type) > 1:
payload_type = payload_type[1]
process_file(
str(root),
file,
payload_type,
groups,
)
return groups

groups = collect_groups(path)

if groups:
save_output(output, report_path, "report", groups)
13 changes: 9 additions & 4 deletions src/IntuneCD/backup_appConfiguration.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
from .remove_keys import remove_keys

# Set MS Graph endpoint
ENDPOINT = "https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations"
ENDPOINT = (
"https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations"
)
APP_ENDPOINT = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps"


Expand All @@ -31,8 +33,9 @@ def savebackup(path, output, exclude, token):
data = makeapirequest(ENDPOINT, token)

if data["value"]:

assignment_responses = batch_assignment(data, "deviceAppManagement/mobileAppConfigurations/", "/assignments", token)
assignment_responses = batch_assignment(
data, "deviceAppManagement/mobileAppConfigurations/", "/assignments", token
)

for profile in data["value"]:
config_count += 1
Expand All @@ -58,7 +61,9 @@ def savebackup(path, output, exclude, token):
print("Backing up App Configuration: " + profile["displayName"])

# Get filename without illegal characters
fname = clean_filename(f"{profile['displayName']}_{str(profile['@odata.type'].split('.')[2])}")
fname = clean_filename(
f"{profile['displayName']}_{str(profile['@odata.type'].split('.')[2])}"
)
# Save App Configuration as JSON or YAML depending on configured value
# in "-o"
save_output(output, configpath, fname, profile)
Expand Down
12 changes: 9 additions & 3 deletions src/IntuneCD/backup_powershellScripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,19 @@ def savebackup(path, output, exclude, token):
for script in data["value"]:
script_ids.append(script["id"])

assignment_responses = batch_assignment(data, "deviceManagement/intents/", "/assignments", token)
script_data_responses = batch_request(script_ids, "deviceManagement/deviceManagementScripts/", "", token)
assignment_responses = batch_assignment(
data, "deviceManagement/deviceManagementScripts/", "/assignments", token
)
script_data_responses = batch_request(
script_ids, "deviceManagement/deviceManagementScripts/", "", token
)

for script_data in script_data_responses:
config_count += 1
if "assignments" not in exclude:
assignments = get_object_assignment(script_data["id"], assignment_responses)
assignments = get_object_assignment(
script_data["id"], assignment_responses
)
if assignments:
script_data["assignments"] = assignments

Expand Down
12 changes: 9 additions & 3 deletions src/IntuneCD/backup_shellScripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

# Set MS Graph endpoint
ENDPOINT = "https://graph.microsoft.com/beta/deviceManagement/deviceShellScripts/"
ASSIGNMENT_ENDPOINT = "https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts"
ASSIGNMENT_ENDPOINT = (
"https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts"
)


# Get all Shell scripts and save them in specified path
Expand All @@ -36,8 +38,12 @@ def savebackup(path, output, exclude, token):
for script in data["value"]:
script_ids.append(script["id"])

assignment_responses = batch_assignment(data, "deviceManagement/deviceManagementScripts/", "/assignments", token)
script_data_responses = batch_request(script_ids, "deviceManagement/deviceShellScripts/", "", token)
assignment_responses = batch_assignment(
data, "deviceManagement/deviceShellScripts/", "?$expand=assignments", token
)
script_data_responses = batch_request(
script_ids, "deviceManagement/deviceShellScripts/", "", token
)

for script_data in script_data_responses:
config_count += 1
Expand Down
94 changes: 80 additions & 14 deletions src/IntuneCD/graph_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ def batch_request(data, url, extra_url, token, method="GET") -> list:

# POST to the graph batch endpoint
json_data = json.dumps(query_data)
request = makeapirequestPost("https://graph.microsoft.com/beta/$batch", token, jdata=json_data)
request = makeapirequestPost(
"https://graph.microsoft.com/beta/$batch", token, jdata=json_data
)
request_data = sorted(request["responses"], key=lambda item: item.get("id"))

# Append each successful request to responses list
Expand Down Expand Up @@ -69,9 +71,15 @@ def batch_assignment(data, url, extra_url, token, app_protection=False) -> list:
# If getting App Protection Assignments, get the platform
if app_protection is True:
for id in data["value"]:
if id["@odata.type"] == "#microsoft.graph.mdmWindowsInformationProtectionPolicy":
if (
id["@odata.type"]
== "#microsoft.graph.mdmWindowsInformationProtectionPolicy"
):
data_ids.append(f"mdmWindowsInformationProtectionPolicies/{id['id']}")
if id["@odata.type"] == "#microsoft.graph.windowsInformationProtectionPolicy":
if (
id["@odata.type"]
== "#microsoft.graph.windowsInformationProtectionPolicy"
):
data_ids.append(f"windowsInformationProtectionPolicies/{id['id']}")
else:
data_ids.append(f"{str(id['@odata.type']).split('.')[2]}s/{id['id']}")
Expand All @@ -83,6 +91,17 @@ def batch_assignment(data, url, extra_url, token, app_protection=False) -> list:
if data_ids:
responses = batch_request(data_ids, url, extra_url, token)
if responses:
if extra_url == "?$expand=assignments":
response_values = []
for value in responses:
response_values.append(
{
"value": value["assignments"],
"@odata.context": value["[email protected]"],
}
)
responses = response_values

group_ids = [
val
for list in responses
Expand All @@ -105,25 +124,49 @@ def batch_assignment(data, url, extra_url, token, app_protection=False) -> list:

# Batch get name of the groups
if group_ids:
group_responses = batch_request(group_ids, "groups/", "?$select=displayName,id", token)
group_responses = batch_request(
group_ids,
"groups/",
"?$select=displayName,id,groupTypes,membershipRule",
token,
)
for value in responses:
if value["value"]:
for val in value["value"]:
if "groupId" in val["target"]:
for id in group_responses:
if id["id"] == val["target"]["groupId"]:
val["target"]["groupName"] = id["displayName"]
if "DynamicMembership" in id["groupTypes"]:
val["target"]["groupType"] = "DynamicMembership"
val["target"]["membershipRule"] = id[
"membershipRule"
]
else:
val["target"]["groupType"] = "StaticMembership"

# Batch get name of the Filters
if filter_ids:
filter_responses = batch_request(filter_ids, "deviceManagement/assignmentFilters/", "?$select=displayName", token)
filter_responses = batch_request(
filter_ids,
"deviceManagement/assignmentFilters/",
"?$select=displayName",
token,
)
for value in responses:
if value["value"]:
for val in value["value"]:
if "deviceAndAppManagementAssignmentFilterId" in val["target"]:
for id in filter_responses:
if id["id"] == val["target"]["deviceAndAppManagementAssignmentFilterId"]:
val["target"]["deviceAndAppManagementAssignmentFilterId"] = id["displayName"]
if (
id["id"]
== val["target"][
"deviceAndAppManagementAssignmentFilterId"
]
):
val["target"][
"deviceAndAppManagementAssignmentFilterId"
] = id["displayName"]

return responses

Expand All @@ -145,20 +188,28 @@ def batch_intents(data, token) -> dict:
intent_values = {"value": []}

# Get each template ID
filtered_data = [val for list in data["value"] for key, val in list.items() if "templateId" in key and val is not None]
filtered_data = [
val
for list in data["value"]
for key, val in list.items()
if "templateId" in key and val is not None
]
template_ids = list(dict.fromkeys(filtered_data))

# Batch get all categories from templates
if template_ids:
categories_responses = batch_request(template_ids, f"{base_url}/templates/", "/categories", token)
categories_responses = batch_request(
template_ids, f"{base_url}/templates/", "/categories", token
)

# Build ID for requesting settings for each Intent
if categories_responses:
for intent in data["value"]:
settings_ids = [
val
for list in categories_responses
if intent["templateId"] is not None and intent["templateId"] in list["@odata.context"]
if intent["templateId"] is not None
and intent["templateId"] in list["@odata.context"]
for val in list["value"]
for keys, val in val.items()
if "id" in keys
Expand All @@ -168,13 +219,18 @@ def batch_intents(data, token) -> dict:

# Batch get all settings for all Intents
if settings_id:
settings_responses = batch_request(settings_id, f"{base_url}/intents/", "/settings", token)
settings_responses = batch_request(
settings_id, f"{base_url}/intents/", "/settings", token
)

# If the Intent ID is in the responses, save the settings to settingsDelta for the Intent
if settings_responses:
for intent in data["value"]:
settingsDelta = [
val for list in settings_responses if intent["id"] in list["@odata.context"] for val in list["value"]
val
for list in settings_responses
if intent["id"] in list["@odata.context"]
for val in list["value"]
]
intent_values["value"].append(
{
Expand All @@ -200,7 +256,12 @@ def get_object_assignment(id, responses) -> list:
"""

remove_keys = {"id", "groupId", "sourceId"}
assignments_list = [val for list in responses if id in list["@odata.context"] for val in list["value"]]
assignments_list = [
val
for list in responses
if id in list["@odata.context"]
for val in list["value"]
]
for value in assignments_list:
for k in remove_keys:
value.pop(k, None)
Expand All @@ -218,5 +279,10 @@ def get_object_details(id, responses) -> list:
:return: List of details for the object
"""

details = [val for list in responses if id in list["@odata.context"] for val in list["value"]]
details = [
val
for list in responses
if id in list["@odata.context"]
for val in list["value"]
]
return details
Loading