diff --git a/Stackmonkey.ps1 b/Stackmonkey.ps1
deleted file mode 100644
index 79fab72..0000000
--- a/Stackmonkey.ps1
+++ /dev/null
@@ -1,1188 +0,0 @@
-#region changes to be done
-# We could change line 298 and 379 the have it log in "SVS Scripting" instead of "Application" if we can find a way to force create the log
-# winget install --id=Google.Chrome --silent --accept-package-agreements --accept-source-agreements
-# winget install --id=Adobe.Acrobat.Reader.64-bit --silent --accept-package-agreements --accept-source-agreements
-#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.
-#>
-
-#region Safely bypass Restricted Execution Policy
-# ─── Safely bypass Restricted Execution Policy ───
-if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage' -or
- (Get-ExecutionPolicy) -eq 'Restricted') {
-
- Write-Host "[Info] Relaunching with ExecutionPolicy Bypass..." -ForegroundColor Yellow
-
- if ($PSCommandPath) {
- powershell.exe -NoProfile -ExecutionPolicy Bypass -File "`"$PSCommandPath`""
- } else {
- powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& { iwr 'https://sm.svstools.com' -UseBasicParsing | iex }"
- }
-
- exit
-}
-
-# ─── TLS and silent install defaults ───
-[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
-$ProgressPreference = 'SilentlyContinue'
-$ConfirmPreference = 'None'
-#endregion Safely bypass Restricted Execution Policy
-
-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$')][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
- $Port = 8082
- $Global:DattoWebhookUrl = 'https://automate.svstools.ca/webhook/svsmspkit'
- 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"
-
- try {
- Uninstall-Module -Name SVSMSP -AllVersions -Force -ErrorAction Stop
- Write-LogHybrid "SVSMSP module uninstalled from system." "Success" "SVSModule" -LogToEvent
- } catch {
- if ($_.Exception.Message -match 'No match was found') {
- Write-LogHybrid "No existing SVSMSP module found to uninstall." "Warning" "SVSModule" -LogToEvent
- } else {
- Write-LogHybrid "Failed to uninstall SVSMSP: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent
- }
- }
-
- if (Get-PSRepository -Name SVS_Repo -ErrorAction SilentlyContinue) {
- try {
- Unregister-PSRepository -Name SVS_Repo -ErrorAction Stop
- Write-LogHybrid "SVS_Repo repository unregistered." "Success" "SVSModule" -LogToEvent
- } catch {
- Write-LogHybrid "Failed to unregister SVS_Repo: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent
- }
- }
-
- if (Get-Module -Name SVSMSP) {
- try {
- Remove-Module SVSMSP -Force -ErrorAction Stop
- Write-LogHybrid "SVSMSP module removed from current session." "Success" "SVSModule" -LogToEvent
- } catch {
- Write-LogHybrid "Failed to remove SVSMSP from session: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent
- }
- }
- }
-
- function Perform-ToolkitInstallation {
- Perform-Cleanup
- Write-LogHybrid "Registering repo $NewRepositoryName…" "Info" "SVSModule" -LogToEvent
- if (-not (Get-PSRepository -Name $NewRepositoryName -ErrorAction SilentlyContinue)) {
- Register-PSRepository -Name $NewRepositoryName -SourceLocation $NewRepositoryURL -InstallationPolicy Trusted
- }
- Write-LogHybrid "Installing module $NewModuleName…" "Info" "SVSModule" -LogToEvent
- Install-Module -Name $NewModuleName -Repository $NewRepositoryName -Scope AllUsers -Force
- Write-LogHybrid "Toolkit installation complete." "Success" "SVSModule" -LogToEvent
- }
-
- Write-LogHybrid "Install-SVSMSP called" "Info" "SVSModule" -LogToEvent
- if ($Cleanup) { Perform-Cleanup; return }
- if ($InstallToolkit) { Perform-ToolkitInstallation; return }
- Perform-ToolkitInstallation
- }
- #endregion SVS Module
-
- #region Write-Log
- 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 = "Script Automation Monkey",
- [string]$EventLog = "SVS Scripting", # <— default changed from Application
- [int] $CustomEventID,
- [string]$LogFile,
- [switch]$PassThru
- )
-
- $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
-
- if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) {
- $Global:LogCache = [System.Collections.ArrayList]::new()
- }
- $Global:LogCache.Add([pscustomobject]@{
- Timestamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
- Level = $Level
- Message = $fmt
- }) | Out-Null
-
- if ($PSBoundParameters.LogFile) {
- try {
- "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')) $fmt" | Out-File -FilePath $LogFile -Append -Encoding UTF8
- } catch {
- Write-Host "[Warning] File log failed: $($_.Exception.Message)" -ForegroundColor Yellow
- }
- }
-
- if ($LogToEvent) {
- try {
- # Ensure the log & source exist and are mapped together.
- $regBase = 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog'
- $mappedLog = $null
- if ([System.Diagnostics.EventLog]::SourceExists($EventSource)) {
- $mappedLog = (Get-ChildItem $regBase -ErrorAction SilentlyContinue |
- Where-Object { Test-Path (Join-Path $_.PSPath $EventSource) } |
- Select-Object -First 1
- ).PSChildName
- }
- if (-not $mappedLog) {
- New-EventLog -LogName $EventLog -Source $EventSource -ErrorAction Stop
- } elseif ($mappedLog -ne $EventLog) {
- # Source already exists but mapped to a different log. Create a new source mapped to target log.
- $newSource = "$EventSource - $EventLog"
- if (-not [System.Diagnostics.EventLog]::SourceExists($newSource)) {
- New-EventLog -LogName $EventLog -Source $newSource -ErrorAction Stop
- }
- $EventSource = $newSource
- }
- } catch {
- Write-Host "[Warning] Could not ensure event log/source ($EventLog / $EventSource): $($_.Exception.Message)" -ForegroundColor Yellow
- # continue; we'll still try to write
- }
-
- $entryType = if ($Level -in 'Warning','Error') { $Level } else { 'Information' }
- try {
- Write-EventLog -LogName $EventLog -Source $EventSource -EntryType $entryType -EventID $EventID -Message $fmt
- } catch {
- Write-Host "[Warning] EventLog write failed: $($_.Exception.Message)" -ForegroundColor Yellow
- }
- }
-
- if ($PassThru) { return $Global:LogCache[-1] }
- }
-
- 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", # <— default changed from Application
- [ValidateSet("Black","DarkGray","Gray","White","Red","Green","Blue","Yellow","Magenta","Cyan")]
- [string]$ForegroundColorOverride
- )
-
- $formatted = "[$Level] [$TaskCategory] $Message"
-
- if ($PSBoundParameters.ContainsKey('ForegroundColorOverride')) {
- Write-Host $formatted -ForegroundColor $ForegroundColorOverride
- $invokeParams = @{
- Message = $Message
- Level = $Level
- TaskCategory = $TaskCategory
- LogToEvent = $LogToEvent
- EventSource = $EventSource
- EventLog = $EventLog
- }
- if (Get-Command Write-Log -ErrorAction SilentlyContinue) { Write-Log @invokeParams } else { Write-LogHelper @invokeParams }
- } else {
- if (Get-Command Write-Log -ErrorAction SilentlyContinue) {
- Write-Log -Message $Message -Level $Level -TaskCategory $TaskCategory -LogToEvent:$LogToEvent -EventSource $EventSource -EventLog $EventLog
- } else {
- Write-LogHelper -Message $Message -Level $Level -TaskCategory $TaskCategory -LogToEvent:$LogToEvent -EventSource $EventSource -EventLog $EventLog
- }
- }
- }
- #endregion Write-Log
-
- #region building the Menus
- $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' },
- @{ Id='wingetChrome'; Name='wingetChrome'; Label='Google Chrome'; HandlerFn='Handle-InstallChrome'; Page='SVSApps' },
- @{ Id='wingetAcrobat'; Name='wingetAcrobat'; Label='Adobe Acrobat Reader (64-bit)'; HandlerFn='Handle-InstallAcrobat'; 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 = ""
-
- if ($_.SubOptions) {
- $subHtml = (
- $_.SubOptions | ForEach-Object {
- ""
- }
- ) -join "`n"
-
- $html += @"
-
-$subHtml
-
-"@
- }
- $html
- }
- ) -join "`n"
- }
- #endregion Build-Checkboxes
-
- #region Get-ModuleVersionHtml
- 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
"
- }
- #endregion Get-ModuleVersionHtml
-
- #region Strat-Server
- function Get-NextFreePort {
- param([int]$Start = $Port)
- for ($p = [Math]::Max(1024,$Start); $p -lt 65535; $p++) {
- $l = [System.Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, $p)
- try { $l.Start(); $l.Stop(); return $p } catch {}
- }
- throw "No free TCP port available."
- }
-
- # Starts the HTTP listener loop
- function Start-Server {
- $Global:Listener = [System.Net.HttpListener]::new()
- $primaryPrefix = "http://localhost:$Port/"
- $wildcardPrefix = "http://+:$Port/"
-
- try {
- $Global:Listener.Prefixes.Add($primaryPrefix)
- $Global:Listener.Start()
- Write-LogHybrid "Listening on $primaryPrefix" Info Server -LogToEvent
- }
- catch [System.Net.HttpListenerException] {
- if ($_.Exception.ErrorCode -eq 5) {
- Write-LogHybrid "Access denied on $primaryPrefix. Attempting URL ACL…" Warning Server -LogToEvent
- try {
- $user = "$env:USERDOMAIN\$env:USERNAME"
- if (-not $user.Trim()) { $user = $env:USERNAME }
- Start-Process -FilePath "netsh" -ArgumentList "http add urlacl url=$wildcardPrefix user=`"$user`" listen=yes" -Verb RunAs -WindowStyle Hidden -Wait
- $Global:Listener = [System.Net.HttpListener]::new()
- $Global:Listener.Prefixes.Add($wildcardPrefix)
- $Global:Listener.Start()
- Write-LogHybrid "Listening on $wildcardPrefix (URL ACL added for $user)" Success Server -LogToEvent
- } catch {
- Write-LogHybrid "URL ACL registration failed: $($_.Exception.Message)" Error Server -LogToEvent
- return
- }
- }
- elseif ($_.Exception.NativeErrorCode -in 32,183) {
- $old = $Port
- $Port = Get-NextFreePort -Start ($Port + 1)
- $Global:Listener = [System.Net.HttpListener]::new()
- $primaryPrefix = "http://localhost:$Port/"
- $Global:Listener.Prefixes.Add($primaryPrefix)
- $Global:Listener.Start()
- Write-LogHybrid "Port $old busy. Listening on $primaryPrefix" Warning Server -LogToEvent
- }
- else {
- Write-LogHybrid "HttpListener start failed: $($_.Exception.Message)" Error Server -LogToEvent
- return
- }
- }
-
- try {
- while ($Global:Listener.IsListening) {
- $ctx = $Global:Listener.GetContext()
- try {
- Dispatch-Request $ctx
- } catch {
- Write-LogHybrid "Dispatch error: $($_.Exception.Message)" Error Server -LogToEvent
- }
- }
- }
- finally {
- $Global:Listener.Close()
- Write-LogHybrid "Listener closed." Info Server -LogToEvent
- }
- }
-#endregion Strat-Server
-
-
- #region UIHtml
- function Get-UIHtml {
- param([string]$Page = 'onboard')
-
-$style = @'
-
-'@
-
-$script = @'
-
-'@
-
-$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
-
-
-
-
-
-
-
-"@
-
- # Build the checkbox HTML/JS from $Global:Tasks
- $onboardLeft = Build-Checkboxes -Page 'onboard' -Column 'left'
- $onboardRight = Build-Checkboxes -Page 'onboard' -Column 'right'
- $offboard = Build-Checkboxes -Page 'offboard' -Column ''
- $tweaks = Build-Checkboxes -Page 'tweaks' -Column ''
- $apps = Build-Checkboxes -Page 'SVSApps' -Column ''
-
- $tasksJsAll = (
- $Global:Tasks | ForEach-Object {
- " { id: '$($_.Id)', handler: '/$($_.Name)', label: '$($_.Label)' }"
- }
- ) -join ",`n"
-
- $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 HTTP helpers + Handlers
- 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 {
- $raw = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd()
- $pw = (ConvertFrom-Json $raw).password
- $Global:WebhookPassword = $pw
- $sites = Install-DattoRMM -UseWebhook -WebhookPassword $pw -FetchSites
- Respond-JSON $Context $sites
- } catch {
- Write-LogHybrid "Handle-FetchSites error: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent
- $Context.Response.StatusCode = 500
- Respond-Text $Context "Internal server error fetching sites."
- }
- }
-
- function Handle-SetSVSPowerPlan { param($Context)
- Set-SVSPowerPlan
- 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: $($_.Exception.Message)" "Error" "OnBoard"
- Respond-Text $Context "ERROR: $($_.Exception.Message)"
- }
- }
-
- function Handle-InstallCyberQP { param($Context)
- Install-CyberQP
- Write-LogHybrid "CyberQP installed" "Success" "OnBoard"
- Respond-Text $Context "CyberQP installed"
- }
-
- function Handle-InstallThreatLocker { param($Context)
- Install-ThreatLocker
- Write-LogHybrid "ThreatLocker installed" "Success" "OnBoard"
- Respond-Text $Context "ThreatLocker installed"
- }
-
- function Handle-InstallRocketCyber { param($Context)
- Install-RocketCyber
- Write-LogHybrid "RocketCyber installed" "Success" "OnBoard"
- Respond-Text $Context "RocketCyber installed"
- }
-
- function Handle-InstallSVSHelpDesk { param($Context)
- Install-SVSHelpDesk
- Write-LogHybrid "SVS HelpDesk installed" "Success" "OnBoard"
- Respond-Text $Context "SVS HelpDesk installed"
- }
-
- # NEW: Winget app handlers
- function Handle-InstallChrome { param($Context)
- try {
- winget install --id=Google.Chrome --silent --accept-package-agreements --accept-source-agreements
- Write-LogHybrid "Installed Google Chrome via winget" "Success" "SVSApps" -LogToEvent
- Respond-Text $Context "Chrome installed"
- } catch {
- Write-LogHybrid "Chrome install failed: $($_.Exception.Message)" "Error" "SVSApps" -LogToEvent
- Respond-Text $Context "ERROR: $($_.Exception.Message)"
- }
- }
-
- function Handle-InstallAcrobat { param($Context)
- try {
- winget install --id=Adobe.Acrobat.Reader.64-bit --silent --accept-package-agreements --accept-source-agreements
- Write-LogHybrid "Installed Adobe Acrobat Reader (64-bit) via winget" "Success" "SVSApps" -LogToEvent
- Respond-Text $Context "Acrobat Reader installed"
- } catch {
- Write-LogHybrid "Acrobat install failed: $($_.Exception.Message)" "Error" "SVSApps" -LogToEvent
- Respond-Text $Context "ERROR: $($_.Exception.Message)"
- }
- }
-
- function Handle-InstallDattoRMM {
- param($Context)
- try {
- if ($Context.Request.HttpMethod -ne 'POST') {
- $Context.Response.StatusCode = 405
- Respond-Text $Context 'Use POST'
- return
- }
- $body = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd()
- $data = ConvertFrom-Json $body
- 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')
- Respond-Text $Context "Triggered DattoRMM for $($data.Name)"
- } catch {
- Write-LogHybrid "Handle-InstallDattoRMM error: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent
- $Context.Response.StatusCode = 500
- Respond-Text $Context "Internal server error during DattoRMM install."
- }
- }
- #endregion HTTP helpers + Handlers
-
- #region Install-DattoRMM
-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
- )
-
- if ($SaveSitesList -and -not $FetchSites) {
- Write-LogHybrid "-SaveSitesList requires -FetchSites." "Error" "DattoRMM" -LogToEvent
- return
- }
-
- if ($UseWebhook) {
- if (-not $WebhookPassword) {
- Write-LogHybrid "Webhook password missing." "Error" "DattoRMM" -LogToEvent
- 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" -LogToEvent
- } catch {
- Write-LogHybrid "Failed to fetch webhook credentials: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent
- return
- }
- }
-
- if (-not $ApiUrl -or -not $ApiKey -or -not $ApiSecretKey) {
- Write-LogHybrid "Missing required API parameters." "Error" "DattoRMM" -LogToEvent
- return
- }
-
- [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" -LogToEvent
- } catch {
- Write-LogHybrid "OAuth token fetch failed: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent
- return
- }
- $headers = @{ Authorization = "Bearer $token" }
-
- 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" -LogToEvent
-
- 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" -LogToEvent
- }
-
- return $siteList
- } catch {
- Write-LogHybrid "Failed to fetch sites: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent
- return @()
- }
- }
-
- 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" -LogToEvent
- } catch {
- Write-LogHybrid "Variable fetch failed: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent
- }
- $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" -LogToEvent
- } catch {
- Write-LogHybrid "Failed to write '$($v.name)': $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent
- }
- }
- }
-
- 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" -LogToEvent
- Start-Process -FilePath $tmp -NoNewWindow
- Write-LogHybrid "RMM agent installer launched." "Success" "DattoRMM" -LogToEvent
- } catch {
- Write-LogHybrid "Agent install failed: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent
- }
- }
- }
-
- 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" -LogToEvent
- } catch {
- Write-LogHybrid "Save-copy failed: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent
- }
- }
-
- if (-not ($PushSiteVars -or $InstallRMM -or $SaveCopy)) {
- Write-LogHybrid "No action specified. Use -FetchSites, -SaveSitesList, -PushSiteVars, -InstallRMM, or -SaveCopy." "Warning" "DattoRMM" -LogToEvent
- }
-}
- #endregion Install-DattoRMM
-
- #region Dispatch-Request
- function Dispatch-Request {
- param($Context)
-
- $path = $Context.Request.Url.AbsolutePath.TrimStart('/')
-
- if ($path -eq 'quit') {
- Write-LogHybrid "Shutdown requested" "Info" "Server" -LogToEvent
- Respond-Text $Context "Server shutting down."
- $Global:Listener.Stop()
- return
- }
-
- if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'getpw') {
- Handle-FetchSites $Context
- return
- }
-
- 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 = $Global:Tasks | Where-Object Name -EQ $path
- if ($task) { & $task.HandlerFn $Context; return }
-
- $Context.Response.StatusCode = 404
- Respond-Text $Context '404 - Not Found'
- }
- #endregion Dispatch-Request
-
- #region MAIN MODE SWITCH
- switch ($PSCmdlet.ParameterSetName) {
- 'Toolkit' {
- Write-LogHybrid "Toolkit-only mode" "Info" "Startup" -LogToEvent
- Install-SVSMSP -InstallToolkit
- return
- }
- 'Cleanup' {
- Write-LogHybrid "Running Toolkit cleanup mode" "Info" "Startup" -LogToEvent
- Install-SVSMSP -Cleanup
- return
- }
- 'DattoFetch' {
- Write-LogHybrid "Fetching site list only…" "Info" "DattoAuth" -LogToEvent
- $sites = Install-DattoRMM -UseWebhook -WebhookPassword $WebhookPassword -FetchSites -SaveSitesList:$SaveSitesList -OutputFile $OutputFile
- Write-LogHybrid "Done." "Success" "DattoAuth" -LogToEvent
- return
- }
- 'DattoInstall' {
- Write-LogHybrid "Headless DattoRMM deploy" "Info" "DattoAuth" -LogToEvent
- 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 "Starting ScriptMonkey UI on http://localhost:$Port/" Info Startup
- try {
- Start-Server
- try {
- Start-Process "msedge.exe" -ArgumentList "--app=http://localhost:$Port" -ErrorAction Stop
- } catch {
- Start-Process "http://localhost:$Port"
- }
- } catch {
- Write-LogHybrid "Failed to start server: $($_.Exception.Message)" Error Startup -LogToEvent
- }
- return
- }
-
- }
- #endregion MAIN MODE SWITCH
-
- #region Bootstrap NuGet/PSGallery silently
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
- $ProgressPreference = 'SilentlyContinue'
- $ConfirmPreference = 'None'
-
- $provPath = "$env:ProgramData\PackageManagement\ProviderAssemblies"
- if (-not (Test-Path $provPath)) {
- try {
- New-Item -Path $provPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
- Write-LogHybrid "Created missing provider folder: $provPath" "Info" "Bootstrap" -LogToEvent
- } catch {
- Write-LogHybrid "Failed to create provider folder: $($_.Exception.Message)" "Warning" "Bootstrap" -LogToEvent
- }
- }
-
- if (-not (Get-Command Install-PackageProvider -ErrorAction SilentlyContinue)) {
- try {
- Install-Module PowerShellGet -Force -AllowClobber -Confirm:$false -ErrorAction Stop
- Write-LogHybrid "Installed PowerShellGet module" "Info" "Bootstrap" -LogToEvent
- } catch {
- Write-LogHybrid "PowerShellGet install failed: $($_.Exception.Message)" "Error" "Bootstrap" -LogToEvent
- }
- }
-
- $pkgMgmtVersion = (Get-Module PackageManagement -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Version
- if ($pkgMgmtVersion -lt [Version]"1.3.1") {
- try {
- Install-Module PackageManagement -Force -AllowClobber -Confirm:$false -ErrorAction Stop
- Write-LogHybrid "Updated PackageManagement to latest version" "Info" "Bootstrap" -LogToEvent
- } catch {
- Write-LogHybrid "PackageManagement update failed: $($_.Exception.Message)" "Warning" "Bootstrap" -LogToEvent
- }
- }
-
- Import-Module PackageManagement -Force -ErrorAction SilentlyContinue | Out-Null
- Import-Module PowerShellGet -Force -ErrorAction SilentlyContinue | Out-Null
-
- $gallery = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue
- if ($gallery -and $gallery.InstallationPolicy -ne 'Trusted') {
- try {
- Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop
- Write-LogHybrid "PSGallery marked as Trusted" "Info" "Bootstrap" -LogToEvent
- } catch {
- Write-LogHybrid "Failed to trust PSGallery: $($_.Exception.Message)" "Warning" "Bootstrap" -LogToEvent
- }
- }
-
- $nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue
- if (-not $nuget) {
- try {
- Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false -ErrorAction Stop
- $nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue
- Write-LogHybrid "Installed NuGet provider v$($nuget.Version)" "Info" "Bootstrap" -LogToEvent
- } catch {
- Write-LogHybrid "NuGet install failed: $($_.Exception.Message)" "Error" "Bootstrap" -LogToEvent
- }
- } else {
- Write-LogHybrid "NuGet provider already present (v$($nuget.Version))" "Info" "Bootstrap" -LogToEvent
- }
-
- try { Import-PackageProvider -Name NuGet -Force -ErrorAction Stop | Out-Null }
- catch { Write-LogHybrid "NuGet provider import failed: $($_.Exception.Message)" "Error" "Bootstrap" -LogToEvent }
- #endregion Bootstrap NuGet/PSGallery silently
-}
-# end function Invoke-ScriptMonkey
-
-# Entrypoint
-if ($MyInvocation.InvocationName -eq '.') {
- # dot-sourced, don't invoke
-} elseif ($PSCommandPath) {
- Invoke-ScriptMonkey @PSBoundParameters
-} else {
- if ($args.Count -gt 0) {
- $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++ } else { $namedArgs[$key] = $true }
- }
- }
- Invoke-ScriptMonkey @namedArgs
- } else {
- Invoke-ScriptMonkey
- }
-}