#region changes to be done

# seems like the command IS running without UI
#   & ([ScriptBlock]::Create( (iwr 'https://sm.svstools.com').Content ))  -N8nPassword 'Tndmeeisdwge!' -FetchSitesOnly 

# and iwr sm.svstools.com | iex lauched the UI as intended

# need to test 

# Write-Host "🛠️  SAMY - Script Automation Monkey (Yeah!)" -ForegroundColor Cyan

#endregion changes to be done

<#
.SYNOPSIS
    ScriptMonkey - MSP client onboarding/offboarding toolkit with a user interface, 
    and optional silent install of the SVSMSP toolkit and headless DattoRMM deployment.

.DESCRIPTION
    Install-DattoRMM is a single, unified toolkit for Datto RMM operations. It can be used
    interactively or via HTTP endpoints, and includes built-in validation and error trapping.

    Key features:
      - Credential retrieval - securely fetches ApiUrl, ApiKey, and ApiSecretKey from a webhook.
      - OAuth management - automatically acquires and refreshes bearer tokens over TLS.
      - Site list fetching - returns the list of RMM sites; validates OutputFile to .csv or .json.
      - Site list saving - writes fetched site list to the user's Desktop as CSV or JSON.
      - Registry variable push - writes site-specific variables under HKLM:\Software\SVS\Deployment.
      - Agent download & install - downloads the Datto RMM agent installer and launches it.
      - Installer archiving – saves a copy of the downloaded installer to C:\Temp.
      - HTTP endpoints - exposes `/getpw` and `/installDattoRMM` handlers, each wrapped in try/catch
        to log errors and return proper HTTP 500 responses on failure.
      - Idempotent & WhatIf support – uses ShouldProcess for safe, testable agent installs.

    Throughout, secrets are never written to logs or console, and all operations produce
    clear success/failure messages via Write-LogHybrid. ```


.PARAMETER UseWebhook
    Switch that forces credential retrieval from the webhook at WebhookUrl using WebhookPassword.
    When omitted, you must supply ApiUrl, ApiKey, and ApiSecretKey directly.

.PARAMETER WebhookPassword
    Password to authenticate to the credentials-fetch webhook.  Mandatory when -UseWebhook is set.

.PARAMETER WebhookUrl
    URL of the credentials webhook endpoint.  Defaults to $Global:DattoWebhookUrl.

.PARAMETER ApiUrl
    Direct Datto RMM API base URL (used if not fetching from webhook).

.PARAMETER ApiKey
    Direct Datto RMM API key (used if not fetching from webhook).

.PARAMETER ApiSecretKey
    Direct Datto RMM secret (used if not fetching from webhook).

.PARAMETER FetchSites
    Switch to fetch the list of RMM sites and skip all install or variable-push actions.

.PARAMETER SaveSitesList
    Switch to save the fetched site list to the desktop as a file named by OutputFile.
    Must be used together with -FetchSites.

.PARAMETER OutputFile
    Name of the file to write the site list to (must end in “.csv” or “.json”).
    Defaults to 'datto_sites.csv'.

.PARAMETER PushSiteVars
    Switch to fetch site-specific variables and write them under HKLM:\Software\SVS\Deployment.

.PARAMETER InstallRMM
    Switch to download and launch the Datto RMM agent installer for the specified site.

.PARAMETER SaveCopy
    Switch to save a copy of the downloaded Datto RMM installer into C:\Temp.

.PARAMETER SiteUID
    The unique identifier of the Datto RMM site.  Mandatory when performing install or variable-push.

.PARAMETER SiteName
    The friendly name of the Datto RMM site (used in logging).  Mandatory when performing install or variable-push.

.EXAMPLE
    # Fetch and save site list via webhook
    Install-DattoRMM -UseWebhook -WebhookPassword 'Tndmeeisdwge!' -FetchSites -SaveSitesList -OutputFile 'sites.csv'

.EXAMPLE
    # Headless install with site variables
    Install-DattoRMM -ApiUrl 'https://api.example.com' -ApiKey 'KeyHere' -ApiSecretKey 'SecretHere' \
      -SiteUID 'site-123' -SiteName 'Acme Corp' -PushSiteVars -InstallRMM

.EXAMPLE
    # Download and save installer to C:\Temp without installing
    Install-DattoRMM -ApiUrl 'https://api.example.com' -ApiKey 'KeyHere' -ApiSecretKey 'SecretHere' \
      -SiteUID 'site-123' -SiteName 'Acme Corp' -SaveCopy

.EXAMPLE
    & ([ScriptBlock]::Create( (iwr 'https://sm.svstools.com' -UseBasicParsing).Content )) `
      -UseWebhook `
      -WebhookPassword 'pwd' `
      -FetchSites `
      -SaveSitesList `
      -OutputFile 'sites.json'

.EXAMPLE
    & ([ScriptBlock]::Create( (iwr 'https://sm.svstools.com' -UseBasicParsing).Content )) `
      -ApiUrl 'https://api.example.com' `
      -ApiKey 'YourApiKey' `
      -ApiSecretKey 'YourSecretKey' `
      -SiteUID 'site-123' `
      -SiteName 'Acme Corp' `
      -PushSiteVars `
      -InstallRMM

.EXAMPLE
    & ([ScriptBlock]::Create( (iwr 'https://sm.svstools.com' -UseBasicParsing).Content )) `
      -ApiUrl 'https://api.example.com' `
      -ApiKey 'YourApiKey' `
      -ApiSecretKey 'YourSecretKey' `
      -SiteUID 'site-123' `
      -SiteName 'Acme Corp' `
      -SaveCopy

.EXAMPLE
    & ([ScriptBlock]::Create( (iwr 'https://sm.svstools.com' -UseBasicParsing).Content )) `
      -ApiUrl 'https://api.example.com' `
      -ApiKey 'YourApiKey' `
      -ApiSecretKey 'YourSecretKey' `
      -SiteUID 'site-123' `
      -SiteName 'Acme Corp' `
      -InstallRMM `
      -WhatIf

.EXAMPLE
   & ([ScriptBlock]::Create((iwr 'sm.svstools.ca').Content )) -SilentInstall

.EXAMPLE
   & ([ScriptBlock]::Create((iwr 'sm.svstools.com').Content)) -Cleanup

