Skip to content

[BUG] Issue with saving files with special characters #268

Description

@Odnel

Describe the bug
Whenever filename or directory contains any of those characters: / \ : * ? < > " | the IntuneCD throws error "[WinError 267] The directory name is invalid or OSError: Invalid argument" (From error I am assuming this issue happens only on Windows, but cannot test it on other enviroment). This behavior causes the script to abort the whole module and skips to the next one. Does not perform backup of following objects in this module.

To Reproduce
Steps to reproduce the behavior: Create a policy containing one of mentioned characters and run backup on Windows.

Expected behavior
A clear and concise description of what you expected to happen.
The error is understandable as this is OS-level issue. The behavior that I would expect is not to abort the whole module when error is thrown.

Screenshots
If applicable, add screenshots to help explain your problem.

Run type (please complete the following information):

  • Mode: [e.g. 0 or 1] 0
  • Client [e.g. Pipeline, local machine] local machine
  • Version [e.g. 1.0.2] 2.6.0

Additional context
We were able to fix it in our enviromeny using AI. Below you can find the output of the changes made:

Changes Made

Change 1: Applications.py_save_script_win32() method

Before:

if item.get(f"{rule_type}Rules"):
    for rule in item[f"{rule_type}Rules"]:
        if rule.get("scriptContent"):
            if self.append_id:
                script_name = (
                    f"{item['displayName']}_{rule_type}Script__{item['id']}.ps1"
                )
            else:
                script_name = f"{item['displayName']}_{rule_type}Script.ps1"
            if not os.path.exists(script_data_path):
                os.makedirs(script_data_path)
            decoded = self.decode_base64(rule["scriptContent"])
            f = open(
                f"{script_data_path}{script_name}",
                "w",
                encoding="utf-8",
            )
            f.write(decoded)

After:

if item.get(f"{rule_type}Rules"):
    for rule in item[f"{rule_type}Rules"]:
        if rule.get("scriptContent"):
            display_name = self._prepare_file_name(item["displayName"])
            if self.append_id:
                script_name = (
                    f"{display_name}_{rule_type}Script__{item['id']}.ps1"
                )
            else:
                script_name = f"{display_name}_{rule_type}Script.ps1"
            if not os.path.exists(script_data_path):
                os.makedirs(script_data_path)
            decoded = self.decode_base64(rule["scriptContent"])
            with open(
                f"{script_data_path}{script_name}",
                "w",
                encoding="utf-8",
            ) as f:
                f.write(decoded)

What changed: Added self._prepare_file_name() to sanitize displayName. Replaced bare open()/write() with with open() to fix a file handle leak.


Change 2: Applications.py_save_script_mac() method

Before:

if item.get(f"{script_type}InstallScript"):
    if item[f"{script_type}InstallScript"].get("scriptContent"):
        if self.append_id:
            script_name = f"{item['displayName']}_{script_type}InstallScript__{item['id']}.sh"
        else:
            script_name = f"{item['displayName']}_{script_type}InstallScript.sh"
        ...
        f = open(
            f"{script_data_path}{script_name}",
            "w",
            encoding="utf-8",
        )
        f.write(decoded)

After:

if item.get(f"{script_type}InstallScript"):
    if item[f"{script_type}InstallScript"].get("scriptContent"):
        display_name = self._prepare_file_name(item["displayName"])
        if self.append_id:
            script_name = f"{display_name}_{script_type}InstallScript__{item['id']}.sh"
        else:
            script_name = f"{display_name}_{script_type}InstallScript.sh"
        ...
        with open(
            f"{script_data_path}{script_name}",
            "w",
            encoding="utf-8",
        ) as f:
            f.write(decoded)

What changed: Same as above — sanitization added, file handle leak fixed.


Change 3: ComplianceScripts.py_save_script() method

Before:

if item.get("detectionScriptContent"):
    if self.append_id:
        script_name = (
            f"{item['displayName'].replace('.ps1', '')}__{item['id']}.ps1"
        )
    else:
        script_name = f"{item['displayName'].replace('.ps1', '')}.ps1"
    ...
    f = open(
        f"{self.script_data_path}{script_name}",
        "w",
        encoding="utf-8",
    )
    f.write(decoded)

