# ManageEngine

This guide covers deploying Safety Endpoint to Windows devices using **Custom Script Configurations** in ManageEngine Endpoint Central (formerly Desktop Central). The deployment uses the same signed script used across all supported platforms — it installs, updates, and verifies Safety Endpoint on a recurring schedule.

{% hint style="warning" %}
**Endpoint Central required.** ManageEngine Mobile Device Manager Plus (MDM Plus) does not support custom script execution. This guide requires **Endpoint Central**, which is available as both a cloud and on-premises deployment.
{% endhint %}

## Overview

Safety Endpoint is deployed as an Endpoint Central **Computer Configuration** using the Custom Script feature. 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 configuration is set to run on every **Refresh Cycle** (approximately every 90 minutes), ensuring devices stay up to date without manual intervention.

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:

* **Endpoint Central** admin access (cloud or on-premises)
* **Endpoint Central agent** installed and healthy on target devices
* **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** with PowerShell 5.0+
  {% 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:

{% 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 uploading.
{% endhint %}
{% endstep %}

{% step %}

#### Create the Custom Script Configuration

1. In the Endpoint Central console, go to **Configurations** → **Add Configurations** → **Windows Configuration** → **Custom Script** → **Computer Configuration**
2. Enter the configuration details:
   * **Name**: `Safety Endpoint - Setup`
   * **Description**: Installs and maintains Safety Endpoint via signed setup script
3. Under **Script Execution**:
   * Select **Command Line**
   * Paste the full script content, or select **Repository** if you have already added the script to the Script Repository
4. Set **Exit Code** to `0` (success indicator)
5. Set **Deploy Schedule**:
   * **Frequency**: **Every Refresh Cycle**
6. Set **Execution Context**:
   * **Run as**: **System user**
     {% endstep %}

{% step %}

#### Define Targets and Deploy

1. Under **Define the targets**, select the computers or custom groups to deploy to
2. Click **Deploy** to apply the configuration

{% hint style="info" %}
The Refresh Cycle runs approximately every **90 minutes**. The setup script is idempotent — it exits quickly when Safety Endpoint is already up to date, so frequent execution has minimal overhead.
{% endhint %}
{% endstep %}
{% endstepper %}

## Monitor Compliance

After deployment, monitor your fleet status in Endpoint Central:

1. Go to **Configurations** → **Deployed Configurations**
2. Find the **Safety Endpoint - Setup** configuration
3. Click to view the deployment status summary

| Status           | What It Means                             | Action                                       |
| ---------------- | ----------------------------------------- | -------------------------------------------- |
| **Success**      | Safety Endpoint is installed and current  | None                                         |
| **Failed**       | The script exited with a non-zero code    | Click the device entry to view error details |
| **Yet to Apply** | Agent has not picked up the configuration | Wait for the next refresh cycle              |
| **In Progress**  | Script is currently running               | Wait for completion                          |

To retry a failed deployment on a specific device, select the device and click **Retry**.

## Uninstall Safety Endpoint

If you need to remove Safety Endpoint from devices, create a separate Custom Script Configuration 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 %}

#### Create the Uninstall Configuration

1. Go to **Configurations** → **Add Configurations** → **Windows Configuration** → **Custom Script** → **Computer Configuration**
2. Enter the configuration details:
   * **Name**: `Safety Endpoint - Uninstall`
   * **Description**: Removes all Safety Endpoint artifacts from the device
3. Under **Script Execution**, select **Command Line** and paste the following:

{% 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 %}

4. Set **Exit Code** to `0`
5. Set **Deploy Schedule**:
   * **Frequency**: **Once**
6. Set **Run as**: **System user**
   {% endstep %}

{% step %}

#### Deploy the Uninstall

1. Under **Define the targets**, select the computers to uninstall from
2. Click **Deploy**

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>Configuration shows "Yet to Apply" for extended periods</summary>

If a device does not pick up the configuration:

* Verify the Endpoint Central agent is installed and running on the device — check the **ManageEngine** service in Windows Services
* Confirm the device appears in **Scope of Management** and is reachable
* Check the agent's last contact time in the device details
* Trigger a manual agent refresh from the Endpoint Central console: right-click the device → **Scan Now**

</details>

<details>

<summary>Configuration shows "Failed" but Safety Endpoint is installed</summary>

A Failed 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

Click the failed device entry to view the error details. The configuration will automatically retry on the next Refresh Cycle (approximately 90 minutes).

</details>

<details>

<summary>Script requires interactive input</summary>

Endpoint Central cannot run scripts that require user interaction. The Safety Endpoint setup and uninstall scripts are fully non-interactive when run with the enrollment key parameter. If you see errors about interactive input, verify you are using the exact script provided in this guide.

</details>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.safetycli.com/safety-docs/deployment/deployment/manageengine-endpoint-central.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