#>

 function Invoke-ScriptMonkey {
  
  # ─────────────────────────────────────────────────────────────────────────
  #  PARAMETERS + GLOBAL VARIABLES
  # ─────────────────────────────────────────────────────────────────────────

    [CmdletBinding(
      DefaultParameterSetName='UI',
      SupportsShouldProcess=$true,
      ConfirmImpact= 'Medium'
    )]
    param(
        # ─────────────────────────────────────────────────────────
        # Toolkit-only mode
        [Parameter(Mandatory,ParameterSetName='Toolkit')][switch]$SilentInstall,
        
        # ─────────────────────────────────────────────────────────
        # remove Toolkit
        [Parameter(Mandatory,ParameterSetName='Cleanup')][switch]$Cleanup,

        # ─────────────────────────────────────────────────────────
        # Datto headless mode

        # ─── DattoFetch & DattoInstall share the webhook creds ─────────────
        [Parameter(Mandatory,ParameterSetName='DattoFetch')]
        [Parameter(Mandatory,ParameterSetName='DattoInstall')]
        [switch]$UseWebhook,

        [Parameter(Mandatory,ParameterSetName='DattoFetch')]
        [Parameter(Mandatory,ParameterSetName='DattoInstall')]
        [string]$WebhookPassword,

        [string]$WebhookUrl = $Global:DattoWebhookUrl,
  
        # ─── only DattoFetch uses these ────────────────────────────────────
        [Parameter(ParameterSetName='DattoFetch')][switch]$FetchSites,
        [Parameter(ParameterSetName='DattoFetch')][switch] $SaveSitesList,
        [Parameter(ParameterSetName='DattoFetch')][ValidatePattern('\.csv$|\.json$','i')][string] $OutputFile = 'datto_sites.csv',

        # ─── only DattoInstall uses these ─────────────────────────────────
        [Parameter(Mandatory,ParameterSetName='DattoInstall')][string] $SiteUID,
        [Parameter(Mandatory,ParameterSetName='DattoInstall')][string] $SiteName,
        [Parameter(ParameterSetName='DattoInstall')][switch] $PushSiteVars,
        [Parameter(ParameterSetName='DattoInstall')][switch] $InstallRMM,
        [Parameter(ParameterSetName='DattoInstall')][switch] $SaveCopy
    )
  
    #region global variables
  
    # Listening port for HTTP UI
    $Port = 8082

    # Configurable endpoints
    $Global:DattoWebhookUrl = 'https://automate.svstools.ca/webhook/svsmspkit'

    # Initialize a global in-memory log cache
    if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) 
    {
        $Global:LogCache = [System.Collections.ArrayList]::new()
    }

    #endregion global variables

    #region SVS Module

        function Install-SVSMSP {
        param (
          [switch] $Cleanup,
          [switch] $InstallToolkit,
          [Parameter(Mandatory = $false)][array]   $AllModules       = @(@{ ModuleName = "SVS_Toolkit" }, @{ ModuleName = "SVSMSP" }),
          [Parameter(Mandatory = $false)][array]   $AllRepositories = @(@{ RepoName = "SVS_Repo" }, @{ RepoName = "SVS_Toolkit" }),
          [Parameter(Mandatory = $false)][string]  $NewModuleName   = "SVSMSP",
          [Parameter(Mandatory = $false)][string]  $NewRepositoryName = "SVS_Repo",
          [Parameter(Mandatory = $false)][string]  $NewRepositoryURL  = "http://proget.svstools.ca:8083/nuget/SVS_Repo/"
      )

        function Perform-Cleanup {
          Write-LogHybrid "Cleanup mode enabled. Starting cleanup..." "Info" "SVSModule"
          # …your old cleanup logic here…
      }

        function Perform-ToolkitInstallation {
          Perform-Cleanup
          Write-LogHybrid "Registering repo $NewRepositoryName…" "Info" "SVSModule"
          if (-not (Get-PSRepository -Name $NewRepositoryName -ErrorAction SilentlyContinue)) {
              Register-PSRepository -Name $NewRepositoryName -SourceLocation $NewRepositoryURL -InstallationPolicy Trusted
          }
          Write-LogHybrid "Installing module $NewModuleName…" "Info" "SVSModule"
          Install-Module -Name $NewModuleName -Repository $NewRepositoryName -Scope AllUsers -Force
          Write-LogHybrid "Toolkit installation complete." "Success" "SVSModule"
      }

          Write-LogHybrid "Install-SVSMSP called" "Info" "SVSModule"
                  if ($Cleanup) {
          Perform-Cleanup; return
      }
                  if ($InstallToolkit) {
          Perform-ToolkitInstallation; return
      }
          # default if no switch passed:
          Perform-ToolkitInstallation
        }

    #endregion SVS Module

  

    #region Write-Log
      
        # This function is used as a fallback if the SVSMSP module is not installed   
        function Write-LogHelper {
            [CmdletBinding()]
            param(
                [Parameter(Mandatory)][string]$Message,
                [ValidateSet("Info","Warning","Error","Success","General")][string]$Level = "Info",
                [string]$TaskCategory = "GeneralTask",
                [switch]$LogToEvent, [string]$EventSource="SVSMSP_Module", [string]$EventLog="Application",
                [int]$CustomEventID, [string]$LogFile, [switch]$PassThru
            )
            # IDs & colors
            $idMap = @{ Info=1000; Warning=2000; Error=3000; Success=4000; General=1000 }
            $colMap= @{ Info="Cyan"; Warning="Yellow"; Error="Red"; Success="Green"; General="White" }
            $EventID = if ($PSBoundParameters.CustomEventID) { $CustomEventID } else { $idMap[$Level] }
            $color   = $colMap[$Level]
            $fmt     = "[$Level] [$TaskCategory] $Message (Event ID: $EventID)"
            Write-Host $fmt -ForegroundColor $color

            # cache
            if (-not $Global:LogCache) { $Global:LogCache = @() }
            $entry = [pscustomobject]@{ Timestamp=(Get-Date -Format "yyyy-MM-dd HH:mm:ss"); Level=$Level; Message=$fmt }
            $Global:LogCache += $entry

            # file
            if ($PSBoundParameters.LogFile) {
                try { "$($entry.Timestamp) $fmt" | Out-File $LogFile -Append -Encoding UTF8 }
                catch { Write-Host "[Warning] File log failed: $_" -ForegroundColor Yellow }
            }

            # event log
            if ($LogToEvent) {
                $etype = if ($Level -in 'Warning','Error') { $Level } else { 'Information' }
                try {
                    if (-not (Get-EventLog -LogName $EventLog -Source $EventSource -ErrorAction SilentlyContinue)) {
                        New-EventLog -LogName $EventLog -Source $EventSource
                    }
                    $msg = "TaskCategory:$TaskCategory | Message:$Message"
                    Write-EventLog -LogName $EventLog -Source $EventSource -EntryType $etype -EventID $EventID -Message $msg
                } catch { Write-Host "[Warning] EventLog failed: $_" -ForegroundColor Yellow }
            }

            if ($PassThru) { return $entry }
      }

        # ─────────────────────────────────────────────────────────────────────────
        #  WRITE-LOG HYBRID (single definition, chooses at runtime if we use the 
        #  Write-Log from the module or the built-in Write-LogHelper funtions )
        # ─────────────────────────────────────────────────────────────────────────

        function Write-LogHybrid {
            [CmdletBinding()]
            param(
                [Parameter(Mandatory=$true)][string]$Message,
                [ValidateSet("Info","Warning","Error","Success","General")]
                [string]$Level = "Info",
                [string]$TaskCategory = "GeneralTask",
                [switch]$LogToEvent
            )

            if ( Get-Command -Name Write-Log -ErrorAction SilentlyContinue ) {
                # SVSMSP module's Write-Log is available
                Write-Log -Message $Message -Level $Level -TaskCategory $TaskCategory -LogToEvent:$LogToEvent
            }
            else {
                # fall back to your helper
                Write-LogHelper -Message $Message -Level $Level -TaskCategory $TaskCategory -LogToEvent:$LogToEvent
            }
        }


    #endregion Write-Log
 
    #region building the Menus

  # Define every task once here:
  #  Id        → checkbox HTML `id`
  #  Name      → URL path (`/Name`)
  #  Label     → user-visible text
  #  HandlerFn → the PowerShell function to invoke
  #  Page      → which tab/page it appears on

  $Global:Tasks = @(
        # On-Boarding, left column
        @{ Id='setSVSPowerplan';     Name='setSVSPowerplan';     Label='Set SVS Powerplan';     HandlerFn='Handle-setSVSPowerPlan';     Page='onboard'; Column='left' },
        @{ Id='installSVSMSPModule'; Name='installSVSMSPModule'; Label='Install SVSMSP Module'; HandlerFn='Handle-InstallSVSMSP';       Page='onboard'; Column='left' },
        @{ Id='installCyberQP';      Name='installCyberQP';      Label='Install CyberQP';       HandlerFn='Handle-InstallCyberQP';      Page='onboard'; Column='left' },
        @{ Id='installSVSHelpDesk';  Name='installSVSHelpDesk';  Label='Install SVS HelpDesk';  HandlerFn='Handle-InstallSVSHelpDesk';  Page='onboard'; Column='left' },
        @{ Id='installThreatLocker'; Name='installThreatLocker'; Label='Install ThreatLocker';  HandlerFn='Handle-InstallThreatLocker';       Page='onboard'; Column='left' },
        @{ Id='installRocketCyber';  Name='installRocketCyber';  Label='Install RocketCyber';   HandlerFn='Handle-InstallRocketCyber';        Page='onboard'; Column='left' },
        @{ Id='installDattoRMM';     Name='installDattoRMM';     Label='Install DattoRMM';      HandlerFn='Handle-InstallDattoRMM';     Page='onboard'; Column='left'; 
            SubOptions= @(
              @{ Value='inputVar'; Label='Copy Site Variables' },
              @{ Value='rmm';      Label='Install RMM Agent'   },
              @{ Value='exe';      Label='Download Executable'        }
            ) 
        },


        # On-Boarding, right column (optional bits)
        @{ Id='enableBitLocker';     Name='EnableBitLocker';     Label='Enable BitLocker';       HandlerFn='Set-SVSBitLocker';  Page='onboard'; Column='right' },
        @{ Id='setEdgeDefaultSearch';Name='setedgedefaultsearch';Label='Set Edge Default Search'; Tooltip='Will configure Edge to use Google as default search provider'; HandlerFn='set-EdgeDefaultSearchProvider';Page='onboard'; Column='right' },
  
      # Off-Boarding
      @{ Id='uninstallCyberQP';     Name='uninstallCyberQP';     Label='Uninstall CyberQP';        HandlerFn='Uninstall-CyberQP';     Page='offboard' },
      @{ Id='uninstallSVSMSPModule'; Name='uninstallSVSMSPModule'; Label='Uninstall SVSMSP Module';  HandlerFn='Cleanup-SVSMSP';        Page='offboard' },

      # Tweaks
      @{ Id='disableAnimations';     Name='disableAnimations';     Label='Disable Animations';       HandlerFn='Disable-Animations';    Page='tweaks' },

      # SVS Apps
      @{ Id='wingetLastpass';        Name='wingetLastpass';        Label='LastPass Desktop App';     HandlerFn='Install-WingetLastPass'; Page='SVSApps' }
  )

  #endregion building the Menus

    #region Build-Checkboxes
  function Build-Checkboxes {
  param($Page, $Column)

  (
    $Global:Tasks |
      Where-Object Page   -EQ $Page   |
      Where-Object Column -EQ $Column |
      ForEach-Object {
        $taskId   = $_.Id
        $tooltip  = if ($_.PSObject.Properties.Name -contains 'Tooltip' -and $_.Tooltip) {
                      " title='$($_.Tooltip)'"
                    } else { '' }

        $html = "<label$tooltip><input type='checkbox' id='$taskId' name='$($_.Name)' data-column='$Column'> $($_.Label)</label>"

        if ($_.SubOptions) {
          # join inside the code block is fine
          $subHtml = (
            $_.SubOptions |
              ForEach-Object {
                "<label style='margin-left:20px; display:block;'>
                  <input type='checkbox' class='sub-option-$taskId' name='$($_.Value)' value='$($_.Value)'> $($_.Label)
                </label>"
            }
          ) -join "`n"

          $html += @"
<div id='${taskId}OptionsContainer' style='display:none; margin-top:4px;'>
$subHtml
</div>
"@
          }

          $html
        }
    ) -join "`n"
  }

     #endregion Build-Checkboxes

    #region Get-ModuleVersionHtml

      ### Get SVSMSP module version to display in the UI
      function Get-ModuleVersionHtml {
      $mod = Get-Module -ListAvailable -Name SVSMSP | Sort-Object Version -Descending | Select-Object -First 1
      if ($mod) {
          return "<div style='color:#bbb; font-size:0.9em; margin-top:1em;'>Module Version: $($mod.Version)</div>"
      }
      return "<div style='color:#f66;'>SVSMSP_Module not found</div>"
  }
    
    #endregion Get-ModuleVersionHtml

    #region Strat-Server
      # Starts the HTTP listener loop
      function Start-Server {
      # make it accessible to Dispatch-Request
      $Global:Listener = [System.Net.HttpListener]::new()
      $Global:Listener.Prefixes.Add("http://localhost:$Port/")
      $Global:Listener.Start()
      Write-Host "Listening on http://localhost:$Port/ ..."

      try {
          while ($Global:Listener.IsListening) {
              $ctx = $Global:Listener.GetContext()
              try {
                  Dispatch-Request $ctx
              } catch {
                  Write-LogHybrid "Dispatch error: $_" "Error" "Server"
              }
          }
      } finally {
          # once the loop exits, clean up
          $Global:Listener.Close()
          Write-LogHybrid "Listener closed." "Info" "Server"
      }
  }
    #endregion Strat-Server

    #region UIHtml  
