diff --git a/samy.ps1 b/samy.ps1
index e98097e..c28603f 100644
--- a/samy.ps1
+++ b/samy.ps1
@@ -2,7 +2,7 @@
#endregion changes to be done
-## Last changes made should fix the issues we had wen running thi in Windows 11 25H2
+## Last changes made should fix the issues we had when running this in Windows 11 25H2
<#
.SYNOPSIS
@@ -19,72 +19,24 @@
- 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.
+ - Agent download and 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.
+ - Idempotent and 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
& ([ScriptBlock]::Create( (iwr 'https://sm.svstools.com' -UseBasicParsing).Content )) `
- -UseWebhook
- -WebhookPassword 'pwd'
- -SiteUID 'site-123'
- -SiteName 'Acme Corp'
- -PushSiteVars
+ -UseWebhook `
+ -WebhookPassword 'pwd' `
+ -SiteUID 'site-123' `
+ -SiteName 'Acme Corp' `
+ -PushSiteVars `
-InstallRMM
-
- # Headlessly installs the Datto RMM agent on “Acme Corp” and writes site variables to the registry.
.EXAMPLE
& ([ScriptBlock]::Create( (iwr 'https://sm.svstools.com' -UseBasicParsing).Content )) `
@@ -96,8 +48,6 @@
-PushSiteVars `
-InstallRMM
- # Headlessly installs the Datto RMM agent on “Acme Corp” and writes site variables to the registry.
-
.EXAMPLE
& ([ScriptBlock]::Create( (iwr 'https://sm.svstools.com' -UseBasicParsing).Content )) `
-UseWebhook `
@@ -106,8 +56,6 @@
-SaveSitesList `
-OutputFile 'sites.json'
- # Fetches the full site list via webhook and saves it as JSON to your Desktop.
-
.EXAMPLE
& ([ScriptBlock]::Create( (iwr 'https://sm.svstools.com' -UseBasicParsing).Content )) `
-ApiUrl 'https://api.example.com' `
@@ -117,8 +65,6 @@
-SiteName 'Acme Corp' `
-SaveCopy
- # Downloads the RMM installer for “Acme Corp” and saves a copy under C:\Temp without running it.
-
.EXAMPLE
& ([ScriptBlock]::Create( (iwr 'https://sm.svstools.com' -UseBasicParsing).Content )) `
-ApiUrl 'https://api.example.com' `
@@ -129,8 +75,6 @@
-InstallRMM `
-WhatIf
- # Shows what would happen when installing the RMM agent, without making any changes.
-
.EXAMPLE
& ([ScriptBlock]::Create((iwr 'sm.svstools.ca').Content )) -SilentInstall
@@ -138,8 +82,9 @@
& ([ScriptBlock]::Create((iwr 'sm.svstools.com').Content)) -Cleanup
#>
+
#region Safely bypass Restricted Execution Policy
-# ─── Safely bypass Restricted Execution Policy ───
+# --- Safely bypass Restricted Execution Policy ---
if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage' -or
(Get-ExecutionPolicy) -eq 'Restricted') {
@@ -154,281 +99,254 @@ if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage' -or
exit
}
-# ─── TLS and silent install defaults ───
+# --- 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
- # ─────────────────────────────────────────────────────────────────────────
-
+function Invoke-ScriptMonkey {
+ # ---------------------------------------------------------------------
+ # PARAMETERS + GLOBALS
+ # ---------------------------------------------------------------------
[CmdletBinding(
- DefaultParameterSetName='UI',
- SupportsShouldProcess=$true,
- ConfirmImpact= 'Medium'
+ DefaultParameterSetName = 'UI',
+ SupportsShouldProcess = $true,
+ ConfirmImpact = 'Medium'
)]
param(
- # ─────────────────────────────────────────────────────────
# Toolkit-only mode
- [Parameter(Mandatory,ParameterSetName='Toolkit')][switch]$SilentInstall,
-
- # ─────────────────────────────────────────────────────────
- # remove Toolkit
- [Parameter(Mandatory,ParameterSetName='Cleanup')][switch]$Cleanup,
+ [Parameter(Mandatory, ParameterSetName = 'Toolkit')]
+ [switch]$SilentInstall,
- # ─────────────────────────────────────────────────────────
- # Datto headless mode
+ # Remove toolkit
+ [Parameter(Mandatory, ParameterSetName = 'Cleanup')]
+ [switch]$Cleanup,
- # ─── DattoFetch & DattoInstall share the webhook creds ─────────────
- [Parameter(Mandatory,ParameterSetName='DattoFetch')]
- [Parameter(Mandatory,ParameterSetName='DattoInstall')]
+ # Datto headless mode: shared webhook bits
+ [Parameter(Mandatory, ParameterSetName = 'DattoFetch')]
+ [Parameter(Mandatory, ParameterSetName = 'DattoInstall')]
[switch]$UseWebhook,
- [Parameter(Mandatory,ParameterSetName='DattoFetch')]
- [Parameter(Mandatory,ParameterSetName='DattoInstall')]
+ [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
+ # DattoFetch only
+ [Parameter(ParameterSetName = 'DattoFetch')]
+ [switch]$FetchSites,
+
+ [Parameter(ParameterSetName = 'DattoFetch')]
+ [switch]$SaveSitesList,
+
+ [Parameter(ParameterSetName = 'DattoFetch')]
+ [ValidatePattern('\.csv$|\.json$')]
+ [string]$OutputFile = 'datto_sites.csv',
+
+ # DattoInstall only
+ [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 VARIABLES
+ # ---------------------------------------------------------------------
+ $Port = 8082
$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()
+ $Global:LogCache = [System.Collections.ArrayList]::new()
}
- #endregion global variables
+ # =====================================================================
+ # SVS MODULE
+ # =====================================================================
+ function Initialize-NuGetProvider {
+ [CmdletBinding()]
+ param()
- #region SVS Module
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+ $ProgressPreference = 'SilentlyContinue'
+ $ConfirmPreference = 'None'
- function Initialize-NuGetProvider {
- [CmdletBinding()]
- param()
-
- #region — guarantee NuGet provider is present without prompting
-
- # ─── Silent defaults ───
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
- $ProgressPreference = 'SilentlyContinue'
- $ConfirmPreference = 'None'
-
- # ─── Pre-create folder if running as SYSTEM (avoids NuGet install bug) ───
- $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
- }
- }
-
- # ─── Ensure PowerShellGet is available ───
- 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
- }
- }
-
- # ─── Ensure PackageManagement is up-to-date ───
- $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 modules silently ───
- Import-Module PackageManagement -Force -ErrorAction SilentlyContinue | Out-Null
- Import-Module PowerShellGet -Force -ErrorAction SilentlyContinue | Out-Null
-
- # ─── Trust PSGallery if not already ───
- $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
- }
- }
-
- # ─── Ensure NuGet is installed silently ───
- $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
- }
-
- # ─── Final import check ───
+ $provPath = "$env:ProgramData\PackageManagement\ProviderAssemblies"
+ if (-not (Test-Path $provPath)) {
try {
- Import-PackageProvider -Name NuGet -Force -ErrorAction Stop | Out-Null
+ New-Item -Path $provPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
+ Write-LogHybrid "Created missing provider folder: $provPath" Info Bootstrap -LogToEvent
} catch {
- Write-LogHybrid "NuGet provider import failed: $($_.Exception.Message)" Error Bootstrap -LogToEvent
+ Write-LogHybrid "Failed to create provider folder: $($_.Exception.Message)" Warning Bootstrap -LogToEvent
}
-
- #endregion — guarantee NuGet provider is present without prompting
}
+ 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
+ }
+ }
- function Install-SVSMSP {
+ $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
+ }
+ }
+
+ 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/"
- )
+ [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"
+ Write-LogHybrid "Cleanup mode enabled. Starting cleanup..." Info SVSModule
- # Attempt to uninstall all versions of SVSMSP
try {
Uninstall-Module -Name SVSMSP -AllVersions -Force -ErrorAction Stop
- Write-LogHybrid "SVSMSP module uninstalled from system." "Success" "SVSModule" -LogToEvent
+ Write-LogHybrid "SVSMSP module uninstalled from system." Success SVSModule -LogToEvent
}
catch {
- # If no module was found, just warn and continue
if ($_.Exception.Message -match 'No match was found') {
- Write-LogHybrid "No existing SVSMSP module found to uninstall." "Warning" "SVSModule" -LogToEvent
+ Write-LogHybrid "No existing SVSMSP module found to uninstall." Warning SVSModule -LogToEvent
}
else {
- Write-LogHybrid "Failed to uninstall SVSMSP: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent
+ Write-LogHybrid "Failed to uninstall SVSMSP: $($_.Exception.Message)" Error SVSModule -LogToEvent
}
}
- # Remove the custom repository if registered
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
+ Write-LogHybrid "SVS_Repo repository unregistered." Success SVSModule -LogToEvent
}
catch {
- Write-LogHybrid "Failed to unregister SVS_Repo: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent
+ Write-LogHybrid "Failed to unregister SVS_Repo: $($_.Exception.Message)" Error SVSModule -LogToEvent
}
}
- # Finally, remove it from the current session if loaded
if (Get-Module -Name SVSMSP) {
try {
Remove-Module SVSMSP -Force -ErrorAction Stop
- Write-LogHybrid "SVSMSP module removed from current session." "Success" "SVSModule" -LogToEvent
+ 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
+ Write-LogHybrid "Failed to remove SVSMSP from session: $($_.Exception.Message)" Error SVSModule -LogToEvent
}
}
}
- function Remove-SVSDeploymentRegKey {
- $regKey = 'HKLM:\Software\SVS'
-
- try {
- if (Test-Path $regKey) {
- Remove-Item -Path $regKey -Recurse -Force
- Write-LogHybrid "Registry key '$regKey' deleted successfully." "Success" "SVSModule" -LogToEvent
- }
- else {
- Write-LogHybrid "Registry key '$regKey' not found; nothing to delete." "Info" "SVSModule" -LogToEvent
- }
- }
- catch {
- Write-LogHybrid "Failed to delete registry key '$regKey': $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent
- }
- }
-
+ function Remove-SVSDeploymentRegKey {
+ $regKey = 'HKLM:\Software\SVS'
+ try {
+ if (Test-Path $regKey) {
+ Remove-Item -Path $regKey -Recurse -Force
+ Write-LogHybrid "Registry key '$regKey' deleted successfully." Success SVSModule -LogToEvent
+ }
+ else {
+ Write-LogHybrid "Registry key '$regKey' not found; nothing to delete." Info SVSModule -LogToEvent
+ }
+ }
+ catch {
+ Write-LogHybrid "Failed to delete registry key '$regKey': $($_.Exception.Message)" Error SVSModule -LogToEvent
+ }
+ }
function Perform-ToolkitInstallation {
- Initialize-NuGetProvider
- 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
- }
+ Initialize-NuGetProvider
+ Perform-Cleanup
- Write-LogHybrid "Install-SVSMSP called" "Info" "SVSModule" -LogToEvent
- if ($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
Remove-SVSDeploymentRegKey
return
-
- }
- if ($InstallToolkit) {
- Perform-ToolkitInstallation; return
- }
- # default if no switch passed:
- Perform-ToolkitInstallation
}
- #endregion SVS Module
+ if ($InstallToolkit) {
+ Perform-ToolkitInstallation
+ return
+ }
-
+ Perform-ToolkitInstallation
+ }
- #region Write-Log
-
- # Fallback logger used when the SVSMSP module (and its Write-Log) is not available.
- # Mirrors the behaviour of the toolkit Write-Log (v1.5), including:
- # - Default EventLog: "SVSMSP Events" (out of Application log)
- # - Default EventSource: "SVSMSP_Module"
- # - Level-based Event IDs and console colors
- # - Global in-memory log cache
- # - One-time Event Log/source initialization with optional auto-elevation
+ # =====================================================================
+ # WRITE-LOG (primary implementation)
+ # =====================================================================
function Write-LogHelper {
<#
.SYNOPSIS
- Standardized logging utility with console/file output and Windows Event Log support,
- including one-time event source initialization and optional auto-elevated creation
- of a custom log/source. (Fallback implementation for ScriptMonkey.)
-
- .DESCRIPTION
- Mirrors the SVSMSP toolkit Write-Log so that Write-LogHybrid can safely fall back
- when the module isn't loaded.
+ Standardized logging utility with console/file output and Windows Event Log support.
.NOTES
Default EventLog : SVSMSP Events
@@ -448,7 +366,6 @@ $ConfirmPreference = 'None'
[string]$EventSource = "SVSMSP_Module",
- # Custom log name so you get your own node under "Applications and Services Logs"
[string]$EventLog = "SVSMSP Events",
[int]$CustomEventID,
@@ -458,7 +375,6 @@ $ConfirmPreference = 'None'
[switch]$PassThru
)
- # ---------- Event ID / console color ----------
$EventID = if ($CustomEventID) { $CustomEventID } else {
switch ($Level) {
"Info" { 1000 }
@@ -480,7 +396,6 @@ $ConfirmPreference = 'None'
$FormattedMessage = "[$Level] [$TaskCategory] $Message (Event ID: $EventID)"
Write-Host $FormattedMessage -ForegroundColor $Color
- # ---------- In-memory cache ----------
if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) {
$Global:LogCache = [System.Collections.ArrayList]::new()
}
@@ -492,7 +407,6 @@ $ConfirmPreference = 'None'
}
[void]$Global:LogCache.Add($logEntry)
- # ---------- Optional file output ----------
if ($LogFile) {
try {
"$($logEntry.Timestamp) $FormattedMessage" |
@@ -503,10 +417,7 @@ $ConfirmPreference = 'None'
}
}
- # ---------- Windows Event Log handling with one-time init + optional auto-elevate ----------
if ($LogToEvent) {
-
- # Per-run cache for (LogName|Source) init state
if (-not $Global:EventSourceInitState) {
$Global:EventSourceInitState = @{}
}
@@ -515,7 +426,7 @@ $ConfirmPreference = 'None'
"Info" { "Information" }
"Warning" { "Warning" }
"Error" { "Error" }
- "Success" { "Information" } # treat success as info in Event Log
+ "Success" { "Information" }
default { "Information" }
}
@@ -525,10 +436,7 @@ $ConfirmPreference = 'None'
-not $Global:EventSourceInitState[$sourceKey]) {
try {
- # Only bother if the source doesn't already exist
if (-not [System.Diagnostics.EventLog]::SourceExists($EventSource)) {
-
- # Check if current token is admin
$isAdmin = $false
try {
$current = [Security.Principal.WindowsIdentity]::GetCurrent()
@@ -542,11 +450,9 @@ $ConfirmPreference = 'None'
}
if ($isAdmin) {
- # Elevated already: create log/source directly
New-EventLog -LogName $EventLog -Source $EventSource -ErrorAction Stop
}
else {
- # Not elevated: run a one-off helper as admin to create log/source
$helperScript = @"
if (-not [System.Diagnostics.EventLog]::SourceExists('$EventSource')) {
New-EventLog -LogName '$EventLog' -Source '$EventSource'
@@ -561,7 +467,6 @@ if (-not [System.Diagnostics.EventLog]::SourceExists('$EventSource')) {
$helperScript | Set-Content -Path $tempPath -Encoding UTF8
try {
- # This will trigger UAC prompt in interactive sessions
$null = Start-Process -FilePath "powershell.exe" `
-ArgumentList "-ExecutionPolicy Bypass -File `"$tempPath`"" `
-Verb RunAs -Wait -PassThru
@@ -575,7 +480,6 @@ if (-not [System.Diagnostics.EventLog]::SourceExists('$EventSource')) {
}
}
- # Re-check after creation attempt
if ([System.Diagnostics.EventLog]::SourceExists($EventSource)) {
$Global:EventSourceInitState[$sourceKey] = $true
}
@@ -590,7 +494,6 @@ if (-not [System.Diagnostics.EventLog]::SourceExists('$EventSource')) {
}
}
- # Only write if initialization succeeded
if ($Global:EventSourceInitState[$sourceKey]) {
try {
$EventMessage = "TaskCategory: $TaskCategory | Message: $Message"
@@ -601,20 +504,12 @@ if (-not [System.Diagnostics.EventLog]::SourceExists('$EventSource')) {
}
}
}
- # ------------------------------------------------------------------------------------------
if ($PassThru) {
return $logEntry
}
}
-
- # ─────────────────────────────────────────────────────────────────────────
- # WRITE-LOG HYBRID
- # Uses module Write-Log if present; otherwise falls back to Write-LogHelper.
- # Defaults aligned with toolkit:
- # EventSource = "SVSMSP_Module"
- # EventLog = "SVSMSP Events"
- # ─────────────────────────────────────────────────────────────────────────
+
function Write-LogHybrid {
[CmdletBinding()]
param(
@@ -644,7 +539,6 @@ if (-not [System.Diagnostics.EventLog]::SourceExists('$EventSource')) {
$formatted = "[$Level] [$TaskCategory] $Message"
- # Build the common parameter set for forwarding into Write-Log / Write-LogHelper
$invokeParams = @{
Message = $Message
Level = $Level
@@ -665,10 +559,8 @@ if (-not [System.Diagnostics.EventLog]::SourceExists('$EventSource')) {
}
if ($PSBoundParameters.ContainsKey('ForegroundColorOverride')) {
- # 1) print to console with the override color
Write-Host $formatted -ForegroundColor $ForegroundColorOverride
- # 2) then forward the call (sans the override) to Write-Log or Write-LogHelper
if (Get-Command Write-Log -ErrorAction SilentlyContinue) {
Write-Log @invokeParams
}
@@ -677,7 +569,6 @@ if (-not [System.Diagnostics.EventLog]::SourceExists('$EventSource')) {
}
}
else {
- # No override: let Write-Log / Write-LogHelper handle everything (including console color)
if (Get-Command Write-Log -ErrorAction SilentlyContinue) {
Write-Log @invokeParams
}
@@ -687,381 +578,222 @@ if (-not [System.Diagnostics.EventLog]::SourceExists('$EventSource')) {
}
}
- #endregion Write-Log
-
-
- # This function is used as a fallback if the SVSMSP module is not installed
- # Should change this "[string]$EventLog = "Application", => [string]$EventLog = "SVS Scripting", "
- 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 = "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)"
-
- # ─── Console Output ─────────────────────────────────────────────
- Write-Host $fmt -ForegroundColor $color
-
- # ─── In-Memory Cache ─────────────────────────────────────────────
-
- # ─── In-Memory Cache ─────────────────────────────────────────────
- 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
-
-
- # ─── File Logging ────────────────────────────────────────────────
- 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: $_" -ForegroundColor Yellow
- }
- }
-
- # ─── Event Log ──────────────────────────────────────────────────
- if ($LogToEvent) {
- try {
- # 1) Ensure your custom source/log exist
- if (-not [System.Diagnostics.EventLog]::SourceExists($EventSource)) {
- New-EventLog -LogName $EventLog -Source $EventSource -ErrorAction Stop
- }
- } catch {
- Write-Host "[Warning] Could not create event log '$EventLog' or source '$EventSource': $($_.Exception.Message)" -ForegroundColor Yellow
- return
- }
-
- # 2) Map level to entry type
- $entryType = if ($Level -in 'Warning','Error') { $Level } else { 'Information' }
-
- # 3) Write to the Windows event log
- try {
- Write-EventLog `
- -LogName $EventLog `
- -Source $EventSource `
- -EntryType $entryType `
- -EventID $EventID `
- -Message $fmt
- }
- catch {
- Write-Host "[Warning] EventLog failed: $($_.Exception.Message)" -ForegroundColor Yellow
- }
- }
-
- if ($PassThru) { return $Global:LogCache[-1] }
- }
-
- # ─────────────────────────────────────────────────────────────────────────
- # WRITE-LOG HYBRID (single definition, chooses at runtime if we use the
- # Write-Log from the module or the built-in Write-LogHelper funtions )
- # Should chanfge this "[string]$EventLog = "Application"," => "[string]$EventLog = "SVS Scripting","
- # ─────────────────────────────────────────────────────────────────────────
-
- 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 = "Application",
- [ValidateSet("Black","DarkGray","Gray","White","Red","Green","Blue","Yellow","Magenta","Cyan")]
- [string]$ForegroundColorOverride
- )
-
- $formatted = "[$Level] [$TaskCategory] $Message"
-
- if ($PSBoundParameters.ContainsKey('ForegroundColorOverride')) {
- # 1) print to console with the override color
- Write-Host $formatted -ForegroundColor $ForegroundColorOverride
-
- # 2) then forward the call (sans the override) to Write-Log or Write-LogHelper
- $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 {
- # No override: let Write-Log / Write-LogHelper handle everything (including console color)
- 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
-
- # 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:SamyTasks = @(
+ # =====================================================================
+ # SAMY TASK DEFINITIONS
+ # =====================================================================
+ $Global:SamyTasks = @(
# On-Boarding, left column
- @{ Id='setSVSPowerplan'; Name='setSVSPowerplan'; Label='Set SVS Powerplan'; HandlerFn='Handle-setSVSPowerPlan'; Page='onboard'; Column='left' },
+ @{ 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='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' }
- )
+ 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='offUninstallCyberQP'; Name='offUninstallCyberQP'; Label='Uninstall CyberQP'; HandlerFn='Handle-UninstallCyberQP'; Page='offboard' },
- @{ Id='offUninstallSVSHelpDesk'; Name='offUninstallSVSHelpDesk'; Label='Uninstall SVS HelpDesk'; HandlerFn='Handle-UninstallSVSHelpDesk'; Page='offboard' },
- @{ Id='offUninstallThreatLocker'; Name='offUninstallThreatLocker'; Label='Uninstall ThreatLocker'; HandlerFn='Handle-UninstallThreatLocker'; Page='offboard' },
- @{ Id='offUninstallRocketCyber'; Name='offUninstallRocketCyber'; Label='Uninstall RocketCyber'; HandlerFn='Handle-UninstallRocketCyber'; Page='offboard' },
- @{ Id='offCleanupSVSMSPModule'; Name='offCleanupSVSMSPModule'; Label='Cleanup SVSMSP Toolkit'; HandlerFn='Handle-CleanupSVSMSP'; Page='offboard' },
+ @{ 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' },
- # Tweaks
- @{ Id='disableAnimations'; Name='disableAnimations'; Label='Disable Animations'; HandlerFn='Disable-Animations'; Page='tweaks' },
+ # Off-Boarding
+ @{ Id='offUninstallCyberQP'; Name='offUninstallCyberQP'; Label='Uninstall CyberQP'; HandlerFn='Handle-UninstallCyberQP'; Page='offboard' },
+ @{ Id='offUninstallSVSHelpDesk'; Name='offUninstallSVSHelpDesk'; Label='Uninstall SVS HelpDesk'; HandlerFn='Handle-UninstallSVSHelpDesk'; Page='offboard' },
+ @{ Id='offUninstallThreatLocker'; Name='offUninstallThreatLocker'; Label='Uninstall ThreatLocker'; HandlerFn='Handle-UninstallThreatLocker'; Page='offboard' },
+ @{ Id='offUninstallRocketCyber'; Name='offUninstallRocketCyber'; Label='Uninstall RocketCyber'; HandlerFn='Handle-UninstallRocketCyber'; Page='offboard' },
+ @{ Id='offCleanupSVSMSPModule'; Name='offCleanupSVSMSPModule'; Label='Cleanup SVSMSP Toolkit'; HandlerFn='Handle-CleanupSVSMSP'; Page='offboard' },
- # 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' }
+ # Tweaks
+ @{ Id='disableAnimations'; Name='disableAnimations'; Label='Disable Animations'; HandlerFn='Disable-Animations'; Page='tweaks' },
- )
-Write-LogHybrid "Tasks by page: onboard=$(
- ($Global:SamyTasks | Where-Object Page -eq 'onboard').Count
-) offboard=$(
- ($Global:SamyTasks | Where-Object Page -eq 'offboard').Count
-) tweaks=$(
- ($Global:SamyTasks | Where-Object Page -eq 'tweaks').Count
-) apps=$(
- ($Global:SamyTasks | Where-Object Page -eq 'SVSApps').Count
-)" Info UI -LogToEvent
-
- #endregion building the Menus
-
- #region Build-Checkboxes
- function Build-Checkboxes {
- param(
- [Parameter(Mandatory)][string]$Page,
- [string]$Column
+ # 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' }
)
- # Start with all tasks on the given page
- $tasks = $Global:SamyTasks | Where-Object Page -EQ $Page
+ Write-LogHybrid "Tasks by page: onboard=$(
+ ($Global:SamyTasks | Where-Object Page -eq 'onboard').Count
+ ) offboard=$(
+ ($Global:SamyTasks | Where-Object Page -eq 'offboard').Count
+ ) tweaks=$(
+ ($Global:SamyTasks | Where-Object Page -eq 'tweaks').Count
+ ) apps=$(
+ ($Global:SamyTasks | Where-Object Page -eq 'SVSApps').Count
+ )" Info UI -LogToEvent
- # Only filter by Column when it actually matters (onboard left/right)
- if (-not [string]::IsNullOrEmpty($Column)) {
- $tasks = $tasks | Where-Object Column -EQ $Column
- }
+ # =====================================================================
+ # BUILD-CHECKBOXES
+ # =====================================================================
+ function Build-Checkboxes {
+ param(
+ [Parameter(Mandatory)][string]$Page,
+ [string]$Column
+ )
- (
- $tasks |
- ForEach-Object {
- $taskId = $_.Id
- $tooltip = if ($_.PSObject.Properties.Name -contains 'Tooltip' -and $_.Tooltip) {
- " title='$($_.Tooltip)'"
- } else { '' }
+ $tasks = $Global:SamyTasks | Where-Object Page -EQ $Page
- $html = ""
+ if (-not [string]::IsNullOrEmpty($Column)) {
+ $tasks = $tasks | Where-Object Column -EQ $Column
+ }
- if ($_.SubOptions) {
- $subHtml = (
- $_.SubOptions |
- ForEach-Object {
- ""
- }
- ) -join "`n"
+ (
+ $tasks |
+ ForEach-Object {
+ $taskId = $_.Id
+ $tooltip = if ($_.PSObject.Properties.Name -contains 'Tooltip' -and $_.Tooltip) {
+ " title='$($_.Tooltip)'"
+ } else { '' }
- $html += @"
-
- $subHtml
-
+ $html = ""
+
+ if ($_.SubOptions) {
+ $subHtml = (
+ $_.SubOptions |
+ ForEach-Object {
+ ""
+ }
+ ) -join "`n"
+
+ $html += @"
+
+$subHtml
+
"@
+ }
+
+ $html
}
-
- $html
- }
- ) -join "`n"
- } # end function Build-checkboxes
-
-
- #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 "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 {}
+ ) -join "`n"
}
- 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
+ # =====================================================================
+ # MODULE VERSION HTML
+ # =====================================================================
+ function Get-ModuleVersionHtml {
+ $mod = Get-Module -ListAvailable -Name SVSMSP | Sort-Object Version -Descending | Select-Object -First 1
+ if ($mod) {
+ return "Module Version: $($mod.Version)
"
}
- }
- elseif ($_.Exception.NativeErrorCode -in 32,183) {
- $old = $Port
- $Port = Get-NextFreePort -Start ($Port + 1)
+ return "SVSMSP_Module not found
"
+ }
+
+ # =====================================================================
+ # SERVER HELPERS
+ # =====================================================================
+ 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."
+ }
+
+ function Start-Server {
$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
- }
- }
+ $primaryPrefix = "http://localhost:$Port/"
+ $wildcardPrefix = "http://+:$Port/"
- try {
- while ($Global:Listener.IsListening) {
- $ctx = $Global:Listener.GetContext()
try {
- Dispatch-Request $ctx
- } catch {
- Write-LogHybrid "Dispatch error: $($_.Exception.Message)" Error Server -LogToEvent
+ $Global:Listener.Prefixes.Add($primaryPrefix)
+ $Global:Listener.Start()
+ Write-LogHybrid "Listening on $primaryPrefix" Info 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')
- if (-not $Page) { $Page = 'onboard' }
-
- #
- # 1) Build checkbox HTML per page/column
- #
- $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 ''
-
- #
- # 2) Build the JS tasks array once (this is the only dynamic JS piece)
- #
- $tasksJsAll = (
- $Global:SamyTasks | ForEach-Object {
- " { id: '$($_.Id)', handler: '/$($_.Name)', label: '$($_.Label)' }"
+ 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
+ }
}
- ) -join ",`n"
- #
- # 3) HTML template that references external CSS/JS hosted on Gitea
- # (adjust the URLs to match your repo + branch)
- #
- $htmlTemplate = @"
+ 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
+ }
+ }
+
+ # =====================================================================
+ # REMOTE TEXT FETCH (GITEA) + UI HTML
+ # =====================================================================
+ function Get-RemoteText {
+ [CmdletBinding()]
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$Url
+ )
+
+ try {
+ $resp = Invoke-WebRequest -Uri $Url -UseBasicParsing -ErrorAction Stop
+ return $resp.Content
+ }
+ catch {
+ Write-LogHybrid "Get-RemoteText failed for $Url: $($_.Exception.Message)" Warning UI -LogToEvent
+ return ""
+ }
+ }
+
+ function Get-UIHtml {
+ param([string]$Page = 'onboard')
+ if (-not $Page) { $Page = 'onboard' }
+
+ $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:SamyTasks | ForEach-Object {
+ " { id: '$($_.Id)', handler: '/$($_.Name)', label: '$($_.Label)' }"
+ }
+ ) -join ",`n"
+
+ $styleContent = Get-RemoteText -Url "https://git.svstools.ca/SVS_Public_Repo/S.A.M.Y/raw/branch/main/samy.css"
+ $scriptContent = Get-RemoteText -Url "https://git.svstools.ca/SVS_Public_Repo/S.A.M.Y/raw/branch/main/samy.js"
+
+ $htmlTemplate = @"
@@ -1070,18 +802,17 @@ function Get-UIHtml {
Script Monkey
-
-
+
-

{{moduleVersion}}
-
Script Automation Monkey (Yeah!)
@@ -1128,7 +859,7 @@ function Get-UIHtml {
-
+
Off-Boarding
@@ -1166,19 +897,16 @@ function Get-UIHtml {
-
-
-
-
-
@@ -1188,730 +916,550 @@ function Get-UIHtml {
"@
- #
- # 4) Replace placeholders
- #
- $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)
+ $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)
- 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
-
- # ★ Store it globally for the next call ★
- $Global:WebhookPassword = $pw
-
- # 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
+ return $html
}
- catch {
- # Log the exception and return HTTP 500
- Write-LogHybrid "Handle-FetchSites error: $($_.Exception.Message)" Error DattoRMM -LogToEvent
- $Context.Response.StatusCode = 500
- Respond-Text $Context "Internal server error fetching sites."
+
+ # =====================================================================
+ # HTTP HELPERS AND 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()
}
- }
- # On-boarding handlers
- function Handle-SetSVSPowerPlan {
- param($Context)
+ 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()
+ }
- # 1) call into your module
- Set-SVSPowerPlan
+ 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()
+ }
- # 2) log & write back a simple text response
- Write-LogHybrid "PowerPlan set" "Success" "OnBoard"
- Respond-Text $Context "PowerPlan applied"
- }
+ function Handle-FetchSites {
+ param($Context)
- 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: $_"
- }
- }
+ try {
+ $raw = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd()
+ $pw = (ConvertFrom-Json $raw).password
- function Handle-InstallCyberQP {
- param($Context)
+ $Global:WebhookPassword = $pw
- # 1) call into your module
- Install-CyberQP
+ $sites = Install-DattoRMM `
+ -UseWebhook `
+ -WebhookPassword $pw `
+ -FetchSites `
+ -SaveSitesList:$SaveSitesList `
+ -OutputFile $OutputFile
- # 2) log & write back a simple text response
- Write-LogHybrid "CyberQP installed" "Success" "OnBoard"
- Respond-Text $Context "CyberQP installed"
- }
+ 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-InstallThreatLocker {
- param($Context)
+ function Handle-SetSVSPowerPlan {
+ param($Context)
+ Set-SVSPowerPlan
+ Write-LogHybrid "PowerPlan set" Success OnBoard
+ Respond-Text $Context "PowerPlan applied"
+ }
- # 1) call into your module
- Install-ThreatLocker
+ 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: $_"
+ }
+ }
- # 2) log & write back a simple text response
- Write-LogHybrid "ThreatLocker installed" "Success" "OnBoard"
- Respond-Text $Context "ThreatLocker installed"
- }
+ function Handle-InstallCyberQP {
+ param($Context)
+ Install-CyberQP
+ Write-LogHybrid "CyberQP installed" Success OnBoard
+ Respond-Text $Context "CyberQP installed"
+ }
- function Handle-InstallRocketCyber {
- param($Context)
+ function Handle-InstallThreatLocker {
+ param($Context)
+ Install-ThreatLocker
+ Write-LogHybrid "ThreatLocker installed" Success OnBoard
+ Respond-Text $Context "ThreatLocker installed"
+ }
- # 1) call into your module
- Install-RocketCyber
+ function Handle-InstallRocketCyber {
+ param($Context)
+ Install-RocketCyber
+ Write-LogHybrid "RocketCyber installed" Success OnBoard
+ Respond-Text $Context "RocketCyber installed"
+ }
- # 2) log & write back a simple text response
- 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"
+ }
- function Handle-InstallSVSHelpDesk {
- param($Context)
+ function Handle-InstallDattoRMM {
+ param($Context)
- # 1) call into your module
- Install-SVSHelpDesk
+ try {
+ if ($Context.Request.HttpMethod -ne 'POST') {
+ $Context.Response.StatusCode = 405
+ Respond-Text $Context 'Use POST'
+ return
+ }
- # 2) log & write back a simple text response
- Write-LogHybrid "SVS HelpDesk installed" "Success" "OnBoard"
- Respond-Text $Context "SVS HelpDesk installed"
- }
+ $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')
-function Handle-InstallDattoRMM {
- param($Context)
+ 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."
+ }
+ }
- try {
- if ($Context.Request.HttpMethod -ne 'POST') {
- $Context.Response.StatusCode = 405
- Respond-Text $Context 'Use POST'
+ 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-UninstallCyberQP {
+ param($Context)
+
+ try {
+ if (Get-Command Uninstall-CyberQP -ErrorAction Stop) {
+ Uninstall-CyberQP
+ Write-LogHybrid "CyberQP uninstalled" Success OffBoard -LogToEvent
+ Respond-Text $Context "CyberQP uninstalled."
+ } else {
+ throw "Uninstall-CyberQP cmdlet not found in SVSMSP toolkit."
+ }
+ }
+ catch {
+ Write-LogHybrid "Error uninstalling CyberQP: $($_.Exception.Message)" Error OffBoard -LogToEvent
+ Respond-Text $Context "ERROR: $($_.Exception.Message)"
+ }
+ }
+
+ function Handle-UninstallSVSHelpDesk {
+ param($Context)
+
+ try {
+ if (Get-Command Uninstall-SVSHelpDesk -ErrorAction Stop) {
+ Uninstall-SVSHelpDesk
+ Write-LogHybrid "SVS HelpDesk uninstalled" Success OffBoard -LogToEvent
+ Respond-Text $Context "SVS HelpDesk uninstalled."
+ } else {
+ throw "Uninstall-SVSHelpDesk cmdlet not found in SVSMSP toolkit."
+ }
+ }
+ catch {
+ Write-LogHybrid "Error uninstalling SVS HelpDesk: $($_.Exception.Message)" Error OffBoard -LogToEvent
+ Respond-Text $Context "ERROR: $($_.Exception.Message)"
+ }
+ }
+
+ function Handle-UninstallThreatLocker {
+ param($Context)
+
+ try {
+ if (Get-Command Uninstall-ThreatLocker -ErrorAction Stop) {
+ Uninstall-ThreatLocker
+ Write-LogHybrid "ThreatLocker uninstalled" Success OffBoard -LogToEvent
+ Respond-Text $Context "ThreatLocker uninstalled."
+ } else {
+ throw "Uninstall-ThreatLocker cmdlet not found in SVSMSP toolkit."
+ }
+ }
+ catch {
+ Write-LogHybrid "Error uninstalling ThreatLocker: $($_.Exception.Message)" Error OffBoard -LogToEvent
+ Respond-Text $Context "ERROR: $($_.Exception.Message)"
+ }
+ }
+
+ function Handle-UninstallRocketCyber {
+ param($Context)
+
+ try {
+ if (Get-Command Uninstall-RocketCyber -ErrorAction Stop) {
+ Uninstall-RocketCyber
+ Write-LogHybrid "RocketCyber uninstalled" Success OffBoard -LogToEvent
+ Respond-Text $Context "RocketCyber uninstalled."
+ } else {
+ throw "Uninstall-RocketCyber cmdlet not found in SVSMSP toolkit."
+ }
+ }
+ catch {
+ Write-LogHybrid "Error uninstalling RocketCyber: $($_.Exception.Message)" Error OffBoard -LogToEvent
+ Respond-Text $Context "ERROR: $($_.Exception.Message)"
+ }
+ }
+
+ function Handle-CleanupSVSMSP {
+ param($Context)
+
+ try {
+ if (Get-Command Install-SVSMSP -ErrorAction Stop) {
+ Install-SVSMSP -Cleanup
+ Write-LogHybrid "SVSMSP toolkit cleanup complete (module, repo, registry)." Success OffBoard -LogToEvent
+ Respond-Text $Context "SVSMSP toolkit cleanup complete."
+ } else {
+ throw "Install-SVSMSP function not found in current session."
+ }
+ }
+ catch {
+ Write-LogHybrid "Error cleaning up SVSMSP toolkit: $($_.Exception.Message)" Error OffBoard -LogToEvent
+ Respond-Text $Context "ERROR: $($_.Exception.Message)"
+ }
+ }
+
+ # =====================================================================
+ # 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
}
- # 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 -LogToEvent
- $Context.Response.StatusCode = 500
- Respond-Text $Context "Internal server error during DattoRMM install."
- }
-}
-
-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)"
- }
-}
-
- #Offboarding Handlers
- function Handle-UninstallCyberQP {
- param($Context)
-
- try {
- if (Get-Command Uninstall-CyberQP -ErrorAction Stop) {
- Uninstall-CyberQP
- Write-LogHybrid "CyberQP uninstalled" Success OffBoard -LogToEvent
- Respond-Text $Context "CyberQP uninstalled."
- } else {
- throw "Uninstall-CyberQP cmdlet not found in SVSMSP toolkit."
- }
- }
- catch {
- Write-LogHybrid "Error uninstalling CyberQP: $($_.Exception.Message)" Error OffBoard -LogToEvent
- Respond-Text $Context "ERROR: $($_.Exception.Message)"
- }
-}
-
-function Handle-UninstallSVSHelpDesk {
- param($Context)
-
- try {
- if (Get-Command Uninstall-SVSHelpDesk -ErrorAction Stop) {
- Uninstall-SVSHelpDesk
- Write-LogHybrid "SVS HelpDesk uninstalled" Success OffBoard -LogToEvent
- Respond-Text $Context "SVS HelpDesk uninstalled."
- } else {
- throw "Uninstall-SVSHelpDesk cmdlet not found in SVSMSP toolkit."
- }
- }
- catch {
- Write-LogHybrid "Error uninstalling SVS HelpDesk: $($_.Exception.Message)" Error OffBoard -LogToEvent
- Respond-Text $Context "ERROR: $($_.Exception.Message)"
- }
-}
-
-function Handle-UninstallThreatLocker {
- param($Context)
-
- try {
- if (Get-Command Uninstall-ThreatLocker -ErrorAction Stop) {
- Uninstall-ThreatLocker
- Write-LogHybrid "ThreatLocker uninstalled" Success OffBoard -LogToEvent
- Respond-Text $Context "ThreatLocker uninstalled."
- } else {
- throw "Uninstall-ThreatLocker cmdlet not found in SVSMSP toolkit."
- }
- }
- catch {
- Write-LogHybrid "Error uninstalling ThreatLocker: $($_.Exception.Message)" Error OffBoard -LogToEvent
- Respond-Text $Context "ERROR: $($_.Exception.Message)"
- }
-}
-
-function Handle-UninstallRocketCyber {
- param($Context)
-
- try {
- if (Get-Command Uninstall-RocketCyber -ErrorAction Stop) {
- Uninstall-RocketCyber
- Write-LogHybrid "RocketCyber uninstalled" Success OffBoard -LogToEvent
- Respond-Text $Context "RocketCyber uninstalled."
- } else {
- throw "Uninstall-RocketCyber cmdlet not found in SVSMSP toolkit."
- }
- }
- catch {
- Write-LogHybrid "Error uninstalling RocketCyber: $($_.Exception.Message)" Error OffBoard -LogToEvent
- Respond-Text $Context "ERROR: $($_.Exception.Message)"
- }
-}
-
-function Handle-CleanupSVSMSP {
- param($Context)
-
- try {
- if (Get-Command Install-SVSMSP -ErrorAction Stop) {
- # This will:
- # - Uninstall SVSMSP
- # - Unregister SVS_Repo
- # - Remove SVSMSP from the session
- # - Delete HKLM:\Software\SVS\Deployment (via Remove-SVSDeploymentRegKey)
- Install-SVSMSP -Cleanup
-
- Write-LogHybrid "SVSMSP toolkit cleanup complete (module, repo, registry)." Success OffBoard -LogToEvent
- Respond-Text $Context "SVSMSP toolkit cleanup complete."
- } else {
- throw "Install-SVSMSP function not found in current session."
- }
- }
- catch {
- Write-LogHybrid "Error cleaning up SVSMSP toolkit: $($_.Exception.Message)" Error OffBoard -LogToEvent
- Respond-Text $Context "ERROR: $($_.Exception.Message)"
- }
-}
-
-
-
- #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 -LogToEvent; return
- }
-
- # 1) Optionally fetch credentials from webhook
- 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
- }
- }
-
- # 2) Validate API parameters
- if (-not $ApiUrl -or -not $ApiKey -or -not $ApiSecretKey) {
- Write-LogHybrid "Missing required API parameters." Error DattoRMM -LogToEvent; 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 -LogToEvent
- } catch {
- Write-LogHybrid "OAuth token fetch failed: $($_.Exception.Message)" Error DattoRMM -LogToEvent; 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 }
+ if ($UseWebhook) {
+ if (-not $WebhookPassword) {
+ Write-LogHybrid "Webhook password missing." Error DattoRMM -LogToEvent
+ return
}
- 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 @()
- }
- }
-
- # 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 -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
+ $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 write '$($v.name)': $($_.Exception.Message)" Error DattoRMM -LogToEvent
+ Write-LogHybrid "Failed to fetch webhook credentials: $($_.Exception.Message)" Error DattoRMM -LogToEvent
+ return
}
}
- }
- # 6) Download & install RMM agent
- if ($InstallRMM) {
- if ($PSCmdlet.ShouldProcess("Site '$SiteName'", "Install RMM agent")) {
+ 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"
- $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
+ $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 "Agent install failed: $($_.Exception.Message)" Error DattoRMM -LogToEvent
+ Write-LogHybrid "Save-copy failed: $($_.Exception.Message)" Error DattoRMM -LogToEvent
}
}
- }
- # 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 -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
}
}
- # 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 -LogToEvent
+ # =====================================================================
+ # 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:SamyTasks | Where-Object Name -EQ $path
+ if ($task) {
+ & $task.HandlerFn $Context
+ return
+ }
+
+ $Context.Response.StatusCode = 404
+ Respond-Text $Context '404 - Not Found'
}
-}
-
-
- #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" -LogToEvent
- 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:SamyTasks | 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)
- # ─────────────────────────────────────────────────────────────────────────
+ # =====================================================================
+ # MAIN ENTRY LOGIC (PARAMETER SET SWITCH)
+ # =====================================================================
switch ($PSCmdlet.ParameterSetName) {
- 'Toolkit' {
- Write-LogHybrid "Toolkit-only mode" Info Startup -LogToEvent
- Install-SVSMSP -InstallToolkit
- return
- }
+ '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
- }
-
- # ───────────────────────────────────────────────────────────
- # 2) If user only wants the site list, do that and exit
- # ───────────────────────────────────────────────────────────
+ '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
+ '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
- }
-
-
- # ────────────────────────────────────────────
- # 3) Invoke the existing Install-DattoRMM cmdlet
- # ────────────────────────────────────────────
-
- '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' {
- $url = "http://localhost:$Port/"
- Write-LogHybrid "Starting ScriptMonkey UI on $url" Info Startup
-
- # Open the UI in a separate PowerShell job so Start-Server can block safely.
- try {
- Start-Job -Name 'OpenScriptMonkeyUI' -ScriptBlock {
- param($u)
- Start-Sleep -Milliseconds 300
- try {
- if (Get-Command -Name 'msedge.exe' -ErrorAction SilentlyContinue) {
- Start-Process -FilePath 'msedge.exe' -ArgumentList "--app=$u"
- } else {
- Start-Process -FilePath $u
- }
- } catch { }
- } -ArgumentList $url | Out-Null
- } catch {
- Write-LogHybrid "Failed to schedule browser launch: $($_.Exception.Message)" Warning Startup -LogToEvent
}
- # Now start the blocking listener loop
- Start-Server
- return
- }
+ 'DattoInstall' {
+ Write-LogHybrid "Headless DattoRMM deploy" Info DattoAuth -LogToEvent
-#>
-
- 'UI' {
- $url = "http://localhost:$Port/"
- Write-LogHybrid "Starting ScriptMonkey UI on $url" Info Startup
-
- # Resolve Edge path explicitly (x86 first, then 64-bit, then PATH)
- $edgeCandidates = @(
- "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe",
- "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe"
- )
- $edgePath = $edgeCandidates | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1
- if (-not $edgePath) {
- $cmd = Get-Command -Name 'msedge.exe' -ErrorAction SilentlyContinue
- if ($cmd) { $edgePath = $cmd.Path }
- }
-
- # Launch Edge (app mode) in a background job so Start-Server can block
- Start-Job -Name 'OpenScriptMonkeyUI' -ScriptBlock {
- param([string]$u, [string]$edge)
- Start-Sleep -Milliseconds 400
- try {
- if ($edge -and (Test-Path $edge)) {
- Start-Process -FilePath $edge -ArgumentList @('--new-window', "--app=$u")
- } else {
- Start-Process -FilePath $u # fallback to default browser
+ if ($PSCmdlet.ShouldProcess("Datto site '$SiteName'", "Headless install")) {
+ Install-DattoRMM `
+ -UseWebhook `
+ -WebhookPassword $WebhookPassword `
+ -SiteUID $SiteUID `
+ -SiteName $SiteName `
+ -PushSiteVars:$PushSiteVars `
+ -InstallRMM:$InstallRMM `
+ -SaveCopy:$SaveCopy
}
- } catch { }
- } -ArgumentList $url, $edgePath | Out-Null
- # Now start the blocking listener loop
- Start-Server
- return
- }
+ return
+ }
+ 'UI' {
+ $url = "http://localhost:$Port/"
+ Write-LogHybrid "Starting ScriptMonkey UI on $url" Info Startup
+ $edgeCandidates = @(
+ "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe",
+ "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe"
+ )
+ $edgePath = $edgeCandidates | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1
+ if (-not $edgePath) {
+ $cmd = Get-Command -Name 'msedge.exe' -ErrorAction SilentlyContinue
+ if ($cmd) { $edgePath = $cmd.Path }
+ }
+ Start-Job -Name 'OpenScriptMonkeyUI' -ScriptBlock {
+ param([string]$u, [string]$edge)
+ Start-Sleep -Milliseconds 400
+ try {
+ if ($edge -and (Test-Path $edge)) {
+ Start-Process -FilePath $edge -ArgumentList @('--new-window', "--app=$u")
+ } else {
+ Start-Process -FilePath $u
+ }
+ } catch { }
+ } -ArgumentList $url, $edgePath | Out-Null
+
+ Start-Server
+ return
+ }
}
- #endregion EntryPoint: Define Invoke-ScriptMonkey
+}
-
- }
-
- if ($MyInvocation.InvocationName -eq '.') {
- # dot-sourced, don't invoke
-} elseif ($PSCommandPath) {
- # script was saved and run directly
+# =====================================================================
+# ENTRYPOINT WRAPPER
+# =====================================================================
+if ($MyInvocation.InvocationName -eq '.') {
+ # dot-sourced, do not auto-run
+}
+elseif ($PSCommandPath) {
Invoke-ScriptMonkey @PSBoundParameters
-} else {
- # iwr | iex fallback
- if ($args.Count -gt 0) {
- # Convert -Param value -Switch into a hashtable for splatting
+}
+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('-')
+ $key = $args[$i].TrimStart('-')
$next = $args[$i + 1]
if ($next -and ($next -notlike '-*')) {
$namedArgs[$key] = $next
- $i++ # Skip next one, it's the value
+ $i++
} else {
$namedArgs[$key] = $true
}
@@ -1922,5 +1470,3 @@ function Install-DattoRMM {
Invoke-ScriptMonkey
}
}
-
-