check_ad_binding

check_ad_binding

Checks whether the Mac is bound to an Active Directory domain and reports the binding status, domain name, and last successful authentication. Use when diagnosing AD login failures or password sync issues.

Metadata

Name

check_ad_binding

Updated

2 months ago

Source

GitHub source

Risk

Low

Requires consent

false

Affected scope

user

Code

/**
 * mcp/skills/checkAdBinding.ts — check_ad_binding skill
 *
 * Checks whether the Mac is bound to an Active Directory domain and reports
 * the binding status, domain name, and last successful authentication. Use
 * when diagnosing AD login failures or password sync issues.
 *
 * Platform strategy
 * -----------------
 * darwin  `dsconfigad -show` to get AD config,
 *         `dscl /Active\ Directory/ -list /` for domain list
 * win32   PowerShell (Get-WmiObject Win32_ComputerSystem).PartOfDomain and
 *         [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
 *
 * Smoke test
 *   npx tsx -r dotenv/config mcp/skills/checkAdBinding.ts
 */

import * as os       from "os";
import { exec }      from "child_process";
import { promisify } from "util";
import { z }         from "zod";

const execAsync = promisify(exec);

// -- Meta ---------------------------------------------------------------------

export const meta = {
  name: "check_ad_binding",
  description:
    "Checks whether the Mac is bound to an Active Directory domain and reports " +
    "the binding status, domain name, and last successful authentication. " +
    "Use when diagnosing AD login failures or password sync issues.",
  riskLevel:       "low",
  destructive:     false,
  requiresConsent: false,
  supportsDryRun:  false,
  affectedScope:   ["user"],
  auditRequired:   false,
  schema: {},
} as const;

// -- Types --------------------------------------------------------------------

interface AdBindingInfo {
  isBound:          boolean;
  domain:           string | null;
  domainController: string | null;
  lastBindCheck:    string | null;
  errors:           string[];
}

// -- PowerShell helper --------------------------------------------------------

async function runPS(script: string): Promise<string> {
  const encoded = Buffer.from(script, "utf16le").toString("base64");
  const { stdout } = await execAsync(
    `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encoded}`,
    { maxBuffer: 10 * 1024 * 1024 },
  );
  return stdout.trim();
}

// -- darwin implementation ----------------------------------------------------

async function checkAdBindingDarwin(): Promise<AdBindingInfo> {
  const errors: string[] = [];
  let isBound           = false;
  let domain:           string | null = null;
  let domainController: string | null = null;
  let lastBindCheck:    string | null = null;

// Run dsconfigad -show to get AD binding details
  try {
    const { stdout } = await execAsync(
      "dsconfigad -show 2>&1",
      { maxBuffer: 2 * 1024 * 1024 },
    );

if (stdout.includes("There is no Active Directory binding")) {
      isBound = false;
    } else {
      // Parse domain
      const domainMatch = stdout.match(/Active Directory Domain\s*=\s*(.+)/i);
      if (domainMatch) {
        domain  = domainMatch[1].trim();
        isBound = true;
      }

// Parse domain controller
      const dcMatch = stdout.match(/Preferred Domain controller\s*=\s*(.+)/i);
      if (dcMatch) domainController = dcMatch[1].trim();

// Parse last bind check from directory services logs if available
      const bindingMatch = stdout.match(/Computer Account\s*=\s*(.+)/i);
      if (bindingMatch) lastBindCheck = `Computer account: ${bindingMatch[1].trim()}`;
    }
  } catch (err) {
    const msg = (err as Error).message ?? "dsconfigad failed";
    errors.push(msg);
  }

// Confirm by listing /Active Directory domains
  if (isBound) {
    try {
      const { stdout } = await execAsync(
        `dscl "/Active Directory/" -list / 2>/dev/null`,
        { maxBuffer: 1 * 1024 * 1024, shell: "/bin/bash" },
      );
      const listed = stdout.trim().split("\n").filter(Boolean);
      if (listed.length > 0 && !domain) {
        domain = listed[0];
      }
    } catch (err) {
      errors.push(`dscl listing failed: ${(err as Error).message}`);
    }
  }

return { isBound, domain, domainController, lastBindCheck, errors };
}

// -- win32 implementation -----------------------------------------------------

async function checkAdBindingWin32(): Promise<AdBindingInfo> {
  const errors: string[] = [];

const ps = `
$ErrorActionPreference = 'SilentlyContinue'
$cs = Get-WmiObject Win32_ComputerSystem
$isBound = [bool]$cs.PartOfDomain
$domain  = if ($isBound) { $cs.Domain } else { $null }
$dc      = $null
$lastCheck = $null
if ($isBound) {
  try {
    $d   = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
    $dc  = $d.PdcRoleOwner.Name
    $lastCheck = (Get-Date).ToString('o')
  } catch {
    # domain unreachable
  }
}
[PSCustomObject]@{
  isBound          = $isBound
  domain           = $domain
  domainController = $dc
  lastBindCheck    = $lastCheck
  errors           = @()
} | ConvertTo-Json -Compress`.trim();

try {
    const raw    = await runPS(ps);
    const parsed = JSON.parse(raw) as AdBindingInfo;
    return parsed;
  } catch (err) {
    errors.push((err as Error).message);
    return { isBound: false, domain: null, domainController: null, lastBindCheck: null, errors };
  }
}

// -- Exported run function ----------------------------------------------------

export async function run(_args: Record<string, never> = {}) {
  const platform = os.platform();

const info = platform === "win32"
    ? await checkAdBindingWin32()
    : await checkAdBindingDarwin();

return { platform, ...info };
}

// -- CLI smoke test -----------------------------------------------------------

if (false) {
  run({})
    .then(r => console.log(JSON.stringify(r, null, 2)))
    .catch((err: Error) => { console.error(err.message); process.exit(1); });
}