> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Intune (Windows)

> Deploy AI Watch to Windows via Microsoft Intune. Detect, endpoint modes, Sessions, and optional runtime discovery ship in one MSI.

AI Watch deploys to Windows as a signed app (`aiwatch.exe`) installed once per device. Tenant config — host, a single **organization API key**, `Mode`, legacy `Enforcement`, Sessions, and optional discovery settings — is written to the registry by the MSI. The MSI **also registers device-local Scheduled Tasks** that run scans (all real user profiles), assert hooks, and install managed AI Watch updates as SYSTEM on a recurring schedule, so there are **no Intune Remediations to create** — this mirrors the macOS `.pkg`, which ships its own launchd jobs.

## Prerequisites

* Microsoft Intune admin with app upload rights. **No Remediations (P2 / add-on) access required** — scanning and hook assertion are scheduled device-locally by the MSI.
* Devices running Windows 10 1809+ / 11, 64-bit, Microsoft Entra joined.
* A single **organization API key** with the **Shadow AI Scan** role minted in **Settings → Organization API keys** (`rl_org_...`). The same key covers scanning, enforcement, sessions, and managed updates.

<Note>
  Windows Home and S mode are not supported.
</Note>

## What you receive

The deployment package (`aiwatch-<version>-intune-package.zip`) contains:

| File                                  | Purpose                                                                                                                                                                                                                               |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aiwatch-<version>-win-x64.intunewin` | App package for Intune upload (installs `aiwatch.exe` + tenant registry **and** registers the device-local Scheduled Tasks)                                                                                                           |
| `custom-detection/detect-install.ps1` | **Recommended detection rule** — requires `aiwatch.exe` (≥ packaged version), `AIWatchScan`, and `AIWatchUpdate` (self-heals incomplete task registration; appends a breadcrumb to `C:\ProgramData\Runlayer\Logs\detect-install.log`) |
| `scripts/bootstrap.ps1`               | Optional manual user-context one-shot (SCCM / GPO logon script) — not required for Intune                                                                                                                                             |

The scan, hook, and update task scripts run **inside** the MSI (registered by its custom action); you don't upload them to Intune. Contact your Runlayer account team if you don't have the `.zip` yet.

## Capability properties

The MSI install command carries tenant config and capability settings. Project and client-config detection is always available after installation; endpoint mode, Sessions, optional runtime discovery, managed updates, and scan tuning are controlled by the properties below. All values write to `HKLM\Software\Runlayer\AIWatch\`: `Host`, `OrgApiKey`, and `Mode` as REG\_SZ; boolean and scan-tuning settings as REG\_DWORD.

| Property                    | Required | Default                | Effect                                                                                                                                                                                         |
| --------------------------- | -------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AIWATCH_HOST`              | **Yes**  | None                   | Tenant host URL                                                                                                                                                                                |
| `AIWATCH_ORG_API_KEY`       | **Yes**  | None                   | The `rl_org_...` secret used for Detect scans, endpoint hooks, and Sessions telemetry                                                                                                          |
| `AIWATCH_MODE`              | No       | Legacy fallback        | `monitor` reports only; `protect` applies scanner Block/Mask plus fail-open, allow-unless-denied MCP governance; `enforce` adds local controls and fail-closed, deny-by-default MCP governance |
| `AIWATCH_ENFORCEMENT`       | No       | `0`                    | Legacy fallback used only when Mode is absent/invalid (`0` = Monitor, `1` = Enforce)                                                                                                           |
| `AIWATCH_SESSIONS`          | No       | `1`                    | Set `0` to skip full event/session hooks; Monitor + Sessions `0` removes hook configs while Detect continues                                                                                   |
| `AIWATCH_DETECT_PROCESSES`  | No       | `0`                    | Set `1` to discover AI tooling and configured MCP servers from running processes and listening sockets                                                                                         |
| `AIWATCH_DETECT_CONTAINERS` | No       | `0`                    | Set `1` for read-only MCP discovery inside running Docker containers; requires Docker                                                                                                          |
| `AIWATCH_AUTO_UPDATE`       | No       | `1`                    | Managed self-update is enabled by default. Set `0` to write `AutoUpdate=0` under HKLM and opt out                                                                                              |
| `AIWATCH_PROJECT_DEPTH`     | No       | `7`                    | Directory depth Detect scans walk under each user home (max `20`; higher values are clamped, non-positive ignored)                                                                             |
| `AIWATCH_PROJECT_TIMEOUT`   | No       | `60`                   | Per-scan time budget in seconds (max `300`; higher values are clamped, non-positive ignored)                                                                                                   |
| `AIWATCH_CPU_CORES`         | No       | half the logical cores | Soft cap on CPU cores the scan may use (`1`..logical cores; higher values are clamped, non-positive ignored)                                                                                   |
| `AIWATCH_MAX_CPU_PERCENT`   | No       | `50`                   | Soft cap on total-machine CPU percent the scan may use (`5`..`100`; out-of-range clamped, non-positive ignored)                                                                                |
| `AIWATCH_MEMORY_LIMIT_MB`   | No       | `1024`                 | Python-heap ceiling in MB; the scan aborts if exceeded (`128`..`8192`; out-of-range clamped, non-positive ignored)                                                                             |