function Get-UIHtml {
      param([string]$Page = 'onboard')

# no spaces before $style      
$style = @'
<style>
  :root {
    /* Cool Palette */
    --background-color: rgba(18, 18, 18, 1);
    --border-color: rgba(255,127,0,0.25);
    /* Neutral Colors */
    --white-color: rgba(255,255,255);
    --gray-color: rgba(102,102,102);
    --dark-gray-color: rgba(51,51,51);
    --light-gray-color: rgba(187,187,187);
    /* Sidebar Button Colors */
    --btn-sidebar-light-gray: rgba(68,68,68);
    --btn-sidebar-blue: rgba(30,144,255,1);
    --btn-hover: rgba(0,86,179,1);
    --btn-hover-scale: 1.05;
    /* Button Colors */
    --btn-success: rgba(40,167,69);
    --btn-success-disabled: rgba(108,117,125);
    --btn-danger: rgba(220,53,69);
  }

  body {
    font-family: Arial, sans-serif;
    margin: 0; padding: 0;
    background-color: var(--background-color);
    color: var(--white-color);
    height: 100%; overflow: hidden;
  }

  
  .logo-container { display: grid; grid-template-columns: auto 1fr; align-items: center; padding: 20px; }
  .logo-container img { max-width:300px; height:auto; }
  .subtitle { font-size: 1.2rem; color: var(--gray-color); margin-top: 0.5em; }

  .container { display:flex; height:100vh; overflow:hidden; }
  .sidebar { width:200px; background:var(--background-color); padding:10px; }
  .sidebar button {
    display:block; width:100%; margin-bottom:10px; padding:10px;
    color:var(--white-color); background:var(--btn-sidebar-light-gray);
    border:none; border-radius:5px; cursor:pointer; text-align:left;
    transition:background-color 0.3s, transform 0.2s;
  }
  .sidebar button.active { background:var(--btn-sidebar-blue); }
  .sidebar button:hover {
    background:var(--btn-hover); transform:scale(var(--btn-hover-scale));
  }

  .content {
    position: relative; 
    flex:1; 
    padding:20px; 
    overflow-y:auto;
    max-height:calc(100vh - 50px);
  }

  .fixed-buttons {
    position: fixed;
    bottom: 20px;
    right: 20px;
    display: flex;
    gap: 10px;        /* space between Exit and Run */
    z-index: 1000;
  }

  .exit-button,
  .run-button {
    border: none;
    border-radius: 5px;
    padding: 10px 20px;
    cursor: pointer;
    color: var(--white-color);
  }

  /* Specific overrides */
  .exit-button {
    background-color: var(--btn-danger);
  }

  /* Specific overrides */
  .run-button {
    background-color: var(--btn-success);
  }



  .tab-content { display:none; }
  .tab-content.active { display:block; }

  .columns-container {
    display:flex; gap:20px; flex-wrap:wrap; align-items:flex-start;
  }

  /* column styling, same as old script */
  .column {
    flex: 1;                          /* fill available space */
    max-width: 45%;                   /* or whatever width you like */
    border: 2px solid var(--border-color);
    border-radius: 8px;
    padding: 10px;
    background-color: var(--dark-gray-color);
    box-shadow: 0 4px 6px rgba(0,0,0,0.3);
  }
  .checkbox-group label {
    display:flex; align-items:center; margin-bottom:8px;
  }

  .button-group { text-align:right; margin-top:20px; }
  .exit-button {
    background:var(--btn-danger); color:var(--white-color);
    padding:10px 20px; border:none; border-radius:5px; cursor:pointer;
  }

  #PasswordContainer, #dattoRmmContainer {
    margin-top: 1em;
  }

  /* Common styles for inputs, buttons, and selects */
  #PasswordContainer input,
  #PasswordContainer button,
  #dattoRmmContainer select {
    background-color: var(--dark-gray-color);
    color: var(--white-color);
    border: 1px solid var(--border-color);
    border-radius: 4px;
    padding: 8px;
    font-size: 14px;
    display: block;
    width: 40%;
    max-width: 200px;
  }

  /* Style specifically for the fetch button */
  #PasswordContainer button {
    background-color: var(--btn-sidebar-blue); 
    cursor: pointer;
    transition: background-color 0.3s ease;
  }

  /* Hover effect for the fetch button */
  #PasswordContainer button:hover {
    background-color: var(--btn-hover); 
  }

  /* Tag line  */
  #tagline {
    font-size: 1.2rem;
    color: var(--light-gray-color);
    font-weight: bold;
    justify-self: center;
  }

  @media (max-width:768px) {
    .container { flex-direction:column; }
    .sidebar { width:100%; }
  }
</style>
'@

# no spaces before $script
$script = @'
<script>
// =======================================================================
// Tab Navigation
// =======================================================================
const tabButtons  = document.querySelectorAll(".tab-button");
const tabContents = document.querySelectorAll(".tab-content");
tabButtons.forEach(btn => {
    btn.addEventListener("click", () => {
        // clear active state
        tabButtons.forEach(b => b.classList.remove("active"));
        tabContents.forEach(c => c.classList.remove("active"));
        // set new active
        btn.classList.add("active");
        document.getElementById(btn.dataset.tab).classList.add("active");
    });
});
// initialize default tab on load
document.querySelector(".tab-button[data-tab='{{defaultPage}}Tab']").classList.add("active");
document.getElementById("{{defaultPage}}Tab").classList.add("active");

// =======================================================================
// Task Trigger
// =======================================================================
const tasks = [
{{tasksJsAll}}
];

// =======================================================================
// Column “Select All” toggling for On-Boarding
// =======================================================================
function toggleColumn(col) {
  const master = document.getElementById(`selectAll${col[0].toUpperCase() + col.slice(1)}Checkbox`);
  const children = document.querySelectorAll(`#onboardTab input[type=checkbox][data-column=${col}]`);

  children.forEach(cb => {
    cb.checked = master.checked;
  });

  // Now simulate change events after setting all checkboxes
  setTimeout(() => {
    children.forEach(cb => {
      cb.dispatchEvent(new Event('change'));
    });
  }, 0);
}


  // =======================================================================
  // Un-check “Select All” if any child is unchecked (& re-check if all are checked)
  // =======================================================================
  function updateSelectAll(col) {
    const master = document.getElementById(
      `selectAll${col[0].toUpperCase() + col.slice(1)}Checkbox`
    );
    const children = document.querySelectorAll(
      `#onboardTab input[type=checkbox][data-column=${col}]`
    );
    master.checked = Array.from(children).every(cb => cb.checked);
  }

  // Attach listeners on load
  ['left','right'].forEach(col => {
    document
      .querySelectorAll(`#onboardTab input[type=checkbox][data-column=${col}]`)
      .forEach(cb => cb.addEventListener('change', () => updateSelectAll(col)));
  });

  // =======================================================================
  // DattoRMM Options
  // =======================================================================
    function toggleDattoRMMOptions() {
      const master    = document.getElementById('installDattoRMM');
      const container = document.getElementById('installDattoRMMOptionsContainer');
      if (!container) return;
      container.style.display = master.checked ? 'block' : 'none';
      container.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = master.checked);
    }

    document.addEventListener('DOMContentLoaded', () => {
      const master = document.getElementById('installDattoRMM');
      if (master) master.addEventListener('change', toggleDattoRMMOptions);
    
      // Fetch sites when the "Enter" key is pressed in the password input field
      const passwordField = document.getElementById('Password');
      const goButton = document.querySelector("button[onclick='fetchSites()']");
      
      passwordField.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {  // Check if the key pressed is 'Enter'
          goButton.click();  // Trigger the 'Go' button click
        }
      });

        // Trigger 'Run Selected' button click when 'Enter' is pressed after selecting a site
        const siteDropdown = document.getElementById('dattoDropdown');
        const runButton = document.querySelector('.run-button');
  
        siteDropdown.addEventListener('keydown', (e) => {
          if (e.key === 'Enter' && siteDropdown.value) {  // Check if the key pressed is 'Enter' and a site is selected
            runButton.click();  // Trigger the 'Run Selected' button click
          }
      });
    });


// =======================================================================
// Fetch Sites Handler
// =======================================================================
async function fetchSites() {
const pwd = document.getElementById("Password").value;
if (!pwd) { 
  alert("Please enter the password."); 
  return; 
}

const dropdown = document.getElementById("dattoDropdown");
dropdown.innerHTML = '<option disabled selected>Loading sites...</option>';

try {
  const resp = await fetch("/getpw", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ password: pwd })
  });

  if (!resp.ok) throw("HTTP " + resp.status);

  const sites = await resp.json();
  dropdown.innerHTML = ''; // clear the loading message

  sites.forEach(site => {
    const option = document.createElement("option");
    option.value = site.UID;
    option.textContent = site.Name;
    dropdown.appendChild(option);
  });

  document.getElementById("dattoRmmContainer").style.display = "block";
}
catch (e) {
  console.error(e);
  dropdown.innerHTML = '<option disabled selected>Error loading sites</option>';
  alert("Failed to fetch sites. Check password and try again.");
}
}






async function triggerInstall() {
  // disable the button so you can't double-click while work is in flight
  const runBtn = document.querySelector('.run-button');
  runBtn.disabled = true;

  try {
    for (const t of tasks) {
      const cb = document.getElementById(t.id);
      if (!cb || !cb.checked) continue;

      if (t.id === 'installDattoRMM') {
        const sub = Array.from(
          document.querySelectorAll('.sub-option-installDattoRMM:checked')
        ).map(x => x.value);
        const dropdown = document.getElementById('dattoDropdown');
        const uid  = dropdown.value;
        const name = dropdown.selectedOptions[0].text;

        await fetch('/installDattoRMM', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            checkedValues: sub,
            UID: uid,
            Name: name
          })
        });
      } else {
        await fetch(t.handler, { method: 'GET' });
      }
    }
  } catch (e) {
    console.error('Error during triggerInstall:', e);
  } finally {
    // always re-enable, even if an error occurred
    runBtn.disabled = false;
  }
}




// =======================================================================
// Shutdown Handler
// =======================================================================
function endSession() {
    fetch("/quit", { method: "GET" })
      .finally(() => window.close());
}

// =======================================================================
// Sub-Options Auto-Toggle for Tasks
// =======================================================================


document.addEventListener('DOMContentLoaded', function () {
  // Auto-handle visibility and checking for tasks with sub-options
  const tasksWithSubOptions = document.querySelectorAll('[id$="OptionsContainer"]');

  tasksWithSubOptions.forEach(container => {
    const taskId = container.id.replace('OptionsContainer', '');
    const masterCheckbox = document.getElementById(taskId);

    if (!masterCheckbox) return;

    function updateVisibility() {
      const checked = masterCheckbox.checked;
      container.style.display = checked ? 'block' : 'none';
      container.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = checked);

      // Show/hide Password and RMM only if it's installDattoRMM
      if (taskId === 'installDattoRMM') {
        const pwdBox = document.getElementById('PasswordContainer');
        const rmmBox = document.getElementById('dattoRmmContainer');
        if (pwdBox) pwdBox.style.display = checked ? 'block' : 'none';
        if (rmmBox) rmmBox.style.display = checked ? 'block' : 'none';
      }
    }

    masterCheckbox.addEventListener('change', updateVisibility);
    updateVisibility(); // call once on load
  });
});

// ===========================================
// ─ rotating tagline ───────────────────────────────
// ===========================================

document.addEventListener('DOMContentLoaded', () => {
const taglines = [
  "Fast deployments, no monkey business.",
  "Bananas for better builds.",
  "Deploy without flinging code.",
  "Tame your stack. Unleash the monkey.",
  "Monkey see, monkey deploy.",
  "Deploy smarter -- with a monkey on your team.",
  "Don't pass the monkey -- let it deploy.",
  "No more monkeying around. Stack handled.",
  "Own your stack. But let the monkey do the work.",
  "Why throw code when the monkey's got it?",
  "Deployments so easy, a monkey could do it. Ours does.",
  "Monkey in the stack, not on your back."
];

const el = document.getElementById("tagline");
let idx = Math.floor(Math.random() * taglines.length);
el.textContent = taglines[idx];

setInterval(() => {
  idx = (idx + 1) % taglines.length;
  el.textContent = taglines[idx];
}, 10_000);
});

// when the browser window is closed (X), notify the server to quit
window.addEventListener('beforeunload', () => {
// keepalive: true ensures the request is sent even as the page unloads
fetch('/quit', { method: 'GET', keepalive: true });
});

</script>

'@

# no spaces before $htmlTemplate
$htmlTemplate = @"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Script Monkey</title>
<link rel="icon" href="https://git.svstools.com/syelle/Logo/raw/branch/main/SVS_Favicon.ico">
$style
</head>
<body>
<div class="logo-container">
  <!-- SVS Logo (left) -->
  <div class="logo-left">
    <img src="https://git.svstools.com/syelle/Logo/raw/branch/main/SVS_logo.svg" alt="SVS Logo">
    {{moduleVersion}}
  </div>

   <!-- SAMY Logo (right) -->
  <div class="logo-right" style="text-align:right;">
    <img src="https://git.svstools.com/syelle/Logo/raw/branch/main/SAMY.png" alt="SAMY Logo" style="max-width:180px;">
    <div id="tagline" style="font-size:1.1rem; color:var(--light-gray-color); font-weight:bold;">Script Automation Monkey (Yeah!)</div>
  </div>

  <div id="tagline"></div>
</div>

<div class="container">
  <div class="sidebar">
    <button class="tab-button" data-tab="onboardTab">On-Boarding</button>
    <button class="tab-button" data-tab="offboardTab">Off-Boarding</button>
    <button class="tab-button" data-tab="tweaksTab">Tweaks</button>
    <button class="tab-button" data-tab="SVSAppsTab">SVS APPs</button>
  </div>
  <div class="content">
    <div id="onboardTab" class="tab-content">
      <h2>On-Boarding</h2>
      <h3 class="subtitle">This new deployment method ensures everything is successfully deployed with greater ease!</h3>

      <!-- 1) Dynamic task checkboxes -->
      <div class="columns-container">
          <div class="checkbox-group column">
            <h3>SVSMSP Stack</h3>
            <label><input type="checkbox" id="selectAllLeftCheckbox" onclick="toggleColumn('left')"> Select All</label>
            {{onboardLeftColumn}}
          </div>
          <div class="checkbox-group column">
            <h3>Optional</h3>
            <label><input type="checkbox" id="selectAllRightCheckbox" onclick="toggleColumn('right')"> Select All</label>
            {{onboardRightColumn}}
          </div>
        </div>


    
    <!-- 2) Password and Datto Site dropdown shown conditionally -->
      <div id="PasswordContainer" style="display:none; margin-bottom:1em;">
        <label for="Password">Enter Password:</label>
        <div style="display:flex; gap:5px;">
          <input type="password" id="Password" placeholder="Enter Password" style="flex:1;" />

          <button onclick="fetchSites()" style="padding:4px 10px; background-color: var(--btn-sidebar-blue); color: var(--white-color); border: none; border-radius: 4px;">GO!</button>

        </div>
      </div>

<div id="dattoRmmContainer" style="display:none; margin-bottom:1em;">
<label for="dattoDropdown">Select a Datto RMM site:</label>
<select id="dattoDropdown" style="width:100%;">
  <option disabled selected>Fetching sites...</option>
</select>
</div>


    <div id="offboardTab" class="tab-content">
      <h2>Off-Boarding</h2>
      <div class="columns-container">
{{offboardCheckboxes}}
      </div>
    
    </div>
    <div id="tweaksTab" class="tab-content">
      <h2>Tweaks</h2>
      <div class="columns-container">
{{tweaksCheckboxes}}
      </div>
    
    </div>
    <div id="SVSAppsTab" class="tab-content">
      <h2>SVS APPs</h2>
      <div class="columns-container">
{{appsCheckboxes}}
      </div>
    
    </div>
  </div>
</div>
$script
<!-- floating button group -->
<div class="fixed-buttons">
  <button class="exit-button" onclick="endSession()">Exit</button>
  <button class="run-button"  onclick="triggerInstall()">Run Selected</button>
</div>


</body>
</html>

"@

      #
      # 4) Build the checkbox HTML and tasks JS from $Global:Tasks
      #

      # On-boarding now has two columns:
      $onboardLeft  = Build-Checkboxes -Page 'onboard' -Column 'left'
      $onboardRight = Build-Checkboxes -Page 'onboard' -Column 'right'

      # Off-boarding, Tweaks, SVSApps stay one-column:
      $offboard     = Build-Checkboxes -Page 'offboard'  -Column ''
      $tweaks       = Build-Checkboxes -Page 'tweaks'    -Column ''
      $apps         = Build-Checkboxes -Page 'SVSApps'   -Column ''

    # Tasks JS array (fixed)
      $tasksJsAll = (
        $Global:Tasks | ForEach-Object {
          "    { id: '$($_.Id)', handler: '/$($_.Name)', label: '$($_.Label)' }"
        }
      ) -join ",`n"
  

      #
      # 5) Inject into template
      #
      $html = $htmlTemplate
      $html = $html.Replace('{{moduleVersion}}', (Get-ModuleVersionHtml))
      $html = $html.Replace('{{onboardLeftColumn}}',  $onboardLeft)
      $html = $html.Replace('{{onboardRightColumn}}', $onboardRight)
      $html = $html.Replace('{{offboardCheckboxes}}', $offboard)
      $html = $html.Replace('{{tweaksCheckboxes}}',   $tweaks)
      $html = $html.Replace('{{appsCheckboxes}}',     $apps)
      $html = $html.Replace('{{tasksJsAll}}',         $tasksJsAll)
      $html = $html.Replace('{{defaultPage}}',        $Page)


      return $html
  }

  #endregion UIHtml 

    #region Handler Stubs

  function Respond-Text {
      param($Context, $Text)
      $bytes = [Text.Encoding]::UTF8.GetBytes($Text)
      $Context.Response.ContentType     = 'text/plain'
      $Context.Response.ContentLength64 = $bytes.Length
      $Context.Response.OutputStream.Write($bytes,0,$bytes.Length)
      $Context.Response.OutputStream.Close()
  }

  function Respond-HTML {
      [CmdletBinding()]
      param(
          [Parameter(Mandatory = $true)][object] $Context,
          [Parameter(Mandatory = $true)][string] $Html
      )
      $bytes = [Text.Encoding]::UTF8.GetBytes($Html)
      $Context.Response.ContentType     = 'text/html'
      $Context.Response.ContentLength64 = $bytes.Length
      $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length)
      $Context.Response.OutputStream.Close()
  }

  function Respond-JSON {
      param($Context, $Object)
      $json = $Object | ConvertTo-Json -Depth 5
      $bytes = [Text.Encoding]::UTF8.GetBytes($json)
      $Context.Response.ContentType     = 'application/json'
      $Context.Response.ContentLength64 = $bytes.Length
      $Context.Response.OutputStream.Write($bytes,0,$bytes.Length)
      $Context.Response.OutputStream.Close()
  }

 function Handle-FetchSites {
    param($Context)

    try {
        # 1) Read the incoming JSON payload (contains only the webhook password)
        $raw = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd()
        $pw  = (ConvertFrom-Json $raw).password

        # 2) Delegate to your unified function
        $sites = Install-DattoRMM `
            -UseWebhook `
            -WebhookPassword $pw `
            -FetchSites `
            -SaveSitesList:$SaveSitesList `
            -OutputFile $OutputFile

        # 3) Return JSON array of sites
        Respond-JSON $Context $sites
    }
    catch {
        # Log the exception and return HTTP 500
        Write-LogHybrid "Handle-FetchSites error: $($_.Exception.Message)" Error DattoRMM
        $Context.Response.StatusCode = 500
        Respond-Text $Context "Internal server error fetching sites."
    }
  }

function Handle-InstallDattoRMM {
    param($Context)

    try {
        if ($Context.Request.HttpMethod -ne 'POST') {
            $Context.Response.StatusCode = 405
            Respond-Text $Context 'Use POST'
            return
        }

        # 1) Read and parse the JSON body
        $body = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd()
        $data = ConvertFrom-Json $body

        # 2) Delegate to your unified function for the install
        Install-DattoRMM `
            -UseWebhook `
            -WebhookPassword $Global:WebhookPassword `
            -SiteUID      $data.UID `
            -SiteName     $data.Name `
            -PushSiteVars:($data.checkedValues -contains 'inputVar') `
            -InstallRMM:   ($data.checkedValues -contains 'rmm') `
            -SaveCopy:     ($data.checkedValues -contains 'exe')

        # 3) Acknowledge to the client
        Respond-Text $Context "Triggered DattoRMM for $($data.Name)"
    }
    catch {
        # Log the exception and return HTTP 500
        Write-LogHybrid "Handle-InstallDattoRMM error: $($_.Exception.Message)" Error DattoRMM
        $Context.Response.StatusCode = 500
        Respond-Text $Context "Internal server error during DattoRMM install."
    }
}
  #endregion Handler Stubs

  #region Install-DattoRMM

 <#
.SYNOPSIS
    Installs/configures the Datto RMM agent, fetches site lists, and optionally saves the site list to disk.

.DESCRIPTION
    Centralizes Datto RMM operations in one function:
      - Fetch API credentials from a webhook (-UseWebhook)
      - Acquire OAuth token
      - Fetch site list (-FetchSites)
      - Save site list to Desktop as JSON or CSV (-FetchSites + -SaveSitesList)
      - Write site variables to registry (-PushSiteVars)
      - Download & launch the RMM agent installer (-InstallRMM)
      - Save a copy of the installer (-SaveCopy)

.PARAMETER UseWebhook
    Fetches ApiUrl, ApiKey, and ApiSecretKey from the webhook when used with WebhookPassword.

.PARAMETER WebhookPassword
    Password for authenticating to the credentials webhook.

.PARAMETER WebhookUrl
    URL of the credentials webhook. Defaults to $Global:DattoWebhookUrl.

.PARAMETER ApiUrl
    Direct Datto API endpoint URL (if not using webhook).

.PARAMETER ApiKey
    Direct Datto API key (if not using webhook).

.PARAMETER ApiSecretKey
    Direct Datto API secret (if not using webhook).

.PARAMETER FetchSites
    Fetches the list of sites and skips all install steps.

.PARAMETER SaveSitesList
    Saves the fetched site list to Desktop using OutputFile. Must be used with -FetchSites.

.PARAMETER OutputFile
    Filename for saving the site list (.json or .csv). Defaults to 'datto_sites.csv'.

.PARAMETER PushSiteVars
    Writes fetched site variables into HKLM:\Software\SVS\Deployment.

.PARAMETER InstallRMM
    Downloads and runs the Datto RMM agent installer.

.PARAMETER SaveCopy
    Saves a copy of the downloaded agent installer to C:\Temp.

.PARAMETER SiteUID
    Unique identifier of the Datto site (required for install and registry push).

.PARAMETER SiteName
    Friendly name of the Datto site (used for logging).

.EXAMPLE
    # Fetch and save site list via webhook
    Install-DattoRMM -UseWebhook -WebhookPassword 'Tndmeeisdwge!' -FetchSites -SaveSitesList -OutputFile 'sites.csv'

.EXAMPLE
    # Headless install with site variables
    Install-DattoRMM -ApiUrl 'https://api.example.com' -ApiKey 'KeyHere' -ApiSecretKey 'SecretHere' \
      -SiteUID 'site-123' -SiteName 'Acme Corp' -PushSiteVars -InstallRMM

.EXAMPLE
    # Download and save installer to C:\Temp without installing
    Install-DattoRMM -ApiUrl 'https://api.example.com' -ApiKey 'KeyHere' -ApiSecretKey 'SecretHere' \
      -SiteUID 'site-123' -SiteName 'Acme Corp' -SaveCopy
