diff --git a/StackMonkey_v1.ps1 b/StackMonkey_v1.ps1
deleted file mode 100644
index 42a0423..0000000
--- a/StackMonkey_v1.ps1
+++ /dev/null
@@ -1,1677 +0,0 @@
-#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
-
-<#
-.SYNOPSIS
- ScriptMonkey - MSP client onboarding/offboarding toolkit with a user interface,
- and optional silent install of the SVSMSP toolkit and headless DattoRMM deployment.
-
-.DESCRIPTION
- Provides an HTTP-hosted GUI for selecting and running tasks (module installs, tweaks, offboarding, etc.).
- When invoked with the correct parameters, it can silently install the SVSMSP toolkit and perform a headless
- DattoRMM deployment without ever launching the browser or UI.
-
-.PARAMETER SilentInstall
- Runs only the SVSMSP module install (Install-Toolkit) and skips launching the browser/UI.
-
-.PARAMETER DattoApiUrl
- The Datto Automate API base URL for headless deployment.
-
-.PARAMETER DattoApiKey
- Your Datto Automate API username.
-
-.PARAMETER DattoApiSecretKey
- Your Datto Automate API password/secret.
-
-.PARAMETER SiteUID
- The target Datto site UID for headless installation.
-
-.PARAMETER SiteName
- The target Datto site name for headless installation.
-
-.PARAMETER PushSiteVars
- Switch to include site variables in the headless DattoRMM install.
-
-.PARAMETER InstallRMM
- Switch to install the RMM agent in the headless DattoRMM install.
-
-.PARAMETER SaveCopy
- Switch to download the RMM installer executable during the headless DattoRMM install.
-
-.EXAMPLE
- & ([ScriptBlock]::Create(
- (iwr 'https://sm.svstools.com' -UseBasicParsing).Content
- )) `
- -N8nPassword 'pwd' `
- -SiteUID 'site-123' `
- -SiteName 'Acme Corp' `
- -InstallRMM `
- -PushSiteVars `
- -SaveCopy `
- -WhatIf
-
-.EXAMPLE
- & ([ScriptBlock]::Create(
- (iwr 'https://sm.svstools.com/ScriptMonkey.ps1' -UseBasicParsing).Content
-)) `
- -N8nPassword 's3cr3t' `
- -FetchSitesOnly `
- -OutputFile 'sites.json'
-
-.EXAMPLE
- & ([ScriptBlock]::Create(
- (iwr 'https://sm.svstools.com/ScriptMonkey.ps1' -UseBasicParsing).Content
-)) `
- -N8nPassword 's3cr3t' `
- -FetchSitesOnly
-# β writes datto_sites.csv
-
-.EXAMPLE
- & ([ScriptBlock]::Create((iwr 'sm.svstools.ca').Content )) -SilentInstall
-
-.EXAMPLE
- & ([ScriptBlock]::Create((iwr 'sm.svstools.com').Content)) -Cleanup
-
-
-.EXAMPLE
- not tested but i thin this is how would call it
- iex (iwr 'https://your.server/ScriptMonkey.ps1' -UseBasicParsing).Content; Invoke-ScriptMonkey -DattoApiUrl
- 'https://β¦' -DattoApiKey 'β¦' -DattoApiSecretKey 'β¦' -SiteUID 'β¦' -SiteName 'β¦' -InstallRMM -PushSiteVars"
-
-#>
-
- #region ScriptMonkey run silently Entrypoint
-
- # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- # 1) ENTRYPOINT + PARAMETER DECLARATION
- # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-
- function Invoke-ScriptMonkey {
-
- [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
-
- # Both Datto sets share the webhook password
- # Shared webhook password for both Datto modes
- [Parameter(Mandatory,ParameterSetName='DattoFetch')]
- [Parameter(Mandatory,ParameterSetName='DattoInstall')]
- [string]$N8nPassword,
-
- # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- # Fetch only set write sites and exit
- [Parameter(ParameterSetName='DattoFetch')][switch] $SaveSitesOnly,
- [Parameter(ParameterSetName='DattoFetch')][string] $OutputFile = 'datto_sites.csv',
-
- # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- # Install set: target site must be provided
- [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 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
-
- #region globalsetting
- # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- # 2) GLOBAL SETTINGS & HELPERS
- # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-
- # Listening port for HTTP UI
- $Port = 8082
-
- # Configurable endpoints
- $Global:DattoWebhookUrl = 'https://automate.svstools.ca/webhook/svsmspkit'
-
- #region Get-DattoApiCredentials
- function Get-DattoApiCredentials {
- [CmdletBinding()]
- param (
- [Parameter(Mandatory)][string]$Password
- )
- $headers = @{ "SVSMSPKit" = $Password }
- try {
- $resp = Invoke-RestMethod -Uri $Global:DattoWebhookUrl `
- -Headers $headers `
- -Method GET
- return @{
- ApiUrl = $resp.ApiUrl
- ApiKey = $resp.ApiKey
- ApiSecretKey = $resp.ApiSecretKey
- }
- }
- catch {
- Write-LogHybrid "Failed to fetch API credentials: $($_.Exception.Message)" Error DattoAuth
- return $null
- }
- }
-
- function Get-DattoRmmSites {
- [CmdletBinding()]
- param(
- [Parameter(Mandatory)]
- [string] $Password,
-
- [Parameter()]
- [string] $WebhookUrl = $Global:DattoWebhookUrl
- )
-
- # 1) Fetch Datto API credentials from your webhook
- Write-Verbose "Fetching Datto API credentials from $WebhookUrl"
- try {
- $headers = @{ 'SVSMSPKit' = $Password }
- $creds = Invoke-RestMethod -Uri $WebhookUrl -Headers $headers -Method GET
- }
- catch {
- Throw "Failed to fetch credentials from webhook: $_"
- }
-
- $apiUrl = $creds.ApiUrl
- $apiKey = $creds.ApiKey
- $apiSecretKey = $creds.ApiSecretKey
-
- # 2) Request an OAuth token
- Write-Verbose "Requesting OAuth token from $apiUrl/auth/oauth/token"
- try {
- $securePwd = ConvertTo-SecureString -String 'public' -AsPlainText -Force
- $credObj = New-Object System.Management.Automation.PSCredential('public-client', $securePwd)
-
- $tokenResp = Invoke-RestMethod `
- -Uri "$apiUrl/auth/oauth/token" `
- -Credential $credObj `
- -Method 'POST' `
- -ContentType 'application/x-www-form-urlencoded' `
- -Body "grant_type=password&username=$apiKey&password=$apiSecretKey"
-
- $token = $tokenResp.access_token
- }
- catch {
- Throw "Failed to obtain OAuth token: $_"
- }
-
- # 3) Fetch the list of RMM sites
- Write-Verbose "Fetching RMM sites from $apiUrl/api/v2/account/sites"
- try {
- $authHeader = @{ Authorization = "Bearer $token" }
- $sitesResp = Invoke-RestMethod `
- -Uri "$apiUrl/api/v2/account/sites" `
- -Method 'GET' `
- -Headers $authHeader `
- -ContentType 'application/json'
-
- $siteList = $sitesResp.sites | Select-Object `
- @{ Name = 'Name'; Expression = { $_.name } }, `
- @{ Name = 'UID'; Expression = { $_.uid } }
-
- if (-not $siteList) {
- Write-Warning "No sites were returned by the API."
- return @()
- }
-
- return $siteList
- }
- catch {
- Throw "Failed to fetch sites from API: $_"
- }
- }
-
-
- # 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()
- }
-
- # Core Write-Log function (advanced with event-log support)
- 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 = "SVS Scripting",
-,
- [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)
- # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- function Write-LogHybrid {
- [CmdletBinding()]
- param(
- [Parameter(Mandatory=$true)][string]$Message,
- [ValidateSet("Info","Warning","Error","Success","General")]
- [string]$Level = "Info",
- [string]$TaskCategory = "GeneralTask",
- [switch]$LogToEvent,
- [string]$EventSource = "Script Automation Monkey",
- [string]$EventLog = "SVS Scripting"
- )
-
- 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 `
- -EventSource $EventSource `
- -EventLog $EventLog
- }
- else {
- # Fall back to your helper
- Write-LogHelper `
- -Message $Message `
- -Level $Level `
- -TaskCategory $TaskCategory `
- -LogToEvent:$LogToEvent `
- -EventSource $EventSource `
- -EventLog $EventLog
- }
-}
-
-
- #region Config & Task Definitions
-
- # 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
-
-
- 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 = ""
-
- if ($_.SubOptions) {
- # join inside the code block is fine
- $subHtml = (
- $_.SubOptions |
- ForEach-Object {
- ""
- }
- ) -join "`n"
-
- $html += @"
-
-$subHtml
-
-"@
- }
-
- $html
- }
- ) -join "`n"
- }
-
-
-
- ### 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 "Module Version: $($mod.Version)
"
- }
- return "SVSMSP_Module not found
"
- }
-
-
-
-# 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"
- }
- }
-#region UIHtml
-function Get-UIHtml {
- param([string]$Page = 'onboard')
-
- #
- # 1) Inline your full original CSS here
- #
-$style = @'
-
-'@
-
-
-$script = @'
-
-
-'@
-
- #
- # 3) The HTML skeleton with placeholders
- #
-$htmlTemplate = @"
-
-
-
-
-
-Script Monkey
-
-$style
-
-
-
-
-
-

- {{moduleVersion}}
-
-
-
-
-

-
Script Automation Monkey (Yeah!)
-
-
-
-
-
-
-
-
-
-
On-Boarding
-
This new deployment method ensures everything is successfully deployed with greater ease!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Off-Boarding
-
-{{offboardCheckboxes}}
-
-
-
-
-
Tweaks
-
-{{tweaksCheckboxes}}
-
-
-
-
-
SVS APPs
-
-{{appsCheckboxes}}
-
-
-
-
-
-$script
-
-
-
-
-
-
-
-
-
-
-"@
-
- #
- # 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()
- }
-
- # new helper to return JSON
- 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)
-
- # 1) Read incoming JSON (using block auto-disposes the reader)
-
- $reader = [IO.StreamReader]::new($Context.Request.InputStream)
- try {
- $raw = $reader.ReadToEnd()
- } finally {
- $reader.Close()
- }
-
- try {
- $pw = (ConvertFrom-Json $raw).password
- } catch {
- Write-LogHybrid "Invalid JSON in /getpw payload: $($_.Exception.Message)" "Error" "FetchSites"
- returnRespondEmpty $Context
- return
- }
-
- # 2) Fetch your Datto API creds from the webhook
- Write-LogHybrid "Calling webhook for Datto credentialsβ¦" "Info" "FetchSites"
-
- try {
- $creds = Get-DattoApiCredentials -Password $pw
- if (-not $creds) {
- Write-LogHybrid "Webhook returned no credentials" Error FetchSites
- returnRespondEmpty $Context 403
- return
- }
-
- # reuse the same globals from the entrypoint
- $Global:ApiUrl = $creds.ApiUrl
- $Global:ApiKey = $creds.ApiKey
- $Global:ApiSecretKey = $creds.ApiSecretKey
-
- Write-LogHybrid "Fetched and stored API credentials." Success FetchSites
- } catch {
- Write-LogHybrid "Credential-fetch error: $($_.Exception.Message)" Error FetchSites -LogToEvent
- returnRespondEmpty $Context 500
- return
- }
-
-
- # 3) Exchange for a bearer token
- Write-LogHybrid "Requesting OAuth token" "Info" "FetchSites"
- try {
- $securePublic = ConvertTo-SecureString 'public' -AsPlainText -Force
- $creds = New-Object System.Management.Automation.PSCredential('public-client',$securePublic)
- $tokenResp = Invoke-RestMethod `
- -Uri "$Global:ApiUrl/auth/oauth/token" `
- -Credential $creds `
- -Method Post `
- -ContentType 'application/x-www-form-urlencoded' `
- -Body "grant_type=password&username=$Global:ApiKey&password=$Global:ApiSecretKey"
- $token = $tokenResp.access_token
- Write-LogHybrid "OAuth token acquired." "Success" "FetchSites"
- } catch {
- Write-LogHybrid "OAuth request failed: $($_.Exception.Message)" "Error" "FetchSites"
- returnRespondEmpty $Context 500
- return
- }
-
- # 4) Pull the site list
- Write-LogHybrid "Fetching Datto RMM site list" "Info" "FetchSites"
- try {
- $hdr = @{ Authorization = "Bearer $token" }
- $sitesResp = Invoke-RestMethod -Uri "$Global:ApiUrl/api/v2/account/sites" `
- -Method Get `
- -Headers $hdr `
- -ContentType 'application/json'
-
- $siteList = $sitesResp.sites | ForEach-Object {
- [PSCustomObject]@{ Name = $_.name; UID = $_.uid }
- }
- Write-LogHybrid "Site list retrieved ($($siteList.Count) sites)." "Success" "FetchSites"
- } catch {
- Write-LogHybrid "Failed to fetch site list: $($_.Exception.Message)" "Error" "FetchSites"
- returnRespondEmpty $Context 500
- return
- }
-
- # 5) Return JSON array
- $json = $siteList | ConvertTo-Json -Depth 2
- $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()
- }
-
-
-
- # Helper function to consistently return an empty JSON array
- function returnRespondEmpty {
- param(
- [Parameter(Mandatory)][object]$Context,
- [Parameter(Mandatory)][ValidateRange(100,599)][int]$StatusCode = 500
- )
- # Always return an empty JSON array body
- $empty = [Text.Encoding]::UTF8.GetBytes("[]")
-
- # Set the desired status code and headers
- $Context.Response.StatusCode = $StatusCode
- $Context.Response.ContentType = 'application/json'
- $Context.Response.ContentLength64 = $empty.Length
-
- # Write and close
- $Context.Response.OutputStream.Write($empty, 0, $empty.Length)
- $Context.Response.OutputStream.Close()
- }
-
-
-
- # On-boarding handlers
- function Handle-SetSVSPowerPlan {
- param($Context)
-
- # 1) call into your module
- Set-SVSPowerPlan
-
- # 2) log & write back a simple text response
- Write-LogHybrid "PowerPlan set" "Success" "OnBoard"
- Respond-Text $Context "PowerPlan applied"
- }
-
- function Handle-InstallSVSMSP {
- param($Context)
- Write-LogHybrid "HTTP trigger: Handle-InstallSVSMSP" "Info" "OnBoard"
- try {
- Install-SVSMSP -InstallToolkit
- Respond-Text $Context "SVSMSP Module installed/updated."
- } catch {
- Write-LogHybrid "Error in Install-SVSMSP: $_" "Error" "OnBoard"
- Respond-Text $Context "ERROR: $_"
- }
- }
-
- function Handle-InstallCyberQP {
- param($Context)
-
- # 1) call into your module
- Install-CyberQP
-
- # 2) log & write back a simple text response
- Write-LogHybrid "CyberQP installed" "Success" "OnBoard"
- Respond-Text $Context "CyberQP installed"
- }
-
- function Handle-InstallThreatLocker {
- param($Context)
-
- # 1) call into your module
- Install-ThreatLocker
-
- # 2) log & write back a simple text response
- Write-LogHybrid "ThreatLocker installed" "Success" "OnBoard"
- Respond-Text $Context "ThreatLocker installed"
- }
-
- function Handle-InstallRocketCyber {
- param($Context)
-
- # 1) call into your module
- Install-RocketCyber
-
- # 2) log & write back a simple text response
- Write-LogHybrid "RocketCyber installed" "Success" "OnBoard"
- Respond-Text $Context "RocketCyber installed"
- }
-
- function Handle-InstallSVSHelpDesk {
- param($Context)
-
- # 1) call into your module
- Install-SVSHelpDesk
-
- # 2) log & write back a simple text response
- Write-LogHybrid "SVS HelpDesk installed" "Success" "OnBoard"
- Respond-Text $Context "SVS HelpDesk installed"
- }
-
- function Handle-InstallDattoRMM {
- param($Context)
- $req = $Context.Request
- $resp = $Context.Response
-
- if ($req.HttpMethod -ne 'POST') {
- $resp.StatusCode = 405; $resp.ContentType = 'text/plain'
- $resp.OutputStream.Write([Text.Encoding]::UTF8.GetBytes('Use POST'),0,7)
- $resp.OutputStream.Close(); return
- }
-
- # parse JSON body
- $body = (New-Object IO.StreamReader $req.InputStream).ReadToEnd()
- $data = $body | ConvertFrom-Json
- $checked = $data.checkedValues
- $uid = $data.UID
- $name = $data.Name
-
- try {
- Install-DattoRMM `
- -ApiUrl $Global:ApiUrl `
- -ApiKey $Global:ApiKey `
- -ApiSecretKey $Global:ApiSecretKey `
- -SiteUID $uid `
- -SiteName $name `
- -PushSiteVars:($checked -contains 'inputVar') `
- -InstallRMM: ($checked -contains 'rmm') `
- -SaveCopy: ($checked -contains 'exe')
-
- Write-LogHybrid "RMM install triggered for $name" "Success" "DattoRMM"
- $resp.StatusCode = 200
- $responseString = "Triggered DattoRMM for $name"
- }
- catch {
- Write-LogHybrid "Error in Install-DattoRMM: $_" "Error" "DattoRMM"
- $resp.StatusCode = 500
- $responseString = "ERROR: $($_.Exception.Message)"
- }
-
- $b = [Text.Encoding]::UTF8.GetBytes($responseString)
- $resp.ContentType = 'text/plain'
- $resp.ContentLength64 = $b.Length
- $resp.OutputStream.Write($b,0,$b.Length)
- $resp.OutputStream.Close()
- }
-
-
- # Off-boarding handlers
- function Handle-UninstallCyberQP {
- param($Context)
-
- # 1) call into your module
- Uninstall-CyberQP
-
- Write-LogHybrid "CyberQP uninstalled" "Success" "OffBoard"
- Respond-Text $Context "CyberQP uninstalled"
- }
-
- function Cleanup-SVSMSP {
- param($Context)
- Write-LogHybrid "SVSMSP cleaned up" "Success" "OffBoard"
- Respond-Text $Context "SVSMSP cleaned up"
- }
-
- # Tweaks handler
- function Disable-Animations {
- param($Context)
- Write-LogHybrid "Animations disabled" "Success" "Tweaks"
- Respond-Text $Context "Animations disabled"
- }
-
- # SVSApps handler
- function Install-WingetLastPass {
- param($Context)
- Write-LogHybrid "Winget LastPass installed" "Success" "SVSApps"
- Respond-Text $Context "Winget LastPass installed"
- }
-
- #endregion
-
- # 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 region globalsetting
-
- #region ScriptMonkey run silently Entrypoint
-
- # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- # 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 = Get-DattoRmmSites -Password $N8nPassword
-
- if ($SaveSitesOnly) {
- # If SaveSitesOnly is true, save the output to a file on desktop
- $desktopPath = [System.Environment]::GetFolderPath('Desktop')
- $filePath = Join-Path -Path $desktopPath -ChildPath $OutputFile
-
- $ext = [IO.Path]::GetExtension($OutputFile).ToLower()
- if ($ext -eq '.json') {
- # Save the file to the desktop using the full path
- $sites | ConvertTo-Json -Depth 3 | Out-File -FilePath $filePath -Encoding UTF8
- } else {
- # Save the file to the desktop using the full path
- $sites | Export-Csv -Path $filePath -NoTypeInformation -Encoding UTF8
- }
-
- Write-LogHybrid "Wrote $($sites.Count) sites to $filePath" Success DattoAuth
-
- } else {
- # If SaveSitesOnly is not true, just fetch sites (for UI purposes or silent mode without saving)
- Write-LogHybrid "Sites fetched successfully, but not saved." 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 `
- -ApiUrl $Global:ApiUrl `
- -ApiKey $Global:ApiKey `
- -ApiSecretKey $Global:ApiSecretKey `
- -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 ScriptMonkey run silently Entrypoint
-
- Write-Host "π οΈ SAMY - Script Automation Monkey (Yeah!)" -ForegroundColor Cyan
- Write-Host "ParameterSetName: $($PSCmdlet.ParameterSetName)" -ForegroundColor Yellow
-
- #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
-
-
-
- # If we got here, it's the UI setβlaunch browser + listener:
- # βββ UI fallback starts here βββ
- Write-LogHybrid "Launching UI" Info Startup
-
-
-
-
-
- #region Install-DattoRMM-Helper
- function Install-DattoRMM-Helper {
- param (
- [string]$ApiUrl,
- [string]$ApiKey,
- [string]$ApiSecretKey,
- [switch]$FetchSitesOnly,
- [string]$SiteName,
- [string]$SiteUID
- )
- if (-not $ApiUrl -or -not $ApiKey -or -not $ApiSecretKey) {
- Write-LogHybrid -Message "Missing required parameters. Please provide ApiUrl, ApiKey, and ApiSecretKey." -Level "Error" -LogToEvent
- return
- }
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
- Write-LogHybrid -Message "Fetching OAuth token..." -Level "Info"
- try {
- $securePassword = ConvertTo-SecureString -String 'public' -AsPlainText -Force
- $apiGenToken = Invoke-WebRequest -Credential (New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ('public-client', $securePassword)) `
- -Uri ('{0}/auth/oauth/token' -f $ApiUrl) `
- -Method 'POST' `
- -ContentType 'application/x-www-form-urlencoded' `
- -Body ('grant_type=password&username={0}&password={1}' -f $ApiKey, $ApiSecretKey) `
- | ConvertFrom-Json
- $requestToken = $apiGenToken.access_token
- Write-LogHybrid -Message "OAuth token fetched successfully." -Level "Success" -LogToEvent
- } catch {
- Write-LogHybrid -Message "Failed to fetch OAuth token. Details: $($_.Exception.Message)" -Level "Error" -LogToEvent
- return
- }
- $getHeaders = @{"Authorization" = "Bearer $requestToken"}
- if ($FetchSitesOnly) {
- Write-Host "Fetching list of sites from the Datto RMM API..." -ForegroundColor Cyan
- try {
- $getHeaders = @{"Authorization" = "Bearer $requestToken" }
- $getSites = Invoke-WebRequest -Uri "$ApiUrl/api/v2/account/sites" -Method Get -Headers $getHeaders -ContentType "application/json"
- $sitesJson = $getSites.Content | ConvertFrom-Json
- $siteList = $sitesJson.sites | ForEach-Object {
- [PSCustomObject]@{
- Name = $_.name
- UID = $_.uid
- }
- }
- Write-Host "Successfully fetched list of sites." -ForegroundColor Green
- return $siteList
- }
- catch {
- Write-Host "Failed to fetch sites from the API. Details: $($_.Exception.Message)" -ForegroundColor Red
- return
- }
- }
- }
- #endregion
-
-
-
- # POST /getpw β read JSON body, call helper, return JSON
-
-
-
-
- #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
- }
-}
-
-