After:

if item.get("detectionScriptContent"):
    script_name_base = self._prepare_file_name(item['displayName'].replace('.ps1', ''))
    if self.append_id:
        script_name = f"{script_name_base}__{item['id']}.ps1"
    else:
        script_name = f"{script_name_base}.ps1"
    ...
    with open(
        f"{self.script_data_path}{script_name}",
        "w",
        encoding="utf-8",
    ) as f:
        f.write(decoded)

What changed: Added _prepare_file_name() sanitization. Replaced bare open() with with open().


Change 4: ComplianceScripts.pymain() method, script saving loop

Before:

for item in script_data_responses:
    self._save_script(item)

After:

for item in script_data_responses:
    try:
        self._save_script(item)
    except Exception as e:
        self.log(tag="error", msg=f"Error saving script for {item.get('displayName', 'unknown')}: {e}")

What changed: Added error isolation so one failed script save doesn't abort the loop.


Change 5: DeviceConfigurations.py — mobileconfig file saving

Before:

f = open(
    self.path + "mobileconfig/" + item["payloadFileName"],
    "w",
    encoding="utf-8",
)

After:

f = open(
    self.path + "mobileconfig/" + self._prepare_file_name(item["payloadFileName"]),
    "w",
    encoding="utf-8",
)

What changed: Added _prepare_file_name() to sanitize payloadFileName from the API response.


Change 6: ReusableSettings.py_save_script() method

Before:

if self.append_id:
    script_name = f"{item['displayName'].replace('.sh', '')}__{item['id']}.sh"
else:
    script_name = f"{item['displayName'].replace('.sh', '')}.sh"

After:

if self.append_id:
    script_name = f"{self._prepare_file_name(item['displayName'].replace('.sh', ''))}__{item['id']}.sh"
else:
    script_name = f"{self._prepare_file_name(item['displayName'].replace('.sh', ''))}.sh"

What changed: Added _prepare_file_name() sanitization.


Change 7: ManagementIntents.py — template name used as directory path

Before:

self.path = f"{self.path}{template_type}/"

After:

self.path = f"{self.path}{self._prepare_file_name(template_type)}/"

What changed: Added _prepare_file_name() to sanitize the template display name used as a subdirectory. This was the direct cause of the [WinError 267] error with "Preview: MDM Security Baseline...".


Change 8: BaseBackupModule.py_process_multiple_items() loop

Before:

results = {"config_count": 0, "outputs": []}
for item in data:
    item_results = self._process_single_item(
        item,
        filetype,
        path,
        name_key,
        log_message,
        audit_compare_info,
        assignment_responses,
    )
    results["config_count"] += item_results["config_count"]
    results["outputs"].extend(item_results["outputs"])
 
return results

After:

results = {"config_count": 0, "outputs": []}
for item in data:
    try:
        item_results = self._process_single_item(
            item,
            filetype,
            path,
            name_key,
            log_message,
            audit_compare_info,
            assignment_responses,
        )
        results["config_count"] += item_results["config_count"]
        results["outputs"].extend(item_results["outputs"])
    except Exception as e:
        item_name = item.get(name_key, item.get("id", "unknown"))
        self.log(
            tag="error",
            msg=f"Error processing item '{item_name}': {e}",
        )
 
return results

What changed: Wrapped the loop body in try/except. A single item failure is now logged as an error but does not prevent the remaining items in the module from being backed up.


Files Modified

File Changes
IntuneCD/backup/Intune/Applications.py Sanitization in _save_script_win32() and _save_script_mac(), file handle leak fix
IntuneCD/backup/Intune/ComplianceScripts.py Sanitization in _save_script(), error isolation in script loop
IntuneCD/backup/Intune/DeviceConfigurations.py Sanitization of payloadFileName
IntuneCD/backup/Intune/ReusableSettings.py Sanitization in _save_script()
IntuneCD/backup/Intune/ManagementIntents.py Sanitization of template name used as directory
IntuneCD/intunecdlib/BaseBackupModule.py Error isolation in _process_multiple_items() loop

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions