#!/usr/bin/env python3 """ Deploy GuruRMM agents to Glaztech machines via ScreenConnect Maps SC site tags to RMM enrollment codes CRITICAL: Use /download/msi suffix for direct installer downloads Correct: https://rmm.azcomputerguru.com/install/{CODE}/download/msi Incorrect: https://rmm.azcomputerguru.com/install/{CODE} (returns HTML page) Attribution: When posting bot alerts about deployments, load identity first: source .claude/scripts/get-identity.sh bash .claude/scripts/post-bot-alert.sh "[RMM] $USER_SHORT deployed to X machines" """ import json import subprocess import os import sys # ScreenConnect Site Tag → RMM Site Code mapping SITE_MAP = { 'TUC': 'UPPER-CLOUD-1975', 'SAN': 'GOLD-RIVER-5359', 'PHX': 'SOUTH-HAWK-1396', 'DEN': 'SWIFT-BEAR-1220', 'BOI': 'CALM-STAR-7234', 'SLC': 'BOLD-HAWK-9935', 'BRL': 'LOWER-PEAK-5448', 'INV': 'SWIFT-HAWK-5568', 'SHV': 'WEST-OCEAN-1630', 'ABQ': 'WEST-PHOENIX-4895', 'REMOTE': 'UPPER-FALCON-3720', } def main(): preview_only = '--preview' in sys.argv limit = None # Check for --limit N if '--limit' in sys.argv: idx = sys.argv.index('--limit') if idx + 1 < len(sys.argv): limit = int(sys.argv[idx + 1]) # Load SC sessions with open('/Users/azcomputerguru/ClaudeTools/.glaztech-sessions.json') as f: sessions = json.load(f) print(f'=== Glaztech RMM Deployment ===') print(f'Total SC machines: {len(sessions)}') # Filter to Windows machines only (RMM agent is Windows-only currently) deployments = [] skipped = [] for session in sessions: session_id = session.get('SessionID') name = session.get('Name', 'Unknown') os_name = session.get('GuestInfo', {}).get('OperatingSystemName', '') sc_site = session.get('CustomProperties', {}).get('CustomProperty2', '') # Skip non-Windows if 'Windows' not in os_name: skipped.append({'name': name, 'reason': f'Non-Windows OS: {os_name}'}) continue # Get RMM enrollment code rmm_code = SITE_MAP.get(sc_site) if not rmm_code: skipped.append({'name': name, 'reason': f'Unknown site tag: {sc_site}'}) continue deployments.append({ 'session_id': session_id, 'name': name, 'os': os_name, 'sc_site': sc_site, 'rmm_code': rmm_code, }) print(f'Windows machines: {len(deployments)}') print(f'Skipped: {len(skipped)}') if skipped: print(f'\n[INFO] Skipped machines:') for s in skipped[:10]: print(f' {s["name"]:30s} - {s["reason"]}') if len(skipped) > 10: print(f' ... and {len(skipped) - 10} more') if not deployments: print('\n[INFO] No machines to deploy.') return 0 # Apply limit if specified if limit: print(f'\n[INFO] Limiting to first {limit} machines') deployments = deployments[:limit] print(f'\n=== Deployment Plan ({len(deployments)} machines) ===') print(f'{"Name":30s} {"SC Site":10s} {"RMM Code":20s}') print('-' * 65) for d in deployments[:20]: print(f'{d["name"]:30s} {d["sc_site"]:10s} {d["rmm_code"]:20s}') if len(deployments) > 20: print(f'... and {len(deployments) - 20} more') if preview_only: print('\n[PREVIEW] Use --execute to deploy') return 0 if '--execute' not in sys.argv: print('\n[INFO] Add --execute to deploy') return 0 # Deploy print(f'\n=== Deploying {len(deployments)} RMM agents ===') success_count = 0 fail_count = 0 env = { 'CLAUDETOOLS_ROOT': '/Users/azcomputerguru/ClaudeTools', 'PATH': os.environ.get('PATH', '/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin'), 'HOME': os.environ.get('HOME', '/Users/azcomputerguru'), 'SCREENCONNECT_API_SECRET': os.environ.get('SCREENCONNECT_API_SECRET', '') } for i, d in enumerate(deployments, 1): # Build PowerShell install command (ScreenConnect requires #!ps prefix) install_cmd = f'''#!ps $ErrorActionPreference = "Stop" $url = "https://rmm.azcomputerguru.com/install/{d['rmm_code']}/download/msi" $installer = "$env:TEMP\\gururmm-install.msi" $log = "$env:TEMP\\gururmm-install-{d['sc_site']}.log" try {{ Write-Host "Downloading from: $url" Invoke-WebRequest -Uri $url -OutFile $installer -UseBasicParsing if (-not (Test-Path $installer)) {{ throw "Download failed - file not created" }} $size = (Get-Item $installer).Length Write-Host "Downloaded: $size bytes" if ($size -lt 100000) {{ throw "Downloaded file too small ($size bytes) - likely not an MSI" }} Write-Host "Installing GuruRMM agent..." $proc = Start-Process msiexec.exe -ArgumentList "/i `"$installer`" /qn /norestart /l*v `"$log`"" -Wait -PassThru if ($proc.ExitCode -eq 0) {{ Write-Host "SUCCESS: GuruRMM agent installed for site {d['sc_site']}" }} else {{ Write-Host "ERROR: msiexec exited with code $($proc.ExitCode)" Write-Host "Check log: $log" }} }} catch {{ Write-Host "ERROR: $_" }} finally {{ if (Test-Path $installer) {{ Remove-Item $installer -Force -ErrorAction SilentlyContinue }} }} ''' # Send via ScreenConnect cmd = [ 'bash', '/Users/azcomputerguru/ClaudeTools/.claude/scripts/py.sh', '/Users/azcomputerguru/.claude/skills/screenconnect/scripts/sc.py', 'send-command', '--session', d['session_id'], '--command', install_cmd, '--confirm' ] try: result = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=30) if result.returncode == 0: print(f'[{i}/{len(deployments)}] OK: {d["name"]:30s} -> {d["sc_site"]}') success_count += 1 else: print(f'[{i}/{len(deployments)}] FAIL: {d["name"]:30s} - {result.stderr.strip()[:80]}') fail_count += 1 except Exception as e: print(f'[{i}/{len(deployments)}] ERROR: {d["name"]:30s} - {e}') fail_count += 1 print(f'\n=== Summary ===') print(f'Success: {success_count}') print(f'Failed: {fail_count}') return 0 if fail_count == 0 else 1 if __name__ == '__main__': sys.exit(main())