diff --git a/ScriptMonkey_v2.ps1 b/ScriptMonkey_v2.ps1
deleted file mode 100644
index f3da843..0000000
--- a/ScriptMonkey_v2.ps1
+++ /dev/null
@@ -1,1806 +0,0 @@
-#region changes to be done
-# We could change line 298 and 379 the have it log in "SVS Scripting" instead of "Application" if we can find a way to force create the log
-#endregion changes to be done
-
-<#
-.SYNOPSIS
- ScriptMonkey - MSP client onboarding/offboarding toolkit with a user interface,
- and optional silent install of the SVSMSP toolkit and headless DattoRMM deployment.
-
-.DESCRIPTION
- Install-DattoRMM is a single, unified toolkit for Datto RMM operations. It can be used
- interactively or via HTTP endpoints, and includes built-in validation and error trapping.
-
- Key features:
- - Credential retrieval - securely fetches ApiUrl, ApiKey, and ApiSecretKey from a webhook.
- - OAuth management - automatically acquires and refreshes bearer tokens over TLS.
- - Site list fetching - returns the list of RMM sites; validates OutputFile to .csv or .json.
- - Site list saving - writes fetched site list to the user's Desktop as CSV or JSON.
- - Registry variable push - writes site-specific variables under HKLM:\Software\SVS\Deployment.
- - Agent download & install - downloads the Datto RMM agent installer and launches it.
- - Installer archiving - saves a copy of the downloaded installer to C:\Temp.
- - HTTP endpoints - exposes /getpw and /installDattoRMM handlers, each wrapped in try/catch
- to log errors and return proper HTTP 500 responses on failure.
- - Idempotent & WhatIf support - uses ShouldProcess for safe, testable agent installs.
-
- Throughout, secrets are never written to logs or console, and all operations produce
- clear success/failure messages via Write-LogHybrid.
-
-
-.PARAMETER UseWebhook
- Switch that forces credential retrieval from the webhook at WebhookUrl using WebhookPassword.
- When omitted, you must supply ApiUrl, ApiKey, and ApiSecretKey directly.
-
-.PARAMETER WebhookPassword
- Password to authenticate to the credentials-fetch webhook. Mandatory when -UseWebhook is set.
-
-.PARAMETER WebhookUrl
- URL of the credentials webhook endpoint. Defaults to $Global:DattoWebhookUrl.
-
-.PARAMETER ApiUrl
- Direct Datto RMM API base URL (used if not fetching from webhook).
-
-.PARAMETER ApiKey
- Direct Datto RMM API key (used if not fetching from webhook).
-
-.PARAMETER ApiSecretKey
- Direct Datto RMM secret (used if not fetching from webhook).
-
-.PARAMETER FetchSites
- Switch to fetch the list of RMM sites and skip all install or variable-push actions.
-
-.PARAMETER SaveSitesList
- Switch to save the fetched site list to the desktop as a file named by OutputFile.
- Must be used together with -FetchSites.
-
-.PARAMETER OutputFile
- Name of the file to write the site list to (must end in “.csv” or “.json”).
- Defaults to 'datto_sites.csv'.
-
-.PARAMETER PushSiteVars
- Switch to fetch site-specific variables and write them under HKLM:\Software\SVS\Deployment.
-
-.PARAMETER InstallRMM
- Switch to download and launch the Datto RMM agent installer for the specified site.
-
-.PARAMETER SaveCopy
- Switch to save a copy of the downloaded Datto RMM installer into C:\Temp.
-
-.PARAMETER SiteUID
- The unique identifier of the Datto RMM site. Mandatory when performing install or variable-push.
-
-.PARAMETER SiteName
- The friendly name of the Datto RMM site (used in logging). Mandatory when performing install or variable-push.
-
-.EXAMPLE
-
- & ([ScriptBlock]::Create( (iwr 'https://sm.svstools.com' -UseBasicParsing).Content )) `
- -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 )) `
- -ApiUrl 'https://api.example.com' `
- -ApiKey 'YourApiKey' `
- -ApiSecretKey 'YourSecretKey' `
- -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 )) `
- -UseWebhook `
- -WebhookPassword 'pwd' `
- -FetchSites `
- -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' `
- -ApiKey 'YourApiKey' `
- -ApiSecretKey 'YourSecretKey' `
- -SiteUID 'site-123' `
- -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' `
- -ApiKey 'YourApiKey' `
- -ApiSecretKey 'YourSecretKey' `
- -SiteUID 'site-123' `
- -SiteName 'Acme Corp' `
- -InstallRMM `
- -WhatIf
-
- # Shows what would happen when installing the RMM agent, without making any changes.
-
-.EXAMPLE
- & ([ScriptBlock]::Create((iwr 'sm.svstools.ca').Content )) -SilentInstall
-
-.EXAMPLE
- & ([ScriptBlock]::Create((iwr 'sm.svstools.com').Content)) -Cleanup
-
-#>
-#region Safely bypass Restricted Execution Policy
-# ─── Safely bypass Restricted Execution Policy ───
-if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage' -or
- (Get-ExecutionPolicy) -eq 'Restricted') {
-
- Write-Host "[Info] Relaunching with ExecutionPolicy Bypass..." -ForegroundColor Yellow
-
- if ($PSCommandPath) {
- powershell.exe -NoProfile -ExecutionPolicy Bypass -File "`"$PSCommandPath`""
- } else {
- powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& { iwr 'https://sm.svstools.com' -UseBasicParsing | iex }"
- }
-
- exit
-}
-
-# ─── TLS and silent install defaults ───
-[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
-$ProgressPreference = 'SilentlyContinue'
-$ConfirmPreference = 'None'
-#endregion Safely bypass Restricted Execution Policy
-
- function Invoke-ScriptMonkey {
-
- # ─────────────────────────────────────────────────────────────────────────
- # PARAMETERS + GLOBAL VARIABLES
- # ─────────────────────────────────────────────────────────────────────────
-
- [CmdletBinding(
- DefaultParameterSetName='UI',
- SupportsShouldProcess=$true,
- ConfirmImpact= 'Medium'
- )]
- param(
- # ─────────────────────────────────────────────────────────
- # Toolkit-only mode
- [Parameter(Mandatory,ParameterSetName='Toolkit')][switch]$SilentInstall,
-
- # ─────────────────────────────────────────────────────────
- # remove Toolkit
- [Parameter(Mandatory,ParameterSetName='Cleanup')][switch]$Cleanup,
-
- # ─────────────────────────────────────────────────────────
- # Datto headless mode
-
- # ─── DattoFetch & DattoInstall share the webhook creds ─────────────
- [Parameter(Mandatory,ParameterSetName='DattoFetch')]
- [Parameter(Mandatory,ParameterSetName='DattoInstall')]
- [switch]$UseWebhook,
-
- [Parameter(Mandatory,ParameterSetName='DattoFetch')]
- [Parameter(Mandatory,ParameterSetName='DattoInstall')]
- [string]$WebhookPassword,
-
- [string]$WebhookUrl = $Global:DattoWebhookUrl,
-
- # ─── only DattoFetch uses these ────────────────────────────────────
- [Parameter(ParameterSetName='DattoFetch')][switch]$FetchSites,
- [Parameter(ParameterSetName='DattoFetch')][switch] $SaveSitesList,
- [Parameter(ParameterSetName='DattoFetch')][ValidatePattern('\.csv$|\.json$')][string] $OutputFile = 'datto_sites.csv',
-
- # ─── only DattoInstall uses these ─────────────────────────────────
- [Parameter(Mandatory,ParameterSetName='DattoInstall')][string] $SiteUID,
- [Parameter(Mandatory,ParameterSetName='DattoInstall')][string] $SiteName,
- [Parameter(ParameterSetName='DattoInstall')][switch] $PushSiteVars,
- [Parameter(ParameterSetName='DattoInstall')][switch] $InstallRMM,
- [Parameter(ParameterSetName='DattoInstall')][switch] $SaveCopy
- )
-
- #region global variables
-
- # Listening port for HTTP UI
- $Port = 8082
-
- # Configurable endpoints
- $Global:DattoWebhookUrl = 'https://automate.svstools.ca/webhook/svsmspkit'
-
- # Initialize a global in-memory log cache
-
- if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) {
- $Global:LogCache = [System.Collections.ArrayList]::new()
- }
-
- #endregion global variables
-
- #region SVS Module
-
- function Install-SVSMSP {
- param (
- [switch] $Cleanup,
- [switch] $InstallToolkit,
- [Parameter(Mandatory = $false)][array] $AllModules = @(@{ ModuleName = "SVS_Toolkit" }, @{ ModuleName = "SVSMSP" }),
- [Parameter(Mandatory = $false)][array] $AllRepositories = @(@{ RepoName = "SVS_Repo" }, @{ RepoName = "SVS_Toolkit" }),
- [Parameter(Mandatory = $false)][string] $NewModuleName = "SVSMSP",
- [Parameter(Mandatory = $false)][string] $NewRepositoryName = "SVS_Repo",
- [Parameter(Mandatory = $false)][string] $NewRepositoryURL = "http://proget.svstools.ca:8083/nuget/SVS_Repo/"
- )
-
- function Perform-Cleanup {
- Write-LogHybrid "Cleanup mode enabled. Starting cleanup..." "Info" "SVSModule"
-
- # 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
- }
- 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
- }
- else {
- 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
- }
- catch {
- 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
- }
- catch {
- Write-LogHybrid "Failed to remove SVSMSP from session: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent
- }
- }
- }
-
-
-
- function Perform-ToolkitInstallation {
- Perform-Cleanup
- Write-LogHybrid "Registering repo $NewRepositoryName…" "Info" "SVSModule" -LogToEvent
- if (-not (Get-PSRepository -Name $NewRepositoryName -ErrorAction SilentlyContinue)) {
- Register-PSRepository -Name $NewRepositoryName -SourceLocation $NewRepositoryURL -InstallationPolicy Trusted
- }
- Write-LogHybrid "Installing module $NewModuleName…" "Info" "SVSModule" -LogToEvent
- Install-Module -Name $NewModuleName -Repository $NewRepositoryName -Scope AllUsers -Force
- Write-LogHybrid "Toolkit installation complete." "Success" "SVSModule" -LogToEvent
- }
-
- Write-LogHybrid "Install-SVSMSP called" "Info" "SVSModule" -LogToEvent
- if ($Cleanup) {
- Perform-Cleanup; return
- }
- if ($InstallToolkit) {
- Perform-ToolkitInstallation; return
- }
- # default if no switch passed:
- Perform-ToolkitInstallation
- }
-
- #endregion SVS Module
-
-
-
- #region Write-Log
-
- # This function is used as a fallback if the SVSMSP module is not installed
- # This function is used as a fallback if the SVSMSP module is not installed
- function Write-LogHelper {
- [CmdletBinding()]
- param(
- [Parameter(Mandatory)][string]$Message,
- [ValidateSet("Info","Warning","Error","Success","General")]
- [string]$Level = "Info",
- [string]$TaskCategory = "GeneralTask",
- [switch]$LogToEvent,
- [string]$EventSource = "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 )
- # ─────────────────────────────────────────────────────────────────────────
-
- 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:Tasks = @(
- # On-Boarding, left column
- @{ Id='setSVSPowerplan'; Name='setSVSPowerplan'; Label='Set SVS Powerplan'; HandlerFn='Handle-setSVSPowerPlan'; Page='onboard'; Column='left' },
- @{ Id='installSVSMSPModule'; Name='installSVSMSPModule'; Label='Install SVSMSP Module'; HandlerFn='Handle-InstallSVSMSP'; Page='onboard'; Column='left' },
- @{ Id='installCyberQP'; Name='installCyberQP'; Label='Install CyberQP'; HandlerFn='Handle-InstallCyberQP'; Page='onboard'; Column='left' },
- @{ Id='installSVSHelpDesk'; Name='installSVSHelpDesk'; Label='Install SVS HelpDesk'; HandlerFn='Handle-InstallSVSHelpDesk'; Page='onboard'; Column='left' },
- @{ Id='installThreatLocker'; Name='installThreatLocker'; Label='Install ThreatLocker'; HandlerFn='Handle-InstallThreatLocker'; Page='onboard'; Column='left' },
- @{ Id='installRocketCyber'; Name='installRocketCyber'; Label='Install RocketCyber'; HandlerFn='Handle-InstallRocketCyber'; Page='onboard'; Column='left' },
- @{ Id='installDattoRMM'; Name='installDattoRMM'; Label='Install DattoRMM'; HandlerFn='Handle-InstallDattoRMM'; Page='onboard'; Column='left';
- SubOptions= @(
- @{ Value='inputVar'; Label='Copy Site Variables' },
- @{ Value='rmm'; Label='Install RMM Agent' },
- @{ Value='exe'; Label='Download Executable' }
- )
- },
-
-
- # On-Boarding, right column (optional bits)
- @{ Id='enableBitLocker'; Name='EnableBitLocker'; Label='Enable BitLocker'; HandlerFn='Set-SVSBitLocker'; Page='onboard'; Column='right' },
- @{ Id='setEdgeDefaultSearch';Name='setedgedefaultsearch';Label='Set Edge Default Search'; Tooltip='Will configure Edge to use Google as default search provider'; HandlerFn='set-EdgeDefaultSearchProvider';Page='onboard'; Column='right' },
-
- # Off-Boarding
- @{ Id='uninstallCyberQP'; Name='uninstallCyberQP'; Label='Uninstall CyberQP'; HandlerFn='Uninstall-CyberQP'; Page='offboard' },
- @{ Id='uninstallSVSMSPModule'; Name='uninstallSVSMSPModule'; Label='Uninstall SVSMSP Module'; HandlerFn='Cleanup-SVSMSP'; Page='offboard' },
-
- # Tweaks
- @{ Id='disableAnimations'; Name='disableAnimations'; Label='Disable Animations'; HandlerFn='Disable-Animations'; Page='tweaks' },
-
- # SVS Apps
- @{ Id='wingetLastpass'; Name='wingetLastpass'; Label='LastPass Desktop App'; HandlerFn='Install-WingetLastPass'; Page='SVSApps' }
- )
-
- #endregion building the Menus
-
- #region Build-Checkboxes
- function Build-Checkboxes {
- param($Page, $Column)
-
- (
- $Global:Tasks |
- Where-Object Page -EQ $Page |
- Where-Object Column -EQ $Column |
- ForEach-Object {
- $taskId = $_.Id
- $tooltip = if ($_.PSObject.Properties.Name -contains 'Tooltip' -and $_.Tooltip) {
- " title='$($_.Tooltip)'"
- } else { '' }
-
- $html = ""
-
- if ($_.SubOptions) {
- # join inside the code block is fine
- $subHtml = (
- $_.SubOptions |
- ForEach-Object {
- ""
- }
- ) -join "`n"
-
- $html += @"
-
-$subHtml
-
-"@
- }
-
- $html
- }
- ) -join "`n"
- }
-
- #endregion Build-Checkboxes
-
- #region Get-ModuleVersionHtml
-
- ### Get SVSMSP module version to display in the UI
- function Get-ModuleVersionHtml {
- $mod = Get-Module -ListAvailable -Name SVSMSP | Sort-Object Version -Descending | Select-Object -First 1
- if ($mod) {
- return "Module Version: $($mod.Version)
"
- }
- return "SVSMSP_Module not found
"
- }
-
- #endregion Get-ModuleVersionHtml
-
- #region Strat-Server
- # Starts the HTTP listener loop
- function Start-Server {
- # make it accessible to Dispatch-Request
- $Global:Listener = [System.Net.HttpListener]::new()
- $Global:Listener.Prefixes.Add("http://localhost:$Port/")
- $Global:Listener.Start()
- Write-LogHybrid "Listening on http://localhost:$Port/" Info Server
-
-
- try {
- while ($Global:Listener.IsListening) {
- $ctx = $Global:Listener.GetContext()
- try {
- Dispatch-Request $ctx
- } catch {
- Write-LogHybrid "Dispatch error: $_" "Error" "Server" -LogToEvent
- }
- }
- } finally {
- # once the loop exits, clean up
- $Global:Listener.Close()
- Write-LogHybrid "Listener closed." "Info" "Server" -LogToEvent
- }
- }
- #endregion Strat-Server
-
- #region UIHtml
-function Get-UIHtml {
- param([string]$Page = 'onboard')
-
-# no spaces before $style
-$style = @'
-
-'@
-
-# no spaces before $script
-$script = @'
-
-
-'@
-
-# no spaces before $htmlTemplate
-$htmlTemplate = @"
-
-
-
-
-
-Script Monkey
-
-$style
-
-
-
-
-
-

- {{moduleVersion}}
-
-
-
-
-

-
Script Automation Monkey (Yeah!)
-
-
-
-
-
-
-
-
-
-
On-Boarding
-
This new deployment method ensures everything is successfully deployed with greater ease!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Off-Boarding
-
-{{offboardCheckboxes}}
-
-
-
-
-
Tweaks
-
-{{tweaksCheckboxes}}
-
-
-
-
-
SVS APPs
-
-{{appsCheckboxes}}
-
-
-
-
-
-$script
-
-
-
-
-
-
-
-
-
-
-"@
-
- #
- # 4) Build the checkbox HTML and tasks JS from $Global:Tasks
- #
-
- # On-boarding now has two columns:
- $onboardLeft = Build-Checkboxes -Page 'onboard' -Column 'left'
- $onboardRight = Build-Checkboxes -Page 'onboard' -Column 'right'
-
- # Off-boarding, Tweaks, SVSApps stay one-column:
- $offboard = Build-Checkboxes -Page 'offboard' -Column ''
- $tweaks = Build-Checkboxes -Page 'tweaks' -Column ''
- $apps = Build-Checkboxes -Page 'SVSApps' -Column ''
-
- # Tasks JS array (fixed)
- $tasksJsAll = (
- $Global:Tasks | ForEach-Object {
- " { id: '$($_.Id)', handler: '/$($_.Name)', label: '$($_.Label)' }"
- }
- ) -join ",`n"
-
-
- #
- # 5) Inject into template
- #
- $html = $htmlTemplate
- $html = $html.Replace('{{moduleVersion}}', (Get-ModuleVersionHtml))
- $html = $html.Replace('{{onboardLeftColumn}}', $onboardLeft)
- $html = $html.Replace('{{onboardRightColumn}}', $onboardRight)
- $html = $html.Replace('{{offboardCheckboxes}}', $offboard)
- $html = $html.Replace('{{tweaksCheckboxes}}', $tweaks)
- $html = $html.Replace('{{appsCheckboxes}}', $apps)
- $html = $html.Replace('{{tasksJsAll}}', $tasksJsAll)
- $html = $html.Replace('{{defaultPage}}', $Page)
-
-
- return $html
- }
-
- #endregion UIHtml
-
- #region Handler Stubs
-
- function Respond-Text {
- param($Context, $Text)
- $bytes = [Text.Encoding]::UTF8.GetBytes($Text)
- $Context.Response.ContentType = 'text/plain'
- $Context.Response.ContentLength64 = $bytes.Length
- $Context.Response.OutputStream.Write($bytes,0,$bytes.Length)
- $Context.Response.OutputStream.Close()
- }
-
- function Respond-HTML {
- [CmdletBinding()]
- param(
- [Parameter(Mandatory = $true)][object] $Context,
- [Parameter(Mandatory = $true)][string] $Html
- )
- $bytes = [Text.Encoding]::UTF8.GetBytes($Html)
- $Context.Response.ContentType = 'text/html'
- $Context.Response.ContentLength64 = $bytes.Length
- $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length)
- $Context.Response.OutputStream.Close()
- }
-
- 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
- }
- 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."
- }
- }
-
- # On-boarding handlers
- function Handle-SetSVSPowerPlan {
- param($Context)
-
- # 1) call into your module
- Set-SVSPowerPlan
-
- # 2) log & write back a simple text response
- Write-LogHybrid "PowerPlan set" "Success" "OnBoard"
- Respond-Text $Context "PowerPlan applied"
- }
-
- function Handle-InstallSVSMSP {
- param($Context)
- Write-LogHybrid "HTTP trigger: Handle-InstallSVSMSP" "Info" "OnBoard"
- try {
- Install-SVSMSP -InstallToolkit
- Respond-Text $Context "SVSMSP Module installed/updated."
- } catch {
- Write-LogHybrid "Error in Install-SVSMSP: $_" "Error" "OnBoard"
- Respond-Text $Context "ERROR: $_"
- }
- }
-
- function Handle-InstallCyberQP {
- param($Context)
-
- # 1) call into your module
- Install-CyberQP
-
- # 2) log & write back a simple text response
- Write-LogHybrid "CyberQP installed" "Success" "OnBoard"
- Respond-Text $Context "CyberQP installed"
- }
-
- function Handle-InstallThreatLocker {
- param($Context)
-
- # 1) call into your module
- Install-ThreatLocker
-
- # 2) log & write back a simple text response
- Write-LogHybrid "ThreatLocker installed" "Success" "OnBoard"
- Respond-Text $Context "ThreatLocker installed"
- }
-
- function Handle-InstallRocketCyber {
- param($Context)
-
- # 1) call into your module
- Install-RocketCyber
-
- # 2) log & write back a simple text response
- Write-LogHybrid "RocketCyber installed" "Success" "OnBoard"
- Respond-Text $Context "RocketCyber installed"
- }
-
- function Handle-InstallSVSHelpDesk {
- param($Context)
-
- # 1) call into your module
- Install-SVSHelpDesk
-
- # 2) log & write back a simple text response
- Write-LogHybrid "SVS HelpDesk installed" "Success" "OnBoard"
- Respond-Text $Context "SVS HelpDesk installed"
- }
-
-
-function Handle-InstallDattoRMM {
- param($Context)
-
- try {
- if ($Context.Request.HttpMethod -ne 'POST') {
- $Context.Response.StatusCode = 405
- Respond-Text $Context 'Use POST'
- return
- }
-
- # 1) Read and parse the JSON body
- $body = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd()
- $data = ConvertFrom-Json $body
-
- # 2) Delegate to your unified function for the install
- Install-DattoRMM `
- -UseWebhook `
- -WebhookPassword $Global:WebhookPassword `
- -SiteUID $data.UID `
- -SiteName $data.Name `
- -PushSiteVars:($data.checkedValues -contains 'inputVar') `
- -InstallRMM: ($data.checkedValues -contains 'rmm') `
- -SaveCopy: ($data.checkedValues -contains 'exe')
-
- # 3) Acknowledge to the client
- Respond-Text $Context "Triggered DattoRMM for $($data.Name)"
- }
- catch {
- # Log the exception and return HTTP 500
- Write-LogHybrid "Handle-InstallDattoRMM error: $($_.Exception.Message)" Error DattoRMM -LogToEvent
- $Context.Response.StatusCode = 500
- Respond-Text $Context "Internal server error during DattoRMM install."
- }
-}
- #endregion Handler Stubs
-
- #region Install-DattoRMM
-
- <#
-.SYNOPSIS
- Installs/configures the Datto RMM agent, fetches site lists, and optionally saves the site list to disk.
-
-.DESCRIPTION
- Centralizes Datto RMM operations in one function:
- - Fetch API credentials from a webhook (-UseWebhook)
- - Acquire OAuth token
- - Fetch site list (-FetchSites)
- - Save site list to Desktop as JSON or CSV (-FetchSites + -SaveSitesList)
- - Write site variables to registry (-PushSiteVars)
- - Download & launch the RMM agent installer (-InstallRMM)
- - Save a copy of the installer (-SaveCopy)
-
-.PARAMETER UseWebhook
- Fetches ApiUrl, ApiKey, and ApiSecretKey from the webhook when used with WebhookPassword.
-
-.PARAMETER WebhookPassword
- Password for authenticating to the credentials webhook.
-
-.PARAMETER WebhookUrl
- URL of the credentials webhook. Defaults to $Global:DattoWebhookUrl.
-
-.PARAMETER ApiUrl
- Direct Datto API endpoint URL (if not using webhook).
-
-.PARAMETER ApiKey
- Direct Datto API key (if not using webhook).
-
-.PARAMETER ApiSecretKey
- Direct Datto API secret (if not using webhook).
-
-.PARAMETER FetchSites
- Fetches the list of sites and skips all install steps.
-
-.PARAMETER SaveSitesList
- Saves the fetched site list to Desktop using OutputFile. Must be used with -FetchSites.
-
-.PARAMETER OutputFile
- Filename for saving the site list (.json or .csv). Defaults to 'datto_sites.csv'.
-
-.PARAMETER PushSiteVars
- Writes fetched site variables into HKLM:\Software\SVS\Deployment.
-
-.PARAMETER InstallRMM
- Downloads and runs the Datto RMM agent installer.
-
-.PARAMETER SaveCopy
- Saves a copy of the downloaded agent installer to C:\Temp.
-
-.PARAMETER SiteUID
- Unique identifier of the Datto site (required for install and registry push).
-
-.PARAMETER SiteName
- Friendly name of the Datto site (used for logging).
-
-.EXAMPLE
- # Fetch and save site list via webhook
- Install-DattoRMM -UseWebhook -WebhookPassword 'Tndmeeisdwge!' -FetchSites -SaveSitesList -OutputFile 'sites.csv'
-
-.EXAMPLE
- # Headless install with site variables
- Install-DattoRMM -ApiUrl 'https://api.example.com' -ApiKey 'KeyHere' -ApiSecretKey 'SecretHere' \
- -SiteUID 'site-123' -SiteName 'Acme Corp' -PushSiteVars -InstallRMM
-
-.EXAMPLE
- # Download and save installer to C:\Temp without installing
- Install-DattoRMM -ApiUrl 'https://api.example.com' -ApiKey 'KeyHere' -ApiSecretKey 'SecretHere' \
- -SiteUID 'site-123' -SiteName 'Acme Corp' -SaveCopy
-#>
-function Install-DattoRMM {
- [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
- param (
- [switch]$UseWebhook,
- [string]$WebhookPassword,
- [string]$WebhookUrl = $Global:DattoWebhookUrl,
- [string]$ApiUrl,
- [string]$ApiKey,
- [string]$ApiSecretKey,
- [switch]$FetchSites,
- [switch]$SaveSitesList,
- [string]$OutputFile = 'datto_sites.csv',
- [switch]$PushSiteVars,
- [switch]$InstallRMM,
- [switch]$SaveCopy,
- [string]$SiteUID,
- [string]$SiteName
- )
-
- # Validate mutually-dependent switches
- if ($SaveSitesList -and -not $FetchSites) {
- Write-LogHybrid "-SaveSitesList requires -FetchSites." Error DattoRMM -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 }
- }
- 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
- } catch {
- Write-LogHybrid "Failed to write '$($v.name)': $($_.Exception.Message)" Error DattoRMM -LogToEvent
- }
- }
- }
-
- # 6) Download & install RMM agent
- if ($InstallRMM) {
- if ($PSCmdlet.ShouldProcess("Site '$SiteName'", "Install RMM agent")) {
- try {
- $dlUrl = "https://zinfandel.centrastage.net/csm/profile/downloadAgent/$SiteUID"
- $tmp = "$env:TEMP\AgentInstall.exe"
- Invoke-WebRequest -Uri $dlUrl -OutFile $tmp -UseBasicParsing
- Write-LogHybrid "Downloaded agent to $tmp." Info DattoRMM -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
- }
- }
- }
-
- # 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
- }
- }
-
- # 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
- }
-}
-
-
- #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:Tasks | Where-Object Name -EQ $path
- if ($task) {
- & $task.HandlerFn $Context
- return
- }
-
- # ---- 404 ----
- $Context.Response.StatusCode = 404
- Respond-Text $Context '404 - Not Found'
- }
- #endregion Dispatch-Request
-
- #region EntryPoint: Define Invoke-ScriptMonkey
-
- # ─────────────────────────────────────────────────────────────────────────
- # 3) MAIN LOGIC (Toolkit vs DattoFetch vs DattoInstall vs UI)
- # ─────────────────────────────────────────────────────────────────────────
-
- switch ($PSCmdlet.ParameterSetName) {
- 'Toolkit' {
- Write-LogHybrid "Toolkit-only mode" Info Startup -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
- # ───────────────────────────────────────────────────────────
-
- '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' {
- Write-LogHybrid "Starting ScriptMonkey UI on http://localhost:$Port/" Info Startup
- Start-Process "msedge.exe" -ArgumentList "--app=http://localhost:$Port"
- Start-Server # blocks until you click Exit
- return
- }
-
- }
- #endregion EntryPoint: Define Invoke-ScriptMonkey
-
-
- #region — guarantee NuGet provider is present without prompting
-
- # ─── 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)" Warn 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)" Warn 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)" Warn 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 ───
- try {
- Import-PackageProvider -Name NuGet -Force -ErrorAction Stop | Out-Null
- } catch {
- Write-LogHybrid "NuGet provider import failed: $($_.Exception.Message)" Error Bootstrap -LogToEvent
- }
-
- #endregion guarantee NuGet provider is present without prompting
-
-
- #region HTTP Listener & Routing
-
- # Handle shutdown command
- if ($path -eq 'quit') {
- Write-LogHybrid "Shutdown requested" "Info" "Server"
- Respond-Text $Context "Server shutting down."
- # This will break out of the while loop in Start-Server
- $Global:Listener.Stop()
- return
- }
-
-
- #endregion
-
-
- }
-
- if ($MyInvocation.InvocationName -eq '.') {
- # dot-sourced, don't invoke
-} elseif ($PSCommandPath) {
- # script was saved and run directly
- Invoke-ScriptMonkey @PSBoundParameters
-} else {
- # iwr | iex fallback
- if ($args.Count -gt 0) {
- # Convert -Param value -Switch into a hashtable for splatting
- $namedArgs = @{}
- for ($i = 0; $i -lt $args.Count; $i++) {
- if ($args[$i] -is [string] -and $args[$i].StartsWith('-')) {
- $key = $args[$i].TrimStart('-')
- $next = $args[$i + 1]
- if ($next -and ($next -notlike '-*')) {
- $namedArgs[$key] = $next
- $i++ # Skip next one, it's the value
- } else {
- $namedArgs[$key] = $true
- }
- }
- }
- Invoke-ScriptMonkey @namedArgs
- } else {
- Invoke-ScriptMonkey
- }
-}
-
-