# MCM (SCCM/MECM)

This guide covers deploying Safety Endpoint to Windows devices using **Packages & Programs** in Microsoft Configuration Manager (MCM), formerly known as System Center Configuration Manager (SCCM) and Microsoft Endpoint Configuration Manager (MECM). The deployment uses the same signed script used across all supported platforms — it installs, updates, and verifies Safety Endpoint on a recurring schedule.

## Overview

Safety Endpoint is deployed as a Configuration Manager **Package** with a **Program** that runs the detection script on a recurring schedule. The script downloads the official setup script, verifies its Authenticode signature, and executes it. Since the setup script is idempotent, it handles installation, updates, and health checks internally.

The script reports status through its exit code:

| Exit Code | Meaning                                     |
| --------- | ------------------------------------------- |
| 0         | Safety Endpoint is installed and up to date |
| 1         | Setup failed — requires manual review       |

## Prerequisites

{% hint style="info" %}
Before starting, make sure you have:

* **Configuration Manager** admin console access with permissions to create Packages and deploy to collections
* **At least one distribution point** configured and operational
* **Enrollment key** from the [Safety Platform](https://getsafety.com/app/device-enrollment) — click **Manage Enrollment Key** → **Create Enrollment Key**
* Target devices running **Windows 10 or later (64-bit)** with PowerShell 5.0+
* The **Configuration Manager client** installed and healthy on target devices
  {% endhint %}

## Deploy Safety Endpoint

{% stepper %}
{% step %}

#### Get Your Enrollment Key

Log in to the [Safety Platform](https://getsafety.com/app/device-enrollment), click **Manage Enrollment Key** → **Create Enrollment Key**, and copy the generated key. This key links devices to your Safety organization.
{% endstep %}

{% step %}

#### Prepare the Script

Copy the script below and replace `REPLACE_WITH_YOUR_ENROLLMENT_KEY` with your organization's enrollment key. Save it as `run.ps1` in a network-accessible folder (e.g., `\\your-server\sources\SafetyEndpoint\run.ps1`):

{% code title="run.ps1" %}

```powershell
# Safety Endpoint - MDM Run Script (Windows)
# Template: values below are populated by the Safety Platform per organization.
# Paste this into your MDM script field (Intune, NinjaOne, etc.)
# Run as: SYSTEM | Schedule: recurring (e.g. every 1 hour)
#
# Downloads the Safety Endpoint setup script, verifies its Authenticode
# signature is from Safety CLI Cybersecurity Inc, and executes it.

# ── Organization Configuration ───────────────────────────────────────
$EnrollmentKey = "REPLACE_WITH_YOUR_ENROLLMENT_KEY"
# Comma-separated list of firewall tools to exclude (e.g. "pip,npm")
$ExcludeFirewallTools = ""
# ─────────────────────────────────────────────────────────────────────

$ErrorActionPreference = "Stop"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$setupUrl = "https://getsafety.com/cli/setup.ps1"
$tmpFile  = Join-Path $env:TEMP "safety-mdm-setup-$(Get-Random).ps1"

# ── HttpClient with proxy support (same as install.ps1 / setup.ps1) ─
Add-Type -AssemblyName System.Net.Http -ErrorAction Stop

$handler = [System.Net.Http.HttpClientHandler]::new()

$proxyUrl = @($env:HTTPS_PROXY, $env:ALL_PROXY, $env:HTTP_PROXY) |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1

if ($proxyUrl) {
    $proxyUri = [Uri]$proxyUrl
    $proxy = New-Object Net.WebProxy($proxyUri)
    if ($proxyUri.UserInfo) {
        $parts = $proxyUri.UserInfo -split ":", 2
        $proxy.Credentials = New-Object Net.NetworkCredential(
            [Uri]::UnescapeDataString($parts[0]),
            $(if ($parts.Length -gt 1) { [Uri]::UnescapeDataString($parts[1]) } else { "" })
        )
    } else {
        $proxy.UseDefaultCredentials = $true
    }
    $handler.Proxy = $proxy
} else {
    $handler.DefaultProxyCredentials = [Net.CredentialCache]::DefaultCredentials
}

$client = [System.Net.Http.HttpClient]::new($handler)
$null = $client.DefaultRequestHeaders.UserAgent.ParseAdd("Safety-Endpoint/1 (run)")

# ── Download, verify, execute ────────────────────────────────────────
try {
    # 1. Download (preserves exact bytes for Authenticode)
    Write-Host "Downloading $setupUrl ..."
    $response = $client.GetAsync($setupUrl).GetAwaiter().GetResult()
    $null = $response.EnsureSuccessStatusCode()
    $bytes = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
    [System.IO.File]::WriteAllBytes($tmpFile, $bytes)

    # 2. Verify Authenticode signature
    $sig = Get-AuthenticodeSignature -FilePath $tmpFile
    if ($sig.Status -ne "Valid") {
        throw "Signature status: $($sig.Status) - $($sig.StatusMessage)"
    }
    if ($sig.SignerCertificate.Subject -notmatch "O=Safety CLI Cybersecurity Inc") {
        throw "Unexpected signer: $($sig.SignerCertificate.Subject)"
    }
    Write-Host "Signature verified: $($sig.SignerCertificate.Subject)"

    # 3. Execute
    $setupArgs = @("-EnrollmentKey", $EnrollmentKey)
    if ($ExcludeFirewallTools) { $setupArgs += @("-ExcludeFirewallTools", $ExcludeFirewallTools) }
    & $tmpFile @setupArgs
}
catch {
    Write-Error "Safety MDM setup failed: $_"
    exit 1
}
finally {
    Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
    if ($client) { $client.Dispose() }
}
```

{% endcode %}

{% hint style="warning" %}
Make sure you replace `REPLACE_WITH_YOUR_ENROLLMENT_KEY` with your actual enrollment key before saving the file.
{% endhint %}
{% endstep %}

{% step %}

#### Create a Package

1. Open the **Configuration Manager console**
2. Go to **Software Library** → **Application Management** → **Packages**
3. Right-click **Packages** and select **Create Package**
4. On the **Package** page:
   * **Name**: `Safety Endpoint - Setup`
   * **Description**: Installs and maintains Safety Endpoint via signed setup script
   * Check **This package contains source files**
   * **Source folder**: Browse to the network folder containing `run.ps1` (e.g., `\\your-server\sources\SafetyEndpoint`)
5. Click **Next** — do not create a program yet (we configure it in the next step)
6. Complete the wizard
   {% endstep %}

{% step %}

#### Create a Program

1. Right-click the **Safety Endpoint - Setup** package and select **Create Program**
2. On the **Standard Program** page:
   * **Name**: `Run Safety Setup`
   * **Command line**: `powershell.exe -ExecutionPolicy Bypass -NoProfile -File run.ps1`
   * **Run**: **Hidden**
   * **Program can run**: **Whether or not a user is logged on**
   * **Run mode**: **Run with administrative rights**
3. On the **Requirements** page:
   * **Platform requirements**: Select your target **64-bit** Windows versions only (Windows 10 x64 and later) — Safety Endpoint does not support 32-bit systems
   * **Maximum allowed run time (minutes)**: `60`
4. Complete the wizard
   {% endstep %}

{% step %}

#### Distribute Content

Before deploying, the package must be copied to your distribution points:

1. Right-click the **Safety Endpoint - Setup** package
2. Select **Distribute Content**
3. Add your **distribution points** or **distribution point groups**
4. Complete the wizard and wait for content distribution to finish

{% hint style="info" %}
You can monitor distribution status in **Monitoring** → **Distribution Status** → **Content Status** → **Safety Endpoint - Setup**.
{% endhint %}
{% endstep %}

{% step %}

#### Deploy to a Device Collection

1. Right-click the **Safety Endpoint - Setup** package and select **Deploy**
2. On the **General** page:
   * **Collection**: Select the device collection to target
3. On the **Content** page:
   * Verify your distribution points are listed
4. On the **Deployment Settings** page:
   * **Purpose**: **Required** (runs automatically without user interaction)
5. On the **Scheduling** page:
   * Under **Assignment schedule**, click **New\...**
   * In the dialog, select **Custom interval** and set **Recur every**: **1 hour**
   * Click **OK** to add the schedule entry — this ensures new devices get Safety Endpoint quickly and existing devices stay updated
   * Set **Rerun behavior**: **Always rerun program**
6. On the **User Experience** page:
   * **Software installation**: Allow
   * **Allow clients to use distribution points from the default site boundary group**: Yes
7. Complete the wizard

{% hint style="info" %}
The setup script is idempotent. Running it frequently has minimal overhead — it exits quickly when Safety Endpoint is already up to date.
{% endhint %}
{% endstep %}
{% endstepper %}

## Monitor Deployments

After deployment, monitor your fleet status in the Configuration Manager console:

1. Go to **Monitoring** → **Deployments**
2. Find the **Safety Endpoint - Setup** deployment
3. Review the deployment status summary

| Status          | What It Means                                 | Action                                      |
| --------------- | --------------------------------------------- | ------------------------------------------- |
| **Success**     | Safety Endpoint is installed and current      | None                                        |
| **Error**       | The script failed on this device              | Review the status message for error details |
| **In Progress** | Script is currently running or waiting to run | Wait for completion                         |
| **Unknown**     | Client has not reported status yet            | Verify client health and connectivity       |

To view details for a specific device, click the deployment, then select the **Asset Details** tab. Right-click a device and choose **More Details** → **Status Messages** to see the script output.

## Uninstall Safety Endpoint

If you need to remove Safety Endpoint from devices, create a separate package with the uninstall wrapper script. Like the deployment script, it downloads the official uninstall script from Safety, verifies its Authenticode signature, and executes it.

{% hint style="danger" %}
The uninstall script removes **all** Safety Endpoint artifacts from the machine, including configuration, firewall wrappers, package manager settings, and data for all user profiles.
{% endhint %}

{% stepper %}
{% step %}

#### Prepare the Uninstall Script

Copy the script below and save it as `uninstall-run.ps1` in a network-accessible folder (e.g., `\\your-server\sources\SafetyEndpoint-Uninstall\uninstall-run.ps1`) — no configuration is needed:

{% code title="uninstall-run.ps1" %}

```powershell
# Safety Endpoint - MDM Uninstall Script (Windows)
# Template: paste this into your MDM script field (Intune, NinjaOne, etc.)
# Run as: SYSTEM | Schedule: one-time or on-demand
#
# Downloads the Safety Endpoint uninstall script, verifies its Authenticode
# signature is from Safety CLI Cybersecurity Inc, and executes it.

$ErrorActionPreference = "Stop"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$uninstallUrl = "https://getsafety.com/cli/uninstall.ps1"
$tmpFile      = Join-Path $env:TEMP "safety-mdm-uninstall-$(Get-Random).ps1"

# ── HttpClient with proxy support (same as install.ps1 / setup.ps1) ─
Add-Type -AssemblyName System.Net.Http -ErrorAction Stop

$handler = [System.Net.Http.HttpClientHandler]::new()

$proxyUrl = @($env:HTTPS_PROXY, $env:ALL_PROXY, $env:HTTP_PROXY) |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1

if ($proxyUrl) {
    $proxyUri = [Uri]$proxyUrl
    $proxy = New-Object Net.WebProxy($proxyUri)
    if ($proxyUri.UserInfo) {
        $parts = $proxyUri.UserInfo -split ":", 2
        $proxy.Credentials = New-Object Net.NetworkCredential(
            [Uri]::UnescapeDataString($parts[0]),
            $(if ($parts.Length -gt 1) { [Uri]::UnescapeDataString($parts[1]) } else { "" })
        )
    } else {
        $proxy.UseDefaultCredentials = $true
    }
    $handler.Proxy = $proxy
} else {
    $handler.DefaultProxyCredentials = [Net.CredentialCache]::DefaultCredentials
}

$client = [System.Net.Http.HttpClient]::new($handler)
$null = $client.DefaultRequestHeaders.UserAgent.ParseAdd("Safety-Endpoint/1 (run)")

# ── Download, verify, execute ────────────────────────────────────────
try {
    # 1. Download (preserves exact bytes for Authenticode)
    Write-Host "Downloading $uninstallUrl ..."
    $response = $client.GetAsync($uninstallUrl).GetAwaiter().GetResult()
    $null = $response.EnsureSuccessStatusCode()
    $bytes = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
    [System.IO.File]::WriteAllBytes($tmpFile, $bytes)

    # 2. Verify Authenticode signature
    $sig = Get-AuthenticodeSignature -FilePath $tmpFile
    if ($sig.Status -ne "Valid") {
        throw "Signature status: $($sig.Status) - $($sig.StatusMessage)"
    }
    if ($sig.SignerCertificate.Subject -notmatch "O=Safety CLI Cybersecurity Inc") {
        throw "Unexpected signer: $($sig.SignerCertificate.Subject)"
    }
    Write-Host "Signature verified: $($sig.SignerCertificate.Subject)"

    # 3. Execute
    & $tmpFile
}
catch {
    Write-Error "Safety MDM uninstall failed: $_"
    exit 1
}
finally {
    Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
    if ($client) { $client.Dispose() }
}
```

{% endcode %}
{% endstep %}

{% step %}

#### Create the Uninstall Package

1. In the Configuration Manager console, go to **Software Library** → **Application Management** → **Packages**
2. Right-click **Packages** and select **Create Package**
3. On the **Package** page:
   * **Name**: `Safety Endpoint - Uninstall`
   * **Description**: Removes all Safety Endpoint artifacts from the device
   * Check **This package contains source files**
   * **Source folder**: Browse to the folder containing `uninstall-run.ps1`
     {% endstep %}

{% step %}

#### Create the Uninstall Program

1. Right-click the **Safety Endpoint - Uninstall** package and select **Create Program**
2. On the **Standard Program** page:
   * **Name**: `Run Safety Uninstall`
   * **Command line**: `powershell.exe -ExecutionPolicy Bypass -NoProfile -File uninstall-run.ps1`
   * **Run**: **Hidden**
   * **Program can run**: **Whether or not a user is logged on**
   * **Run mode**: **Run with administrative rights**
     {% endstep %}

{% step %}

#### Distribute and Deploy

1. Right-click the package → **Distribute Content** → add your distribution points
2. Right-click the package → **Deploy** → select the target device collection
3. On the **Deployment Settings** page:
   * **Purpose**: **Required**
4. On the **Scheduling** page:
   * Set to run **As soon as possible** (one-time deployment)
   * **Rerun behavior**: **Never rerun deployed program**

The script runs once per device. It requires SYSTEM privileges and will clean up:

* Safety Endpoint binaries and system PATH entries
* Per-user configuration, firewall wrappers, and shell profiles
* Package manager configurations (pip, uv, npm)
* Scheduled tasks created by Safety Endpoint
  {% endstep %}
  {% endstepper %}

## Troubleshooting

<details>

<summary>Script fails with proxy or network errors</summary>

The script automatically detects proxy settings from the `HTTPS_PROXY`, `ALL_PROXY`, or `HTTP_PROXY` environment variables. If your network requires a proxy that is not configured via these variables, set the appropriate environment variable at the system level:

1. Go to **System Properties** → **Environment Variables**
2. Add a system variable `HTTPS_PROXY` with your proxy URL (e.g., `http://proxy.company.com:8080`)

If using an authenticated proxy, include credentials in the URL: `http://user:password@proxy.company.com:8080`

</details>

<details>

<summary>Signature verification fails</summary>

The script verifies that the downloaded setup script is signed by **Safety CLI Cybersecurity Inc** using Authenticode. If signature verification fails:

* Ensure the device has internet access to `getsafety.com`
* Check that TLS 1.2 is enabled on the device
* Verify no network appliance is intercepting or modifying HTTPS traffic (SSL inspection can break Authenticode signatures)

</details>

<details>

<summary>Content distribution fails or clients cannot access the package</summary>

If devices report that the package content is not available:

* In the console, go to **Monitoring** → **Distribution Status** → **Content Status** and verify the package shows as successfully distributed
* Ensure the target devices are within a **boundary group** that includes the distribution point
* Check that the source folder path (`\\your-server\sources\...`) is still accessible and contains `run.ps1`
* If using a pull distribution point, allow time for content to replicate

</details>

<details>

<summary>Deployment shows "Error" but Safety Endpoint is installed</summary>

An Error status means the script exited with a non-zero code. This can happen if:

* The setup script detected a problem during its health check
* A newer version failed to install
* A temporary network issue occurred during the run

Review the status messages for the specific device in **Monitoring** → **Deployments**. The next scheduled run will retry automatically.

</details>

<details>

<summary>PowerShell execution policy blocks the script</summary>

The program command line includes `-ExecutionPolicy Bypass` which should override local policy. If you still encounter execution policy issues:

* Verify no Group Policy enforces a restrictive execution policy that overrides the `-ExecutionPolicy` parameter
* Ensure the Configuration Manager client agent is configured to allow script execution
* Check **Client Settings** → **Computer Agent** → **PowerShell execution policy** is set to **Bypass**

</details>

<details>

<summary>Client agent is not running or unhealthy</summary>

Deployments require a healthy Configuration Manager client on each device. If a device is not receiving the deployment:

* Verify the **CCMExec** service is running on the device
* Check the client health status in **Assets and Compliance** → **Devices** → select the device → **Client Activity** tab
* Run a **Client Notification** → **Evaluate Machine Policy** to force a policy refresh
* Review `C:\Windows\CCM\Logs\execmgr.log` on the device for program execution details

</details>
