Update StackMonkey.ps1

This commit is contained in:
2025-06-29 04:27:09 -04:00
parent e49a435328
commit a3024818aa

View File

@@ -87,6 +87,21 @@
# ─────────────────────────────────────────────────────────────────────────
# 1) ENTRYPOINT + PARAMETER DECLARATION
# ─────────────────────────────────────────────────────────────────────────
function Invoke-ScriptMonkey {
[CmdletBinding(
DefaultParameterSetName='UI',
SupportsShouldProcess=$true,
ConfirmImpact='Medium'
)]
param(
[Parameter(Mandatory,ParameterSetName='Toolkit')] [switch]$SilentInstall,
[Parameter(Mandatory,ParameterSetName='DattoFetch')]
[Parameter(Mandatory,ParameterSetName='DattoInstall')]
[string]$N8nPassword,
)
# — all of your modules, helpers, functions, etc. go here —
[CmdletBinding(
DefaultParameterSetName='UI',
@@ -122,19 +137,19 @@
#region — guarantee NuGet provider is present without prompting
# ─── Top of script ───
Import-Module PackageManagement -Force -ErrorAction SilentlyContinue | Out-Null
Import-Module PowerShellGet -Force -ErrorAction SilentlyContinue | Out-Null
# ─── Top of script ───
Import-Module PackageManagement -Force -ErrorAction SilentlyContinue | Out-Null
Import-Module PowerShellGet -Force -ErrorAction SilentlyContinue | Out-Null
# ─── ensure TLS 1.2 + no prompts ───
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$ProgressPreference = 'SilentlyContinue'
$ConfirmPreference = 'None'
# ─── ensure TLS 1.2 + no prompts ───
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$ProgressPreference = 'SilentlyContinue'
$ConfirmPreference = 'None'
# check if NuGet exists (no output—assigned to $nuget)
$nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue
# check if NuGet exists (no output—assigned to $nuget)
$nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue
if (-not $nuget) {
if (-not $nuget) {
# install it (again, assignment suppresses the table)
Install-PackageProvider `
-Name NuGet `
@@ -146,26 +161,26 @@ if (-not $nuget) {
# re-query just for version info
$found = Get-PackageProvider -Name NuGet -ListAvailable
Write-Host "Installed NuGet provider v$($found.Version)" -ForegroundColor Green
}
else {
}
else {
Write-Host "NuGet provider already present (v$($found.Version))" -ForegroundColor DarkGray
}
}
# now import it silently
Import-PackageProvider -Name NuGet -Force -ErrorAction SilentlyContinue | Out-Null
# now import it silently
Import-PackageProvider -Name NuGet -Force -ErrorAction SilentlyContinue | Out-Null
# ensure trust PSGallery without its own output (so you don't get “untrusted repository” prompt
$gallery = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue
if ($gallery.InstallationPolicy -ne 'Trusted') {
# ensure trust PSGallery without its own output (so you don't get “untrusted repository” prompt
$gallery = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue
if ($gallery.InstallationPolicy -ne 'Trusted') {
Set-PSRepository `
-Name PSGallery `
-InstallationPolicy Trusted `
-ErrorAction SilentlyContinue | Out-Null
Write-Host "PSGallery marked as Trusted" -ForegroundColor Green
}
}
#endregion
#endregion
# ─────────────────────────────────────────────────────────────────────────
@@ -178,8 +193,8 @@ if ($gallery.InstallationPolicy -ne 'Trusted') {
# Configurable endpoints
$Global:DattoWebhookUrl = 'https://automate.svstools.ca/webhook/svsmspkit'
#region Get-DattoApiCredentials
function Get-DattoApiCredentials {
#region Get-DattoApiCredentials
function Get-DattoApiCredentials {
[CmdletBinding()]
param (
[Parameter(Mandatory)][string]$Password
@@ -199,9 +214,9 @@ function Get-DattoApiCredentials {
Write-LogHybrid "Failed to fetch API credentials: $($_.Exception.Message)" Error DattoAuth
return $null
}
}
}
function Get-DattoRmmSites {
function Get-DattoRmmSites {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
@@ -268,15 +283,15 @@ function Get-DattoRmmSites {
catch {
Throw "Failed to fetch sites from API: $_"
}
}
}
# Initialize a global in-memory log cache
if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) {
# Initialize a global in-memory log cache
if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) {
$Global:LogCache = [System.Collections.ArrayList]::new()
}
}
# Core Write-Log function (advanced with event-log support)
# Core Write-Log function (advanced with event-log support)
function Write-LogHelper {
[CmdletBinding()]
param(
@@ -318,12 +333,12 @@ if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.Arr
}
if ($PassThru) { return $entry }
}
}
# ─────────────────────────────────────────────────────────────────────────
# WRITE-LOG HYBRID (single definition, chooses at runtime)
# ─────────────────────────────────────────────────────────────────────────
function Write-LogHybrid {
# ─────────────────────────────────────────────────────────────────────────
# WRITE-LOG HYBRID (single definition, chooses at runtime)
# ─────────────────────────────────────────────────────────────────────────
function Write-LogHybrid {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$Message,
@@ -341,26 +356,26 @@ function Write-LogHybrid {
# fall back to your helper
Write-LogHelper -Message $Message -Level $Level -TaskCategory $TaskCategory -LogToEvent:$LogToEvent
}
}
}
#endregion
#endregion
# STACK = Scripted Tooling for Automated Client Kickoff
# MONKEY = Module-based Onboarding & Next-step Kickoff Engine Yoke
# Conveys the idea of coupling tasks together and keeping them under control.
# STACK = Scripted Tooling for Automated Client Kickoff
# MONKEY = Module-based Onboarding & Next-step Kickoff Engine Yoke
# Conveys the idea of coupling tasks together and keeping them under control.
#region Config & Task Definitions
#region Config & Task Definitions
# Define every task once here:
# Id → checkbox HTML `id`
# Name → URL path (`/Name`)
# Label → user-visible text
# HandlerFn → the PowerShell function to invoke
# Page → which tab/page it appears on
# Define every task once here:
# Id → checkbox HTML `id`
# Name → URL path (`/Name`)
# Label → user-visible text
# HandlerFn → the PowerShell function to invoke
# Page → which tab/page it appears on
$Global:Tasks = @(
$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' },
@@ -390,28 +405,28 @@ $Global:Tasks = @(
# SVS Apps
@{ Id='wingetLastpass'; Name='wingetLastpass'; Label='LastPass Desktop App'; HandlerFn='Install-WingetLastPass'; Page='SVSApps' }
)
)
#endregion
#endregion
# If we got here, it's the UI set—launch browser + listener:
# If we got here, it's the UI set—launch browser + listener:
# ——— UI fallback starts here ———
Write-LogHybrid "Launching UI" Info Startup
#region Handler Stubs
#region Handler Stubs
function Respond-Text {
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 {
function Respond-HTML {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)][object] $Context,
@@ -422,10 +437,10 @@ function Respond-HTML {
$Context.Response.ContentLength64 = $bytes.Length
$Context.Response.OutputStream.Write($bytes, 0, $bytes.Length)
$Context.Response.OutputStream.Close()
}
}
# new helper to return JSON
function Respond-JSON {
# new helper to return JSON
function Respond-JSON {
param($Context, $Object)
$json = $Object | ConvertTo-Json -Depth 5
$bytes = [Text.Encoding]::UTF8.GetBytes($json)
@@ -433,11 +448,11 @@ function Respond-JSON {
$Context.Response.ContentLength64 = $bytes.Length
$Context.Response.OutputStream.Write($bytes,0,$bytes.Length)
$Context.Response.OutputStream.Close()
}
}
#region Install-DattoRMM-Helper
function Install-DattoRMM-Helper {
#region Install-DattoRMM-Helper
function Install-DattoRMM-Helper {
param (
[string]$ApiUrl,
[string]$ApiKey,
@@ -487,12 +502,12 @@ function Install-DattoRMM-Helper {
return
}
}
}
#endregion
}
#endregion
#region SVS Module
#region SVS Module
function Install-SVSMSP {
function Install-SVSMSP {
param (
[switch] $Cleanup,
[switch] $InstallToolkit,
@@ -528,14 +543,14 @@ function Install-SVSMSP {
}
# default if no switch passed:
Perform-ToolkitInstallation
}
}
#endregion
#endregion
# POST /getpw → read JSON body, call helper, return JSON
# POST /getpw → read JSON body, call helper, return JSON
function Handle-FetchSites {
function Handle-FetchSites {
param($Context)
# 1) Read incoming JSON (using block auto-disposes the reader)
@@ -558,7 +573,7 @@ function Handle-FetchSites {
# 2) Fetch your Datto API creds from the webhook
Write-LogHybrid "Calling webhook for Datto credentials…" "Info" "FetchSites"
try {
try {
$creds = Get-DattoApiCredentials -Password $pw
if (-not $creds) {
Write-LogHybrid "Webhook returned no credentials" Error FetchSites
@@ -572,11 +587,11 @@ try {
$Global:ApiSecretKey = $creds.ApiSecretKey
Write-LogHybrid "Fetched and stored API credentials." Success FetchSites
} catch {
} catch {
Write-LogHybrid "Credential-fetch error: $($_.Exception.Message)" Error FetchSites -LogToEvent
returnRespondEmpty $Context 500
return
}
}
# 3) Exchange for a bearer token
@@ -624,12 +639,12 @@ try {
$Context.Response.ContentLength64 = $bytes.Length
$Context.Response.OutputStream.Write($bytes, 0, $bytes.Length)
$Context.Response.OutputStream.Close()
}
}
# Helper function to consistently return an empty JSON array
function returnRespondEmpty {
# Helper function to consistently return an empty JSON array
function returnRespondEmpty {
param(
[Parameter(Mandatory)][object]$Context,
[Parameter(Mandatory)][ValidateRange(100,599)][int]$StatusCode = 500
@@ -645,12 +660,12 @@ function returnRespondEmpty {
# Write and close
$Context.Response.OutputStream.Write($empty, 0, $empty.Length)
$Context.Response.OutputStream.Close()
}
}
# On-boarding handlers
function Handle-SetSVSPowerPlan {
# On-boarding handlers
function Handle-SetSVSPowerPlan {
param($Context)
# 1) call into your module
@@ -659,9 +674,9 @@ function Handle-SetSVSPowerPlan {
# 2) log & write back a simple text response
Write-LogHybrid "PowerPlan set" "Success" "OnBoard"
Respond-Text $Context "PowerPlan applied"
}
}
function Handle-InstallSVSMSP {
function Handle-InstallSVSMSP {
param($Context)
Write-LogHybrid "HTTP trigger: Handle-InstallSVSMSP" "Info" "OnBoard"
try {
@@ -671,9 +686,9 @@ function Handle-InstallSVSMSP {
Write-LogHybrid "Error in Install-SVSMSP: $_" "Error" "OnBoard"
Respond-Text $Context "ERROR: $_"
}
}
}
function Handle-InstallCyberQP {
function Handle-InstallCyberQP {
param($Context)
# 1) call into your module
@@ -682,9 +697,9 @@ function Handle-InstallCyberQP {
# 2) log & write back a simple text response
Write-LogHybrid "CyberQP installed" "Success" "OnBoard"
Respond-Text $Context "CyberQP installed"
}
}
function Handle-InstallThreatLocker {
function Handle-InstallThreatLocker {
param($Context)
# 1) call into your module
@@ -693,9 +708,9 @@ function Handle-InstallThreatLocker {
# 2) log & write back a simple text response
Write-LogHybrid "ThreatLocker installed" "Success" "OnBoard"
Respond-Text $Context "ThreatLocker installed"
}
}
function Handle-InstallRocketCyber {
function Handle-InstallRocketCyber {
param($Context)
# 1) call into your module
@@ -704,9 +719,9 @@ function Handle-InstallRocketCyber {
# 2) log & write back a simple text response
Write-LogHybrid "RocketCyber installed" "Success" "OnBoard"
Respond-Text $Context "RocketCyber installed"
}
}
function Handle-InstallSVSHelpDesk {
function Handle-InstallSVSHelpDesk {
param($Context)
# 1) call into your module
@@ -715,9 +730,9 @@ function Handle-InstallSVSHelpDesk {
# 2) log & write back a simple text response
Write-LogHybrid "SVS HelpDesk installed" "Success" "OnBoard"
Respond-Text $Context "SVS HelpDesk installed"
}
}
function Handle-InstallDattoRMM {
function Handle-InstallDattoRMM {
param($Context)
$req = $Context.Request
$resp = $Context.Response
@@ -761,11 +776,11 @@ function Handle-InstallDattoRMM {
$resp.ContentLength64 = $b.Length
$resp.OutputStream.Write($b,0,$b.Length)
$resp.OutputStream.Close()
}
}
# Off-boarding handlers
function Handle-UninstallCyberQP {
# Off-boarding handlers
function Handle-UninstallCyberQP {
param($Context)
# 1) call into your module
@@ -773,33 +788,33 @@ function Handle-UninstallCyberQP {
Write-LogHybrid "CyberQP uninstalled" "Success" "OffBoard"
Respond-Text $Context "CyberQP uninstalled"
}
}
function Cleanup-SVSMSP {
function Cleanup-SVSMSP {
param($Context)
Write-LogHybrid "SVSMSP cleaned up" "Success" "OffBoard"
Respond-Text $Context "SVSMSP cleaned up"
}
}
# Tweaks handler
function Disable-Animations {
# Tweaks handler
function Disable-Animations {
param($Context)
Write-LogHybrid "Animations disabled" "Success" "Tweaks"
Respond-Text $Context "Animations disabled"
}
}
# SVSApps handler
function Install-WingetLastPass {
# SVSApps handler
function Install-WingetLastPass {
param($Context)
Write-LogHybrid "Winget LastPass installed" "Success" "SVSApps"
Respond-Text $Context "Winget LastPass installed"
}
}
#endregion
#endregion
#region UI Generation
#region UI Generation
function Build-Checkboxes {
function Build-Checkboxes {
param($Page, $Column)
(
@@ -826,37 +841,37 @@ function Build-Checkboxes {
) -join "`n"
$html += @"
<div id='${taskId}OptionsContainer' style='display:none; margin-top:4px;'>
$subHtml
</div>
"@
<div id='${taskId}OptionsContainer' style='display:none; margin-top:4px;'>
$subHtml
</div>
"@
}
$html
}
) -join "`n"
}
}
### Get SVSMSP module version to display in the UI
function Get-ModuleVersionHtml {
### Get SVSMSP module version to display in the UI
function Get-ModuleVersionHtml {
$mod = Get-Module -ListAvailable -Name SVSMSP | Sort-Object Version -Descending | Select-Object -First 1
if ($mod) {
return "<div style='color:#bbb; font-size:0.9em; margin-top:1em;'>Module Version: $($mod.Version)</div>"
}
return "<div style='color:#f66;'>SVSMSP_Module not found</div>"
}
}
function Get-UIHtml {
function Get-UIHtml {
param([string]$Page = 'onboard')
#
# 1) Inline your full original CSS here
#
$style = @'
<style>
$style = @'
<style>
:root {
/* Cool Palette */
--background-color: rgba(18, 18, 18, 1);
@@ -1011,8 +1026,8 @@ $style = @'
.container { flex-direction:column; }
.sidebar { width:100%; }
}
</style>
'@
</style>
'@
$script = @'
<script>
@@ -1267,22 +1282,22 @@ $style = @'
</script>
'@
'@
#
# 3) The HTML skeleton with placeholders
#
$htmlTemplate = @"
<!DOCTYPE html>
<html lang="en">
<head>
$htmlTemplate = @"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Script Monkey</title>
<link rel="icon" href="https://git.svstools.com/syelle/Logo/raw/branch/main/SVS_Favicon.ico">
$style
</head>
<body>
</head>
<body>
<div class="logo-container">
<div class="logo-left">
<img src="https://git.svstools.com/syelle/Logo/raw/branch/main/SVS_logo.svg" alt="SVS Logo">
@@ -1330,32 +1345,32 @@ $htmlTemplate = @"
</div>
</div>
<div id="dattoRmmContainer" style="display:none; margin-bottom:1em;">
<div id="dattoRmmContainer" style="display:none; margin-bottom:1em;">
<label for="dattoDropdown">Select a Datto RMM site:</label>
<select id="dattoDropdown" style="width:100%;">
<option disabled selected>Fetching sites...</option>
</select>
</div>
</div>
<div id="offboardTab" class="tab-content">
<h2>Off-Boarding</h2>
<div class="columns-container">
{{offboardCheckboxes}}
{{offboardCheckboxes}}
</div>
</div>
<div id="tweaksTab" class="tab-content">
<h2>Tweaks</h2>
<div class="columns-container">
{{tweaksCheckboxes}}
{{tweaksCheckboxes}}
</div>
</div>
<div id="SVSAppsTab" class="tab-content">
<h2>SVS APPs</h2>
<div class="columns-container">
{{appsCheckboxes}}
{{appsCheckboxes}}
</div>
</div>
@@ -1367,10 +1382,10 @@ $htmlTemplate = @"
<button class="exit-button" onclick="endSession()">Exit</button>
<button class="run-button" onclick="triggerInstall()">Run Selected</button>
</div>
</body>
</html>
</body>
</html>
"@
"@
#
# 4) Build the checkbox HTML and tasks JS from $Global:Tasks
@@ -1408,26 +1423,26 @@ $htmlTemplate = @"
return $html
}
}
#endregion
#endregion
#region HTTP Listener & Routing
#region HTTP Listener & Routing
# Handle shutdown command
if ($path -eq 'quit') {
# Handle shutdown command
if ($path -eq 'quit') {
Write-LogHybrid "Shutdown requested" "Info" "Server"
Respond-Text $Context "Server shutting down."
# This will break out of the while loop in Start-Server
$Global:Listener.Stop()
return
}
}
# Sends the HTML for a given page or invokes a task handler
function Dispatch-Request {
# Sends the HTML for a given page or invokes a task handler
function Dispatch-Request {
param($Context)
# figure out the path
@@ -1466,11 +1481,11 @@ function Dispatch-Request {
# ---- 404 ----
$Context.Response.StatusCode = 404
Respond-Text $Context '404 - Not Found'
}
}
# Starts the HTTP listener loop
function Start-Server {
# Starts the HTTP listener loop
function Start-Server {
# make it accessible to Dispatch-Request
$Global:Listener = [System.Net.HttpListener]::new()
$Global:Listener.Prefixes.Add("http://localhost:$Port/")
@@ -1491,16 +1506,16 @@ function Start-Server {
$Global:Listener.Close()
Write-LogHybrid "Listener closed." "Info" "Server"
}
}
}
#endregion
#endregion
#region ScriptMonkey run silently Entrypoint
#region ScriptMonkey run silently Entrypoint
# ────────────────────────────────────────────────────────────────────────
# 3) MAIN LOGIC (Toolkit vs DattoFetch vs DattoInstall vs UI)
# ────────────────────────────────────────────────────────────────────────
#
# 3) MAIN LOGIC (Toolkit vs DattoFetch vs DattoInstall vs UI)
#
switch ($PSCmdlet.ParameterSetName) {
'Toolkit' {
@@ -1561,6 +1576,8 @@ function Start-Server {
}
#endregion ScriptMonkey run silently Entrypoint
Invoke-ScriptMonkey @PSBoundParameters
} # < end of Invoke-ScriptMonkey
Invoke-ScriptMonkey @PSBoundParameters