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

# Microsoft Intune

> Deploy Unbound to Windows devices with Intune Remediations

## Overview

Run the Unbound installer on a schedule with Intune **Remediations**.

<Note>
  Remediations requires Windows Enterprise E3 or E5, included in Microsoft 365 E3, E5 and F3. Without it, use the [scheduled task](#no-remediations-licence) below.
</Note>

<Warning>
  Deploy Python 3 as a Win32 app with `InstallAllUsers=1 PrependPath=1 Include_launcher=1`. A per-user install will not be found.
</Warning>

Allow HTTPS to `getunbound.ai`, `backend.getunbound.ai`, `api.getunbound.ai`, `raw.githubusercontent.com` and `github.com`.

***

## 1. Get your API key

Open [Configure](https://gateway.getunbound.ai/configure) and copy the key from the **API Access** panel.

***

## 2. Create the detection script

Save this as `Detect_Unbound.ps1`:

```powershell theme={null}
$base = 'C:\Program Files\Unbound'
$marker = Join-Path $base 'last-success.txt'
$owned = (Test-Path $marker) -and
    ((Get-Acl $base).GetOwner([Security.Principal.SecurityIdentifier]).Value -in @('S-1-5-18', 'S-1-5-32-544'))
if ($owned -and ((Get-Date) - (Get-Item $marker).LastWriteTime).TotalHours -lt 20) {
    Write-Output "Unbound: ran $((Get-Item $marker).LastWriteTime)"; exit 0
}
Write-Output "Unbound: needs a run"; exit 1
```

***

## 3. Create the remediation script

Save this as `Remediate_Unbound.ps1`, replacing `YOUR_ADMIN_API_KEY` with your key:

```powershell theme={null}
$base = 'C:\Program Files\Unbound'
New-Item -ItemType Directory -Force -Path $base | Out-Null
icacls $base /setowner '*S-1-5-32-544' /T /C | Out-Null
icacls $base /reset /T /C | Out-Null
icacls $base /inheritance:r /grant:r '*S-1-5-18:(OI)(CI)F' '*S-1-5-32-544:(OI)(CI)F' | Out-Null
$acl = Get-Acl $base
$allowed = @('S-1-5-18', 'S-1-5-32-544')
if ($acl.GetOwner([Security.Principal.SecurityIdentifier]).Value -notin $allowed -or
    ($acl.Access | Where-Object {
        $_.IdentityReference.Translate([Security.Principal.SecurityIdentifier]).Value -notin $allowed })) {
    throw "Could not secure $base. Refusing to continue."
}
Remove-Item (Join-Path $base '*') -Force -Recurse -ErrorAction SilentlyContinue

$installer = Join-Path $base 'onboard.ps1'
Invoke-WebRequest -Uri 'https://getunbound.ai/setup/mdm/windows/onboard' -OutFile $installer -UseBasicParsing -ErrorAction Stop

& $installer -ApiKey 'YOUR_ADMIN_API_KEY' -Backfill
$code = $LASTEXITCODE

if ($code -eq 0) {
    try { Set-Content -Path (Join-Path $base 'last-success.txt') -Value (Get-Date -Format 'o') -ErrorAction Stop }
    catch { Write-Output "Installed, but could not write the marker. $_"; $code = 1 }
}
Remove-Item $installer -Force -ErrorAction SilentlyContinue
exit $code
```

The folder sits under `C:\Program Files`, which standard users cannot write to, and is locked to SYSTEM and administrators. Nothing can be planted there ahead of the script.

`-Backfill` imports past GitHub Copilot, Claude Code and Codex sessions. Re-running never duplicates a session.

***

## 4. Create the package in Intune

Go to **Devices → Manage devices → Scripts and remediations → Remediations → Create script package**.

<Steps>
  <Step title="Upload the scripts">
    Name the package `Unbound daily onboard` and upload both files as UTF-8 without a BOM.
  </Step>

  <Step title="Set the options">
    **Run using logged-on credentials: No**. **Enforce signature check: No**. **Run in 64-bit PowerShell: Yes**.
  </Step>

  <Step title="Assign and schedule">
    Assign to your developer device group and set the schedule to **Daily**.
  </Step>
</Steps>

To run it on one device now, open that device in Intune and choose **Run remediation**.

***

## Verify

<CardGroup cols={3}>
  <Card title="Intune" icon="list-check">
    Device status shows **Without issues**.
  </Card>

  <Card title="Device" icon="desktop">
    `C:\Program Files\Unbound\last-success.txt` updates daily.
  </Card>

  <Card title="Unbound" icon="chart-line" href="https://gateway.getunbound.ai/settings?tab=devices">
    Devices appear with a recent last-seen time.
  </Card>
</CardGroup>

***

## Troubleshooting

| Symptom                                      | Fix                                                                           |
| -------------------------------------------- | ----------------------------------------------------------------------------- |
| `Python 3 is required but not found in PATH` | Re-deploy Python with `InstallAllUsers=1 PrependPath=1`, then reboot.         |
| Remediation shows **Failed**                 | Open the device in Intune and read the output. It names the step that failed. |
| Copilot rows show `auto`                     | Allow one daily cycle. Sessions VS Code has already rotated cannot be read.   |

***

## No Remediations licence

Deploy this once as a Platform script, run as SYSTEM in 64-bit. The task script holds your API key, so it lives under `C:\Program Files`, which standard users cannot write to, locked to SYSTEM and administrators.

<Accordion title="Scheduled task script">
  ```powershell theme={null}
  $base = 'C:\Program Files\Unbound'
  New-Item -ItemType Directory -Force -Path $base | Out-Null
  icacls $base /setowner '*S-1-5-32-544' /T /C | Out-Null
  icacls $base /reset /T /C | Out-Null
  icacls $base /inheritance:r /grant:r '*S-1-5-18:(OI)(CI)F' '*S-1-5-32-544:(OI)(CI)F' | Out-Null
  $acl = Get-Acl $base
  $allowed = @('S-1-5-18', 'S-1-5-32-544')
  if ($acl.GetOwner([Security.Principal.SecurityIdentifier]).Value -notin $allowed -or
      ($acl.Access | Where-Object {
          $_.IdentityReference.Translate([Security.Principal.SecurityIdentifier]).Value -notin $allowed })) {
      throw "Could not secure $base. Refusing to write the API key."
  }
  Remove-Item (Join-Path $base '*') -Force -Recurse -ErrorAction SilentlyContinue
  $runner = Join-Path $base 'run-unbound.ps1'

  @'
  $installer = 'C:\Program Files\Unbound\onboard.ps1'
  Invoke-WebRequest -Uri 'https://getunbound.ai/setup/mdm/windows/onboard' -OutFile $installer -UseBasicParsing -ErrorAction Stop
  & $installer -ApiKey 'YOUR_ADMIN_API_KEY' -Backfill
  $code = $LASTEXITCODE
  if ($code -eq 0) {
      try { Set-Content -Path 'C:\Program Files\Unbound\last-success.txt' -Value (Get-Date -Format 'o') -ErrorAction Stop }
      catch { Write-Output "Installed, but could not write the marker. $_"; $code = 1 }
  }
  Remove-Item $installer -Force -ErrorAction SilentlyContinue
  exit $code
  '@ | Set-Content -Path $runner -Encoding UTF8

  $action    = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$runner`""
  $trigger   = New-ScheduledTaskTrigger -Daily -At 9:00am
  $settings  = New-ScheduledTaskSettingsSet -StartWhenAvailable -RunOnlyIfNetworkAvailable -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Hours 2)
  $principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
  Register-ScheduledTask -TaskName 'Unbound Daily Onboard' -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force -ErrorAction Stop | Out-Null
  Start-ScheduledTask -TaskName 'Unbound Daily Onboard' -ErrorAction Stop
  ```

  Remove it with `Unregister-ScheduledTask -TaskName 'Unbound Daily Onboard' -Confirm:$false`.
</Accordion>

***

## Remove Unbound

Run the installer with `-Clear` in an elevated PowerShell, then delete `C:\Program Files\Unbound`.

<Card title="Tamper Resistance" icon="lock" href="/mdm-integrations/tamper-resistance">
  Best practices for keeping Unbound active on every device with managed settings.
</Card>