#>
function Install-DattoRMM {
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
    param (
        [switch]$UseWebhook,
        [string]$WebhookPassword,
        [string]$WebhookUrl    = $Global:DattoWebhookUrl,
        [string]$ApiUrl,
        [string]$ApiKey,
        [string]$ApiSecretKey,
        [switch]$FetchSites,
        [switch]$SaveSitesList,
        [string]$OutputFile      = 'datto_sites.csv',
        [switch]$PushSiteVars,
        [switch]$InstallRMM,
        [switch]$SaveCopy,
        [string]$SiteUID,
        [string]$SiteName
    )

    # Validate mutually-dependent switches
    if ($SaveSitesList -and -not $FetchSites) {
        Write-LogHybrid "-SaveSitesList requires -FetchSites." Error DattoRMM; return
    }

    # 1) Optionally fetch credentials from webhook
    if ($UseWebhook) {
        if (-not $WebhookPassword) {
            Write-LogHybrid "Webhook password missing." Error DattoRMM; return
        }
        try {
            $resp = Invoke-RestMethod -Uri $WebhookUrl `
                                      -Headers @{ SVSMSPKit = $WebhookPassword } `
                                      -Method GET
            $ApiUrl       = $resp.ApiUrl
            $ApiKey       = $resp.ApiKey
            $ApiSecretKey = $resp.ApiSecretKey
            Write-LogHybrid "Webhook credentials fetched." Success DattoRMM
        } catch {
            Write-LogHybrid "Failed to fetch webhook credentials: $($_.Exception.Message)" Error DattoRMM; return
        }
    }

    # 2) Validate API parameters
    if (-not $ApiUrl -or -not $ApiKey -or -not $ApiSecretKey) {
        Write-LogHybrid "Missing required API parameters." Error DattoRMM; return
    }

    # 3) Acquire OAuth token
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    try {
        $publicCred = New-Object System.Management.Automation.PSCredential(
            'public-client', (ConvertTo-SecureString 'public' -AsPlainText -Force)
        )
        $tokenResp = Invoke-RestMethod -Uri "$ApiUrl/auth/oauth/token" `
                                       -Credential $publicCred `
                                       -Method Post `
                                       -ContentType 'application/x-www-form-urlencoded' `
                                       -Body "grant_type=password&username=$ApiKey&password=$ApiSecretKey"
        $token = $tokenResp.access_token
        Write-LogHybrid "OAuth token acquired." Success DattoRMM
    } catch {
        Write-LogHybrid "OAuth token fetch failed: $($_.Exception.Message)" Error DattoRMM; return
    }
    $headers = @{ Authorization = "Bearer $token" }

    # 4) Fetch site list only
    if ($FetchSites) {
        try {
            $sitesResp = Invoke-RestMethod -Uri "$ApiUrl/api/v2/account/sites" -Method Get -Headers $headers
            $siteList = $sitesResp.sites | ForEach-Object {
                [PSCustomObject]@{ Name = $_.name; UID = $_.uid }
            }
            Write-LogHybrid "Fetched $($siteList.Count) sites." Success DattoRMM

            if ($SaveSitesList) {
                $desktop = [Environment]::GetFolderPath('Desktop')
                $path    = Join-Path $desktop $OutputFile
                $ext     = [IO.Path]::GetExtension($OutputFile).ToLower()
                if ($ext -eq '.json') {
                    $siteList | ConvertTo-Json -Depth 3 | Out-File -FilePath $path -Encoding UTF8
                } else {
                    $siteList | Export-Csv -Path $path -NoTypeInformation -Encoding UTF8
                }
                Write-LogHybrid "Wrote $($siteList.Count) sites to $path" Success DattoRMM
            }

            return $siteList
        } catch {
            Write-LogHybrid "Failed to fetch sites: $($_.Exception.Message)" Error DattoRMM; return @()
        }
    }

    # 5) Push site variables to registry
    if ($PushSiteVars) {
        try {
            $varsResp = Invoke-RestMethod -Uri "$ApiUrl/api/v2/site/$SiteUID/variables" -Method Get -Headers $headers
            Write-LogHybrid "Fetched variables for '$SiteName'." Success DattoRMM
        } catch {
            Write-LogHybrid "Variable fetch failed: $($_.Exception.Message)" Error DattoRMM
        }
        $regPath = "HKLM:\Software\SVS\Deployment"
        foreach ($v in $varsResp.variables) {
            try {
                if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null }
                New-ItemProperty -Path $regPath -Name $v.name -Value $v.value -PropertyType String -Force | Out-Null
                Write-LogHybrid "Wrote '$($v.name)' to registry." Success DattoRMM
            } catch {
                Write-LogHybrid "Failed to write '$($v.name)': $($_.Exception.Message)" Error DattoRMM
            }
        }
    }

    # 6) Download & install RMM agent
    if ($InstallRMM) {
        if ($PSCmdlet.ShouldProcess("Site '$SiteName'", "Install RMM agent")) {
            try {
                $dlUrl = "https://zinfandel.centrastage.net/csm/profile/downloadAgent/$SiteUID"
                $tmp   = "$env:TEMP\AgentInstall.exe"
                Invoke-WebRequest -Uri $dlUrl -OutFile $tmp -UseBasicParsing
                Write-LogHybrid "Downloaded agent to $tmp." Info DattoRMM
                Start-Process -FilePath $tmp -NoNewWindow
                Write-LogHybrid "RMM agent installer launched." Success DattoRMM
            } catch {
                Write-LogHybrid "Agent install failed: $($_.Exception.Message)" Error DattoRMM
            }
        }
    }

    # 7) Save a copy of installer to C:\Temp
    if ($SaveCopy) {
        try {
            $dlUrl = "https://zinfandel.centrastage.net/csm/profile/downloadAgent/$SiteUID"
            $path  = "C:\Temp\AgentInstall.exe"
            if (-not (Test-Path 'C:\Temp')) { New-Item -Path 'C:\Temp' -ItemType Directory | Out-Null }
            Invoke-WebRequest -Uri $dlUrl -OutFile $path -UseBasicParsing
            Write-LogHybrid "Saved installer copy to $path." Info DattoRMM
        } catch {
            Write-LogHybrid "Save-copy failed: $($_.Exception.Message)" Error DattoRMM
        }
    }

    # 8) Warn if no action was taken
    if (-not ($PushSiteVars -or $InstallRMM -or $SaveCopy)) {
        Write-LogHybrid "No action specified. Use -FetchSites, -SaveSitesList, -PushSiteVars, -InstallRMM, or -SaveCopy." Warning DattoRMM
    }
}

      
  #endregion Install-DattoRMM

    #region Dispatch-Request

      # Sends the HTML for a given page or invokes a task handler
      function Dispatch-Request {
      param($Context)

      # figure out the path
      $path = $Context.Request.Url.AbsolutePath.TrimStart('/')

      # ---- Shutdown handler ----
      if ($path -eq 'quit') {
          Write-LogHybrid "Shutdown requested" "Info" "Server"
          Respond-Text $Context "Server shutting down."
          # stop the listener loop
          $Global:Listener.Stop()
          return
      }

      # ---- Fetch Sites endpoint ----
      if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'getpw') {
          Handle-FetchSites $Context
          return
      }

      # ---- Serve UI pages ----
      if ($path -in @('', 'onboard', 'offboard', 'tweaks', 'SVSApps')) {
          $page = if ($path -eq '') { 'onboard' } else { $path }
          $html = Get-UIHtml -Page $page
          Respond-HTML $Context $html
          return
      }
          
      # ---- Task invocation ----
      $task = $Global:Tasks | Where-Object Name -EQ $path
      if ($task) {
          & $task.HandlerFn $Context
          return
      }

      # ---- 404 ----
      $Context.Response.StatusCode = 404
      Respond-Text $Context '404 - Not Found'
  }
    #endregion Dispatch-Request

    #region EntryPoint: Define Invoke-ScriptMonkey

  # ─────────────────────────────────────────────────────────────────────────
  #  3) MAIN LOGIC (Toolkit vs DattoFetch vs DattoInstall vs UI)
  # ─────────────────────────────────────────────────────────────────────────

    switch ($PSCmdlet.ParameterSetName) {
      'Toolkit' {
        Write-LogHybrid "Toolkit-only mode" Info Startup
        Install-SVSMSP -InstallToolkit
        return
      }

      'Cleanup' {
        Write-LogHybrid "Running Toolkit cleanup mode" Info Startup
        Install-SVSMSP -Cleanup
        return
      }
      
      # ───────────────────────────────────────────────────────────
      # 2) If user only wants the site list, do that and exit
      # ───────────────────────────────────────────────────────────

      'DattoFetch' {
          Write-LogHybrid "Fetching site list only…" Info DattoAuth
          $sites = Install-DattoRMM `
              -UseWebhook `
              -WebhookPassword $WebhookPassword `
              -FetchSites `
              -SaveSitesList:$SaveSitesList `
              -OutputFile $OutputFile

            Write-LogHybrid "Done." Success DattoAuth
            return
       }


      # ────────────────────────────────────────────
      # 3) Invoke the existing Install-DattoRMM cmdlet
      # ────────────────────────────────────────────
      
      'DattoInstall' {
      Write-LogHybrid "Headless DattoRMM deploy" Info DattoAuth

      if ($PSCmdlet.ShouldProcess("Datto site '$SiteName'", "Headless install")) {
        Install-DattoRMM `
        -UseWebhook `
        -WebhookPassword $WebhookPassword `
        -SiteUID $SiteUID `
        -SiteName $SiteName `
        -PushSiteVars:$PushSiteVars `
        -InstallRMM:$InstallRMM `
        -SaveCopy:$SaveCopy
      }

      return

      }

      'UI' {
      Write-LogHybrid "Launching UI" Info Startup
      Write-Host "Starting ScriptMonkey UI on http://localhost:$Port/" -ForegroundColor Cyan
      Start-Process "msedge.exe" -ArgumentList "--app=http://localhost:$Port"
      Start-Server    # blocks until you click Exit
      return
    }

    }
  #endregion EntryPoint: Define Invoke-ScriptMonkey

    #region — guarantee NuGet provider is present without prompting

  # ─── Top of script ───
  Import-Module PackageManagement -Force -ErrorAction SilentlyContinue | Out-Null
  Import-Module PowerShellGet       -Force -ErrorAction SilentlyContinue | Out-Null

  # ─── ensure TLS 1.2 + no prompts ───
  [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
  $ProgressPreference = 'SilentlyContinue'
  $ConfirmPreference  = 'None'

  # check if NuGet exists (no output—assigned to $nuget)
  $nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue

  if (-not $nuget) {
      # install it (again, assignment suppresses the table)
      Install-PackageProvider `
        -Name NuGet `
        -MinimumVersion 2.8.5.201 `
        -Force `
        -Confirm:$false

      
  # re-query just for version info
      $found = Get-PackageProvider -Name NuGet -ListAvailable
      Write-Host "Installed NuGet provider v$($found.Version)" -ForegroundColor Green
  }
  else {
      Write-Host "NuGet provider already present (v$($found.Version))" -ForegroundColor DarkGray
  }

  # now import it silently
  Import-PackageProvider -Name NuGet -Force -ErrorAction SilentlyContinue | Out-Null

  # ensure trust PSGallery without its own output (so you don't get “untrusted repository” prompt
  $gallery = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue
  if ($gallery.InstallationPolicy -ne 'Trusted') {
      Set-PSRepository `
        -Name PSGallery `
        -InstallationPolicy Trusted `
        -ErrorAction SilentlyContinue | Out-Null

      Write-Host "PSGallery marked as Trusted" -ForegroundColor Green
  }

  #endregion

   

    #region HTTP Listener & Routing

  # Handle shutdown command
  if ($path -eq 'quit') {
      Write-LogHybrid "Shutdown requested" "Info" "Server"
      Respond-Text $Context "Server shutting down."
      # This will break out of the while loop in Start-Server
      $Global:Listener.Stop()
      return
  }

  
  #endregion


 }

  if ($MyInvocation.InvocationName -eq '.') {
    # dot-sourced, don't invoke
} elseif ($PSCommandPath) {
    # script was saved and run directly
    Invoke-ScriptMonkey @PSBoundParameters
} else {
    # iwr | iex fallback
     if ($args.Count -gt 0) {
        # Convert -Param value -Switch into a hashtable for splatting
        $namedArgs = @{}
        for ($i = 0; $i -lt $args.Count; $i++) {
            if ($args[$i] -is [string] -and $args[$i].StartsWith('-')) {
                $key = $args[$i].TrimStart('-')
                $next = $args[$i + 1]
                if ($next -and ($next -notlike '-*')) {
                    $namedArgs[$key] = $next
                    $i++  # Skip next one, it's the value
                } else {
                    $namedArgs[$key] = $true
                }
            }
        }
        Invoke-ScriptMonkey @namedArgs
    } else {
        Invoke-ScriptMonkey
    }
}


