Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
282 changes: 247 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,261 @@
____ __ _____ __ ____ _____ _ __
/ _/___ / /___ ______ ___ / ___// /_ ___ / / / / ___/__________(_)___ / /______
/ // __ \/ __/ / / / __ \/ _ \ \__ \/ __ \/ _ \/ / / \__ \/ ___/ ___/ / __ \/ __/ ___/
_/ // / / / /_/ /_/ / / / / __/ ___/ / / / / __/ / / ___/ / /__/ / / / /_/ / /_(__ )
/___/_/ /_/\__/\__,_/_/ /_/\___/ /____/_/ /_/\___/_/_/ /____/\___/_/ /_/ .___/\__/____/
/_/
# mSCP CLI tool recommendation for Intune custom compliance

This repository is a **tool recommendation and integration guide**. It does not contain or claim to be the mSCP CLI. It recommends using the CLI from the official [NIST macOS Security Compliance Project](https://github.com/usnistgov/macos_security) to generate a macOS security baseline and discovery script for Microsoft Intune custom compliance.

# Intune Customer Experience Engineering Shell Script Repo
The workflow selects an mSCP baseline, uses the recommended CLI to generate the macOS discovery script that detects target identifiers, validates its JSON output, and creates the matching Intune rules JSON.

This repository is for Intune Shell Script Samples. It is maintained by the Microsoft Intune Customer Experience Engineering Team. The samples provided here are for education and showing the art of the possible. You are encouraged to download, test and enhance for your own use.
## Important compatibility note

- ***[Use shell scripts on macOS devices in Intune](https://learn.microsoft.com/mem/intune/apps/macos-shell-scripts)***
- ***[Use shell scripts on Linux devices in Intune](https://learn.microsoft.com/mem/intune/configuration/custom-settings-linux)***
- ***[Use custom compliance settings in Linux](https://learn.microsoft.com/mem/intune/protect/compliance-use-custom-settings)***
The recommended mSCP CLI generates the discovery script. The script detects target identifiers such as `system_settings_firewall_enable` and returns their current values. A separate Intune rules JSON file matches those identifiers and defines which values are compliant:

- It is a zsh audit/remediation script.
- It requires root.
- It writes audit results to local plist, log, and CSV files.
- The script can produce human-readable audit output or JSON discovery output, depending on the source revision.
- Intune requires one valid JSON object whose property names match the rules JSON `SettingName` values.

The Repo is split into scripts for Linux and macOS
This guide assumes that the checked-out mSCP source supports `--json`. Verify `./mscp.py guidance --help` and the generated script before deployment. If the checked-out official source does not provide JSON output, the generated audit script cannot yet be used directly as an Intune discovery script; use a reviewed JSON-output implementation before uploading it.

## Prerequisites

Run the generation and testing steps on a Mac:

- macOS supported by the selected mSCP rule set
- Python 3.12 or later
- administrator access for audit testing
- `/bin/zsh` and the macOS utilities used by the selected checks
- `/usr/bin/jq` if your JSON-output integration uses it
- an Intune tenant with permission to create macOS compliance policies

## Step 1: clone the official mSCP repository

```bash
git clone https://github.com/usnistgov/macos_security.git
cd macos_security
```

Use a tagged release or a reviewed commit for production instead of silently changing source versions.

## Step 2: create the Python environment

```bash
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install --upgrade pip
python3 -m pip install -e .
```

Confirm that the CLI starts:

```bash
./mscp.py --help
```

## Step 3: generate a baseline

Generate a CIS Level 1 baseline:

```bash
./mscp.py baseline -k cis_lvl1
```

The CLI searches the mSCP rule library for the `cis_lvl1` keyword and writes a YAML baseline similar to:

```text
custom/baselines/cis_lvl1_macos_26.0.yaml
```

The reported rule count is the number of selected rules. It is not a device compliance score.

You can inspect available baseline keywords before choosing one:

```bash
./mscp.py baseline --list_tags
```

## Step 4: generate the native mSCP compliance script

Use the baseline YAML as the positional argument and request script generation:

```bash
./mscp.py guidance \
custom/baselines/cis_lvl1_macos_26.0.yaml \
--script \
--no-docs
```

The short form is:

```bash
./mscp.py guidance -s custom/baselines/cis_lvl1_macos_26.0.yaml
```

The generated files are placed under a directory similar to:

```text
build/cis_lvl1_macos_26.0/
cis_lvl1_macos_26.0_compliance.sh
preferences/
org.cis_lvl1_macos_26.0.audit.plist
```

The script contains the check and remediation commands derived from the selected mSCP rules. `--no-docs` skips the AsciiDoc, PDF, and HTML guidance documents.

## Step 5: test the generated script safely

Run an audit-only check as root:

```bash
sudo ./build/cis_lvl1_macos_26.0/cis_lvl1_macos_26.0_compliance.sh \
--check \
--quiet=2
```

Do not use `--fix` or `--cfc` during Intune discovery testing. Those options can change device settings.

The native mSCP script records audit state in locations similar to:

```text
/Library/Preferences/org.cis_lvl1_macos_26.0.audit.plist
/Library/Logs/<hostname>_cis_lvl1_macos_26.0_baseline.log
/Library/Logs/<hostname>_cis_lvl1_macos_26.0_baseline.csv
```

Review the audit output and confirm that every selected check works on the target macOS version. Investigate empty values, command errors, and checks that report explanatory text instead of a simple value.

## Step 6: create the Intune rules JSON for the discovery identifiers

The discovery script returns target identifiers as JSON property names. Create an Intune rules JSON file that matches those identifiers exactly. It must contain a top-level `Rules` array. Each rule must map to a property returned by the discovery script:

- `SettingName` is case-sensitive and must match the discovered JSON property.
- `DataType` must match the returned type.
- `Operator` defines the comparison.
- `Operand` is the value considered compliant.
- `RemediationStrings` explains how to restore compliance.

Intune supports these operators:

```text
IsEquals
NotEquals
GreaterThan
GreaterEquals
LessThan
LessEquals
```

Example rule for a JSON property named `system_settings_firewall_enable`:

```json
{
"Rules": [
{
"SettingName": "system_settings_firewall_enable",
"Operator": "IsEquals",
"DataType": "Boolean",
"Operand": true,
"MoreInfoUrl": "https://github.com/usnistgov/macos_security",
"RemediationStrings": [
{
"Language": "en_US",
"Title": "The firewall must be enabled. Value discovered was {ActualValue}.",
"Description": "Enable the macOS firewall or apply the required management configuration."
}
]
}
]
}
```

The rules JSON matches the discovery identifiers; it does not run the checks. Build it from the reviewed mSCP rule metadata and the exact JSON output of the generated discovery script. Do not map a value such as `"FAIL RUNNING"` to a Boolean rule without explicitly normalizing it first.

## Step 7: validate discovery identifiers and JSON output

Intune macOS custom compliance requires the uploaded discovery script to return one valid JSON object. Confirm that the generated discovery script:

1. Runs the selected mSCP checks in read-only mode.
2. Returns every intended target identifier as a stable JSON property name.
3. Returns booleans, integers, versions, and strings using the types declared in the rules JSON.
4. JSON-escapes multiline and special-character values.
5. Writes diagnostic logs to stderr or a file, never into the JSON response on stdout.
6. Returns a nonzero exit code when the discovery operation itself fails.

The expected shape is similar to:

```json
{
"system_settings_firewall_enable": true,
"audit_folders_mode_configure": 700,
"system_settings_ssh_disable": "FAIL RUNNING"
}
```
|-Linux
|---Config
|---Custom Compliance
|---Misc
|
|-macOS
|---Apps
|---Config
|---Custom Attributes
|---Custom Profiles
```

That object is the device's discovered state. Intune compares it with the `Rules` array; the object is not an overall compliance score.

Validate the final output on a pilot Mac:

```bash
sudo ./build/cis_lvl1_macos_26.0/cis_lvl1_macos_26.0_compliance.sh \
--check \
--quiet=2 \
--json | /usr/bin/python3 -m json.tool
```

Use the JSON property names from this output as the `SettingName` values in the matching rules JSON. Do not upload a script until the discovery identifiers, output types, and rules JSON have been tested together.

## Step 8: upload the discovery script to Intune

1. Open **Intune admin center**.
2. Go to **Devices** -> **macOS** -> **Compliance** -> **Scripts**.
3. Select **Add** and create a macOS custom compliance discovery script.
4. Enter a descriptive name, such as `mSCP CIS Level 1 macOS 26.0`.
5. Upload the tested mSCP-generated JSON discovery script.
6. Set **Run this script using the logged on credentials** to **No** when the integration requires root, as the native mSCP audit script does.
7. Save the script.

## Step 9: create the Intune compliance policy

1. Go to **Devices** -> **macOS** -> **Compliance**.
2. Select **Create policy**.
3. Select **macOS** and the Mac compliance policy profile.
4. On **Compliance settings**, enable **Custom Compliance**.
5. Select the discovery script uploaded in Step 8.
6. Upload the rules JSON whose `SettingName` values match the identifiers returned by the discovery script.
7. Configure the noncompliance actions required for the pilot.
8. Assign the policy to a small pilot device group.
9. Review the policy and select **Create**.

The script and rules JSON are inseparable. Every `SettingName` in the rules JSON must be returned by the script with the declared data type.

## Step 10: validate device compliance

1. On a pilot Mac, open **Company Portal**.
2. Select **Devices**, select the Mac, and choose **Check Status**.
3. Wait for the device to check in.
4. In Intune, open **Reports** -> **Device compliance** -> **Noncompliant devices and settings**.
5. Filter for macOS and review each custom setting.

For troubleshooting, compare:

```text
mSCP rule metadata
-> generated check
-> JSON discovery property and value
-> Intune rules JSON
-> Intune per-setting result
```

Custom compliance evaluates and reports device state. It does not remediate the Mac. Use mSCP configuration profiles, DDM, another management policy, or a separately governed remediation process to enforce settings.

### Disclaimer
Understand the impact of each sample script prior to running it; samples should be run in a non-production or "test" environment.
## Updating the baseline

### Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
When updating mSCP:

When you submit a pull request, a CLA bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
1. Review the selected source commit or release.
2. Regenerate the baseline YAML.
3. Regenerate the compliance script.
4. Re-test every JSON property and data type.
5. Update the Intune rules JSON when rule IDs, expected values, or supported checks change.
6. Pilot the updated script and policy before broad assignment.

This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
Keep the generated baseline, script, rules JSON, source version, and test evidence together so the Intune policy can be reproduced and audited.
23 changes: 23 additions & 0 deletions docs/baseline-generation-summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated baseline summary

These assets were generated from the mSCP macOS 26.0 baseline files.

| Baseline | Supported rules | Unsupported rules | Policy parts |
| --- | ---: | ---: | ---: |
| `800-171_macos_26.0` | 145 | 33 | 3 |
| `800-53r5_high_macos_26.0` | 170 | 53 | 3 |
| `800-53r5_low_macos_26.0` | 138 | 30 | 3 |
| `800-53r5_moderate_macos_26.0` | 167 | 47 | 3 |
| `all_rules_macos_26.0` | 241 | 92 | 4 |
| `cis_lvl1_macos_26.0` | 93 | 5 | 3 |
| `cis_lvl2_macos_26.0` | 110 | 7 | 3 |
| `cisv8_macos_26.0` | 159 | 20 | 3 |
| `cmmc_lvl1_macos_26.0` | 75 | 13 | 2 |
| `cmmc_lvl2_macos_26.0` | 181 | 34 | 3 |
| `cnssi-1253_high_macos_26.0` | 200 | 67 | 4 |
| `cnssi-1253_low_macos_26.0` | 196 | 52 | 4 |
| `cnssi-1253_moderate_macos_26.0` | 199 | 57 | 4 |
| `disa_stig_macos_26.0` | 149 | 11 | 3 |
| `hicp_lp_macos_26.0` | 124 | 22 | 3 |
| `nlmapgov_base_macos_26.0` | 36 | 3 | 1 |
| `nlmapgov_plus_macos_26.0` | 82 | 7 | 1 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions docs/pilot-control-mapping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Pilot control mapping

This pilot focuses on controls that map cleanly to Intune custom compliance and have stable local signals on macOS.

| Policy | Intune setting | mSCP rule | Example NIST references |
| --- | --- | --- | --- |
| Root | `AutomaticLoginDisabled` | `system_settings_automatic_login_disable` | IA-2, IA-5(13) |
| Root | `AutomaticLogoutEnabled` | `system_settings_automatic_logout_enforce` | AC-12, AC-2(5) |
| Root | `AutomaticLogoutSeconds` | `system_settings_automatic_logout_enforce` | AC-12, AC-2(5) |
| Root | `SshPasswordAuthenticationDisabled` | `auth_ssh_password_authentication_disable` | IA-2, IA-5(2), MA-4 |
| Root | `FailedLoginAttemptsMaximum` | `pwpolicy_account_lockout_enforce` | AC-7 |
| Root | `FailedLoginLockoutMinutes` | `pwpolicy_account_lockout_timeout_enforce` | AC-7 |
| Root | `PasswordHistoryDepth` | `pwpolicy_history_enforce` | IA-5(1) |
| Root | `FirewallEnabled` | `system_settings_firewall_enable` | AC-4, SC-7, CM-7 |
| Root | `FirewallStealthModeEnabled` | `system_settings_firewall_stealth_mode_enable` | SC-7, SC-7(16), CM-7 |
| Root | `ScreenSharingDisabled` | `system_settings_screen_sharing_disable` | AC-3, AC-17 |
| Root | `PrinterSharingDisabled` | `system_settings_printer_sharing_disable` | CM-7, CM-7(1) |
| Root | `GatekeeperEnabled` | `os_gatekeeper_enable` | CM-14, CM-5, SI-3, SI-7(1) |
| User | `BluetoothSharingDisabled` | `system_settings_bluetooth_sharing_disable` | AC-3, AC-18(4), CM-7 |
| User | `AirDropDisabled` | `os_airdrop_disable` | AC-3, AC-20, CM-7 |

## Notes

- `AutomaticLogoutEnabled` and `AutomaticLogoutSeconds` are split into two Intune rules so `0` does not incorrectly pass a maximum-seconds comparison.
- Password policy values are returned as numbers so the same script can support stricter or looser operands later.
- This pilot intentionally skips some mSCP rules that are better enforced by configuration profiles or whose observable local state is ambiguous without a profile-specific signal.
32 changes: 32 additions & 0 deletions examples/macos-custom-compliance-demo-rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"Rules": [
{
"SettingName": "IntuneAgentInstalled",
"Operator": "IsEquals",
"DataType": "String",
"Operand": "true",
"MoreInfoUrl": "https://learn.microsoft.com/en-us/intune/device-security/compliance/custom-settings",
"RemediationStrings": [
{
"Language": "en_US",
"Title": "The Microsoft Intune Agent must be installed. Value discovered was {ActualValue}.",
"Description": "Install the Microsoft Intune Agent on the Mac."
}
]
},
{
"SettingName": "IntuneAgentVersion",
"Operator": "NotEquals",
"DataType": "String",
"Operand": "not installed",
"MoreInfoUrl": "https://learn.microsoft.com/en-us/intune/device-security/compliance/custom-settings",
"RemediationStrings": [
{
"Language": "en_US",
"Title": "The Microsoft Intune Agent must be installed. Value discovered was {ActualValue}.",
"Description": "Install the Microsoft Intune Agent on the Mac."
}
]
}
]
}
10 changes: 10 additions & 0 deletions examples/macos-custom-compliance-demo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

info_plist="/Library/Intune/Microsoft Intune Agent.app/Contents/Info.plist"

if [[ -f "$info_plist" ]]; then
version=$(/usr/bin/plutil -extract CFBundleShortVersionString raw "$info_plist" 2>/dev/null)
printf '{"IntuneAgentInstalled":"true","IntuneAgentVersion":"%s"}\n' "$version"
else
printf '{"IntuneAgentInstalled":"false","IntuneAgentVersion":"not installed"}\n'
fi
Loading