The MDM Configuration setup wizard renders the full install command from the selected mode, Sessions setting, and scan settings. Copy it before closing the dialog.

## Phase 1 — Create the app

<Steps>
  <Step title="Open the Intune admin center">
    Go to [intune.microsoft.com](https://intune.microsoft.com/) → **Apps** → **Windows** → **Add** → **Windows app (Win32)**.
  </Step>

  <Step title="Upload the package">
    Select the `aiwatch-<version>-win-x64.intunewin` file from the deployment package.
  </Step>

  <Step title="App information">
    | Field           | Value                                                               |
    | --------------- | ------------------------------------------------------------------- |
    | **Name**        | `Runlayer AI Watch`                                                 |
    | **Description** | `Runlayer AI Watch agent for Shadow MCP detection and enforcement.` |
    | **Publisher**   | `Runlayer Inc.`                                                     |
    | **App Version** | The version from the deployment package (e.g. `1.0.0`)              |
    | **Notes**       | `stableAppId=com.runlayer.aiwatch`                                  |

    Click **Next**.
  </Step>

  <Step title="Program">
    | Field                       | Value                                                                                                                                                                                                                                            |
    | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | **Install command**         | `msiexec /i aiwatch-<version>-win-x64.msi /qn AIWATCH_HOST=https://<your-host>.runlayer.com AIWATCH_ORG_API_KEY=rl_org_... AIWATCH_MODE=enforce AIWATCH_ENFORCEMENT=1 AIWATCH_SESSIONS=1 AIWATCH_DETECT_PROCESSES=0 AIWATCH_DETECT_CONTAINERS=0` |
    | **Uninstall command**       | `msiexec /x aiwatch-<version>-win-x64.msi /qn`                                                                                                                                                                                                   |
    | **Install behavior**        | System                                                                                                                                                                                                                                           |
    | **Device restart behavior** | No action                                                                                                                                                                                                                                        |

    Use the install command rendered by the setup guide (**Settings** → **MDM configuration** → **Add new**). For Detect-only, use `AIWATCH_MODE=monitor AIWATCH_ENFORCEMENT=0 AIWATCH_SESSIONS=0` and leave both `AIWATCH_DETECT_*` properties at `0`. For a [Monitor rollout](/shadow-ai/enforce#monitor-rollout-no-blocking), set `AIWATCH_MODE=monitor` and keep Sessions enabled. Managed self-update is on by default; add `AIWATCH_AUTO_UPDATE=0` only when your organization needs to opt out.

    For full package, scheduled-task, and hook cleanup, see [Remove AI Watch](/shadow-ai/deploy/remove).

    Click **Next**.
  </Step>

  <Step title="Requirements">
    * **Operating system architecture**: 64-bit
    * **Minimum operating system**: Windows 10 1809

    Click **Next**.
  </Step>

  <Step title="Detection rules">
    **Recommended — custom detection script (self-healing):** choose **Use a custom detection script** and upload `custom-detection/detect-install.ps1` from the deployment package. Set **Run script in 64-bit PowerShell host** to **Yes** and **Enforce script signature check** to **Yes** for CI-built releases (leave it **No** for an unsigned local build). It reports installed only when `aiwatch.exe` (≥ the packaged version), `AIWatchScan`, and `AIWatchUpdate` exist — so a wiped folder or isolated failure to register either mandatory task flips the app to *not installed* and Intune reinstalls, re-running the task-registration custom action. `AIWatchHooks` is intentionally optional. This also drives [Supersedence upgrades](#upgrade-path).

    The #1 cause of a false *not installed* — especially when the breadcrumb log shows `installed:` — is **STDERR contamination**: Intune treats any bytes on STDERR as not-installed even with non-empty STDOUT + exit 0, and PowerShell maps the Warning / Verbose / Progress / Debug / Information streams onto STDERR. The shipped script silences and redirects all non-output streams to prevent this; confirm it in `C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\AgentExecutor.log` (non-empty captured STDERR is the smoking gun). Once STDERR is ruled out, check the execution context: **Install behavior = System** (so detection runs as SYSTEM and can read the `\Runlayer` tasks), **Run script as 64-bit** (the task + exe live under 64-bit paths), and **signature check** matching the signed/unsigned build. Each run appends one line to `C:\ProgramData\Runlayer\Logs\detect-install.log` (running identity + which check failed) — read it on a device that detects as *not installed* to pinpoint the branch.

    **Alternative — file version rule:** select **Manually configure detection rules** → **Add**:

    | Field                | Value                                        |
    | -------------------- | -------------------------------------------- |
    | **Rule type**        | File                                         |
    | **Path**             | `C:\Program Files\Runlayer\AIWatch`          |
    | **File or folder**   | `aiwatch.exe`                                |
    | **Detection method** | File version                                 |
    | **Operator**         | Greater than or equal to                     |
    | **Value**            | The version you are deploying (e.g. `1.0.0`) |

    A file-version rule also supports Supersedence upgrades but won't self-heal a wiped task folder.

    Click **OK** → **Next**.
  </Step>

  <Step title="Dependencies and Supersedence">
    * **Dependencies**: skip (none required).
    * **Supersedence**: skip on first deploy. For upgrades, see [Upgrade path](#upgrade-path) below.

    Click **Next**.
  </Step>

  <Step title="Return codes">
    Keep the defaults. They match the values expected by the MSI installer:

    | Code | Result      |
    | ---- | ----------- |
    | 0    | Success     |
    | 1707 | Success     |
    | 3010 | Soft reboot |
    | 1641 | Hard reboot |
    | 1618 | Retry       |

    Click **Next**.
  </Step>

  <Step title="Assignments">
    Select target device groups under **Required** or **Available for enrolled devices** → **Review + create**.
  </Step>
</Steps>

## Phase 2 — Scheduling (automatic)

There is no Phase 2 to configure. When the MSI installs, a deferred SYSTEM custom action registers the device-local Scheduled Tasks. It starts the scan and hook tasks once immediately; the update task waits about 67 minutes for its first trigger so it cannot overlap the MSI transaction that created it. All three live in a hidden, lock-down-secured `\Runlayer` Task Scheduler folder:

| Task            | Runs as | What it does                                                                                                            | Schedule                                                                   |
| --------------- | ------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `AIWatchScan`   | SYSTEM  | `aiwatch.exe scan --all-users` — enumerates every real user profile and scans each in its own child process (see below) | At boot, any-user logon, every 15 min                                      |
| `AIWatchHooks`  | SYSTEM  | `aiwatch.exe setup hooks install --mdm` to assert hook configs (Protect / Enforce / Sessions)                           | At boot + hourly                                                           |
| `AIWatchUpdate` | SYSTEM  | Checks the HKLM `OrgApiKey`, then runs `aiwatch.exe self-update`; the binary also honors `AutoUpdate=0`                 | First run about 67 minutes after registration, then hourly; no at-boot run |

A single SYSTEM `AIWatchScan` task does the per-user fan-out **inside the binary** (not the scheduler): it enumerates real user profiles from `HKLM\...\ProfileList` (local `S-1-5-21-...` and Microsoft Entra `S-1-12-1-...`) and scans each profile in its own child `aiwatch.exe scan --username <user>` process:

* **Logged on** → the child is launched *as that user* (token drop), so it reads their own `%APPDATA%` MCP client configs. This works for **Entra** users too — the token comes from their live session.
* **Logged off** → the child runs as SYSTEM with `USERPROFILE` / `APPDATA` / `LOCALAPPDATA` pointed at the profile's home directory, so the scanner still reads that user's tree. (The SYSTEM file walk is hardened against junction/symlink traversal.)

Each profile is scanned independently — one failure can't abort the run. A logged-on user (incl. Entra) is scanned as themselves; a logged-off user who has signed in at least once is still covered (as SYSTEM); a brand-new user with no profile yet is picked up on the next tick.

<Note>
  This single SYSTEM task replaces the legacy per-user `AIWatchScanManager` + `AIWatchScan-<SID>` fan-out, which used `-LogonType Interactive` and could not map an Entra `S-1-12-1` SID (`0x80070534`) — so Entra users silently never got a scan task. The register custom action removes any leftover legacy tasks on upgrade. **Logged-off Entra trade-off:** privilege-drop needs a live session token and S4U is local/AD-only, so a logged-off Entra user is scanned **as SYSTEM** (paths pointed at their profile) rather than as themselves — coverage is preserved, only the privilege-drop is unavailable for that subset.
</Note>

<Note>
  Because scans are scheduled on the device (not by Intune), "did it run" is reported by **backend device last-seen** plus Task Scheduler's `LastTaskResult` / event log — there is no per-run Intune Remediations dashboard row. This matches the macOS launchd model.
</Note>

### Tamper resistance

The `\Runlayer` task folder and every task are hidden with protected permissions. The folder, `AIWatchScan`, and `AIWatchHooks` grant full control to SYSTEM and Administrators and read + execute to standard users. `AIWatchUpdate` and its transient installer handoff are stricter: only SYSTEM and Administrators have access, so a standard user cannot trigger the SYSTEM installer. The register custom action re-asserts these permissions on each MSI repair/upgrade, and if `AIWatchScan` or `AIWatchUpdate` is missing the recommended `detect-install.ps1` rule flips the app to *not installed* so Intune reinstalls. A local administrator can still remove or uninstall the agent — the same trust boundary as uninstalling any managed app.

## Phase 3 — Hooks (Protect, Enforce, or Sessions)

Also automatic. The `AIWatchHooks` task refreshes the linked backend settings and asserts hook configs hourly when Mode is Protect/Enforce or Sessions is enabled. It is a silent no-op for Monitor + Sessions disabled. To stop applying endpoint decisions, use **Settings** → **MDM configuration** → **Manage settings** and select Monitor; if Sessions stays enabled, hooks remain installed for telemetry. Re-push `AIWATCH_MODE` only when intentionally changing the registry fallback.

## Verification

After devices sync with Intune (and, for Protect or Enforce, a user logon):

```powershell theme={null}
# 1. App installed; registry has Host + OrgApiKey and configured capabilities?
Get-ItemProperty "HKLM:\Software\Runlayer\AIWatch"

# 2. Scheduled Tasks registered? (hidden \Runlayer folder — expect all three)
Get-ScheduledTask -TaskPath "\Runlayer\" | Format-Table TaskName, State

# 3. The all-users scan task's last result, and per-profile outcomes?
Get-ScheduledTaskInfo -TaskPath "\Runlayer\" -TaskName "AIWatchScan" |
  Select-Object LastRunTime, LastTaskResult
# Orchestrator summary + per-profile result lines (all_users_*) land here:
Get-Content "C:\ProgramData\Runlayer\Logs\scheduled-task.log" -Tail 40
# Detailed per-profile child-scan output lives in each scanned user's profile:
#   C:\Users\<username>\.runlayer\logs

# The update task's last result? (0 includes disabled, no target, or a handoff
# successfully scheduled; it does not prove the MSI install completed.)
Get-ScheduledTaskInfo -TaskPath "\Runlayer\" -TaskName "AIWatchUpdate" |
  Select-Object LastRunTime, LastTaskResult

# If an update was offered, wait for the detached handoff and verify the final
# installed version plus its dedicated log.
Get-ItemProperty "HKLM:\Software\Runlayer\AIWatch" -Name "Version" |
  Select-Object Version
Get-Content "C:\Program Files\Runlayer\UpdateStaging\aiwatch-update.log" -Tail 50

# 4. (Protect, Enforce, or Sessions) hook configs current where supported?
& "C:\Program Files\Runlayer\AIWatch\aiwatch.exe" setup hooks check --mdm
# Expect exit 0.

# 5. (Protect, Enforce, or Sessions) hook configs on disk?
Get-Content "C:\ProgramData\Cursor\hooks.json"
# Claude Code hooks currently live in the console user's settings.json:
Get-Content "$env:USERPROFILE\.claude\settings.json"
```

In the Runlayer dashboard, go to **Settings** → **MDM Configuration** and confirm Detect data is arriving (device last-seen advances each scan tick). There is no Intune Remediations dashboard row to check — task health is read from `LastTaskResult` / the Task Scheduler event log on the device.

For updates, `LastTaskResult = 0` on the parent task means the policy check
completed or a detached handoff was accepted. It is not the final MSI result.
Compare the installed `Version` with the expected backend target and inspect
`aiwatch-update.log` after the handoff has had time to finish.

## Upgrade path

The MSI uses `MajorUpgrade` with `RemoveExistingProducts` before `InstallFinalize` — it uninstalls the old `C:\Program Files\Runlayer\AIWatch\` tree then installs the new one. Registry values in `HKLM\Software\Runlayer\AIWatch` survive because they are owned by a separate component keyed on a registry value, not a file. The task-registration custom action re-runs on the upgrade install (re-asserting the `\Runlayer` tasks with the new scripts), and the unregister action is gated to a true uninstall so tasks are **not** torn down mid-upgrade.

On scheduler-equipped devices, `AIWatchUpdate` performs that upgrade automatically: it creates and locks the Runlayer update directory, copies the backend-selected MSI into it, rehashes and verifies that staged file, and only then queues a one-shot SYSTEM install after the running updater exits. No API key or MSI property is put on the task command line; the MSI recovers existing tenant settings through its AppSearch chain.

<Warning>
  The first automatic update cannot install its own scheduler. Devices on an older MSI need **one manual Intune/MDM push** to a version that includes `AIWatchUpdate`; subsequent updates are automatic unless `AutoUpdate` is `0`.
</Warning>

<Steps>
  <Step title="Download the new package">
    Download the new `aiwatch-<version>-intune-package.zip` and extract the `.intunewin`.
  </Step>

  <Step title="Create a new app">
    Follow [Phase 1](#phase-1--create-the-app) again. The install command must still include `AIWATCH_HOST` and `AIWATCH_ORG_API_KEY` (plus your mode, Sessions, and scan properties) — re-writing the same registry values is a no-op, but the MSI requires both mandatory properties.
  </Step>

  <Step title="Set the detection rule + Supersedence">
    Set the detection rule to **File version >= `<new version>`**. On the new app's **Supersedence** page, add the previous version and select **Supersede**, then assign to the same device groups. Retire the old app once the new one is assigned.
  </Step>
</Steps>

## Troubleshooting

<AccordionGroup>
  <Accordion title="App shows as 'Not installed' on devices">
    * Ensure the device is Microsoft Entra joined (not just registered)
    * Check that the Intune Management Extension service is running
    * Verify the install command has the correct host and API key
    * Review logs in `C:\ProgramData\Microsoft\IntuneManagementExtension\Logs`
  </Accordion>

  <Accordion title="MSI install fails: `AIWATCH_HOST` / `AIWATCH_ORG_API_KEY` property is required">
    The install command is missing a mandatory property. Both `AIWATCH_HOST` and `AIWATCH_ORG_API_KEY` are required — the MSI's launch conditions refuse a half-configured registry write. Re-run with both set.
  </Accordion>

  <Accordion title="No `\Runlayer` tasks after install / no scan data in Runlayer">
    * The task-registration action gates on `OrgApiKey`. Confirm `Get-ItemProperty "HKLM:\Software\Runlayer\AIWatch"` shows `OrgApiKey`, then trigger an app repair/reinstall.
    * Check the task-registration lines (tagged `[register]`) in `C:\ProgramData\Runlayer\Logs\scheduled-task.log`.
    * A user who has never signed in has no profile yet, so they aren't scanned until the next `AIWatchScan` tick (logon / boot / 15 min) after their first sign-in.
    * Verify the organization API key is correct and not revoked (**Settings** → **Organization API keys**), and that the device can reach your Runlayer instance over HTTPS.
  </Accordion>

  <Accordion title="`AIWatchScan` runs but a profile has no scan data">
    The single SYSTEM `AIWatchScan` task runs `aiwatch.exe scan --all-users` and scans each real profile in a child process. Check its `LastTaskResult` and tail `C:\ProgramData\Runlayer\Logs\scheduled-task.log` for the orchestrator summary (`all_users_scan_complete`) and per-profile lines (`all_users_profile_scan_*`) — a non-zero task result means at least one profile's child scan failed, and the log names the SID/username. (The detailed per-profile child-scan output lands in each scanned user's own profile at `C:\Users\<username>\.runlayer\logs` — the child scan's `USERPROFILE` is pointed at that profile — not in `scheduled-task.log`.) Confirm the profile has a real `ProfileImagePath` under `HKLM\...\ProfileList` (local `S-1-5-21-...` / Entra `S-1-12-1-...`; service SIDs `S-1-5-18/19/20` are skipped by design). A **logged-off Entra** user is scanned as SYSTEM (env-pointed at their profile), not privilege-dropped — expected and documented; they are scanned as themselves once logged on.
  </Accordion>

  <Accordion title="Standard user sees 'Access is denied' editing the tasks">
    Expected. Standard users have read + execute only for the `\Runlayer` folder, `AIWatchScan`, and `AIWatchHooks`, and no access to `AIWatchUpdate` or its transient handoff. Use an elevated session to inspect or change them.
  </Accordion>
</AccordionGroup>
