diff --git a/Stackmonkey.ps1 b/Stackmonkey.ps1 deleted file mode 100644 index 79fab72..0000000 --- a/Stackmonkey.ps1 +++ /dev/null @@ -1,1188 +0,0 @@ -#region changes to be done -# We could change line 298 and 379 the have it log in "SVS Scripting" instead of "Application" if we can find a way to force create the log -# winget install --id=Google.Chrome --silent --accept-package-agreements --accept-source-agreements -# winget install --id=Adobe.Acrobat.Reader.64-bit --silent --accept-package-agreements --accept-source-agreements -#endregion changes to be done - -<# -.SYNOPSIS - ScriptMonkey - MSP client onboarding/offboarding toolkit with a user interface, - and optional silent install of the SVSMSP toolkit and headless DattoRMM deployment. - -.DESCRIPTION - Install-DattoRMM is a single, unified toolkit for Datto RMM operations. It can be used - interactively or via HTTP endpoints, and includes built-in validation and error trapping. - - Key features: - - Credential retrieval - securely fetches ApiUrl, ApiKey, and ApiSecretKey from a webhook. - - OAuth management - automatically acquires and refreshes bearer tokens over TLS. - - Site list fetching - returns the list of RMM sites; validates OutputFile to .csv or .json. - - Site list saving - writes fetched site list to the user's Desktop as CSV or JSON. - - Registry variable push - writes site-specific variables under HKLM:\Software\SVS\Deployment. - - Agent download & install - downloads the Datto RMM agent installer and launches it. - - Installer archiving - saves a copy of the downloaded installer to C:\Temp. - - HTTP endpoints - exposes /getpw and /installDattoRMM handlers, each wrapped in try/catch - to log errors and return proper HTTP 500 responses on failure. - - Idempotent & WhatIf support - uses ShouldProcess for safe, testable agent installs. - - Throughout, secrets are never written to logs or console, and all operations produce - clear success/failure messages via Write-LogHybrid. -#> - -#region Safely bypass Restricted Execution Policy -# ─── Safely bypass Restricted Execution Policy ─── -if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage' -or - (Get-ExecutionPolicy) -eq 'Restricted') { - - Write-Host "[Info] Relaunching with ExecutionPolicy Bypass..." -ForegroundColor Yellow - - if ($PSCommandPath) { - powershell.exe -NoProfile -ExecutionPolicy Bypass -File "`"$PSCommandPath`"" - } else { - powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& { iwr 'https://sm.svstools.com' -UseBasicParsing | iex }" - } - - exit -} - -# ─── TLS and silent install defaults ─── -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$ProgressPreference = 'SilentlyContinue' -$ConfirmPreference = 'None' -#endregion Safely bypass Restricted Execution Policy - -function Invoke-ScriptMonkey { - # ───────────────────────────────────────────────────────────────────────── - # PARAMETERS + GLOBAL VARIABLES - # ───────────────────────────────────────────────────────────────────────── - - [CmdletBinding( - DefaultParameterSetName='UI', - SupportsShouldProcess=$true, - ConfirmImpact= 'Medium' - )] - param( - # ───────────────────────────────────────────────────────── - # Toolkit-only mode - [Parameter(Mandatory,ParameterSetName='Toolkit')][switch]$SilentInstall, - - # ───────────────────────────────────────────────────────── - # remove Toolkit - [Parameter(Mandatory,ParameterSetName='Cleanup')][switch]$Cleanup, - - # ───────────────────────────────────────────────────────── - # Datto headless mode - - # ─── DattoFetch & DattoInstall share the webhook creds ───────────── - [Parameter(Mandatory,ParameterSetName='DattoFetch')] - [Parameter(Mandatory,ParameterSetName='DattoInstall')] - [switch]$UseWebhook, - - [Parameter(Mandatory,ParameterSetName='DattoFetch')] - [Parameter(Mandatory,ParameterSetName='DattoInstall')] - [string]$WebhookPassword, - - [string]$WebhookUrl = $Global:DattoWebhookUrl, - - # ─── only DattoFetch uses these ──────────────────────────────────── - [Parameter(ParameterSetName='DattoFetch')][switch]$FetchSites, - [Parameter(ParameterSetName='DattoFetch')][switch] $SaveSitesList, - [Parameter(ParameterSetName='DattoFetch')][ValidatePattern('\.csv$|\.json$')][string] $OutputFile = 'datto_sites.csv', - - # ─── only DattoInstall uses these ───────────────────────────────── - [Parameter(Mandatory,ParameterSetName='DattoInstall')][string] $SiteUID, - [Parameter(Mandatory,ParameterSetName='DattoInstall')][string] $SiteName, - [Parameter(ParameterSetName='DattoInstall')][switch] $PushSiteVars, - [Parameter(ParameterSetName='DattoInstall')][switch] $InstallRMM, - [Parameter(ParameterSetName='DattoInstall')][switch] $SaveCopy - ) - - #region global variables - $Port = 8082 - $Global:DattoWebhookUrl = 'https://automate.svstools.ca/webhook/svsmspkit' - if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) { - $Global:LogCache = [System.Collections.ArrayList]::new() - } - #endregion global variables - - #region SVS Module - function Install-SVSMSP { - param ( - [switch] $Cleanup, - [switch] $InstallToolkit, - [Parameter(Mandatory = $false)][array] $AllModules = @(@{ ModuleName = "SVS_Toolkit" }, @{ ModuleName = "SVSMSP" }), - [Parameter(Mandatory = $false)][array] $AllRepositories = @(@{ RepoName = "SVS_Repo" }, @{ RepoName = "SVS_Toolkit" }), - [Parameter(Mandatory = $false)][string] $NewModuleName = "SVSMSP", - [Parameter(Mandatory = $false)][string] $NewRepositoryName = "SVS_Repo", - [Parameter(Mandatory = $false)][string] $NewRepositoryURL = "http://proget.svstools.ca:8083/nuget/SVS_Repo/" - ) - - function Perform-Cleanup { - Write-LogHybrid "Cleanup mode enabled. Starting cleanup..." "Info" "SVSModule" - - try { - Uninstall-Module -Name SVSMSP -AllVersions -Force -ErrorAction Stop - Write-LogHybrid "SVSMSP module uninstalled from system." "Success" "SVSModule" -LogToEvent - } catch { - if ($_.Exception.Message -match 'No match was found') { - Write-LogHybrid "No existing SVSMSP module found to uninstall." "Warning" "SVSModule" -LogToEvent - } else { - Write-LogHybrid "Failed to uninstall SVSMSP: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - - if (Get-PSRepository -Name SVS_Repo -ErrorAction SilentlyContinue) { - try { - Unregister-PSRepository -Name SVS_Repo -ErrorAction Stop - Write-LogHybrid "SVS_Repo repository unregistered." "Success" "SVSModule" -LogToEvent - } catch { - Write-LogHybrid "Failed to unregister SVS_Repo: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - - if (Get-Module -Name SVSMSP) { - try { - Remove-Module SVSMSP -Force -ErrorAction Stop - Write-LogHybrid "SVSMSP module removed from current session." "Success" "SVSModule" -LogToEvent - } catch { - Write-LogHybrid "Failed to remove SVSMSP from session: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - } - - function Perform-ToolkitInstallation { - Perform-Cleanup - Write-LogHybrid "Registering repo $NewRepositoryName…" "Info" "SVSModule" -LogToEvent - if (-not (Get-PSRepository -Name $NewRepositoryName -ErrorAction SilentlyContinue)) { - Register-PSRepository -Name $NewRepositoryName -SourceLocation $NewRepositoryURL -InstallationPolicy Trusted - } - Write-LogHybrid "Installing module $NewModuleName…" "Info" "SVSModule" -LogToEvent - Install-Module -Name $NewModuleName -Repository $NewRepositoryName -Scope AllUsers -Force - Write-LogHybrid "Toolkit installation complete." "Success" "SVSModule" -LogToEvent - } - - Write-LogHybrid "Install-SVSMSP called" "Info" "SVSModule" -LogToEvent - if ($Cleanup) { Perform-Cleanup; return } - if ($InstallToolkit) { Perform-ToolkitInstallation; return } - Perform-ToolkitInstallation - } - #endregion SVS Module - - #region Write-Log - function Write-LogHelper { - [CmdletBinding()] - param( - [Parameter(Mandatory)][string]$Message, - [ValidateSet("Info","Warning","Error","Success","General")] - [string]$Level = "Info", - [string]$TaskCategory = "GeneralTask", - [switch]$LogToEvent, - [string]$EventSource = "Script Automation Monkey", - [string]$EventLog = "SVS Scripting", # <— default changed from Application - [int] $CustomEventID, - [string]$LogFile, - [switch]$PassThru - ) - - $idMap = @{ Info=1000; Warning=2000; Error=3000; Success=4000; General=1000 } - $colMap = @{ Info="Cyan"; Warning="Yellow"; Error="Red"; Success="Green"; General="White" } - $EventID = if ($PSBoundParameters.CustomEventID) { $CustomEventID } else { $idMap[$Level] } - $color = $colMap[$Level] - $fmt = "[$Level] [$TaskCategory] $Message (Event ID: $EventID)" - - Write-Host $fmt -ForegroundColor $color - - if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) { - $Global:LogCache = [System.Collections.ArrayList]::new() - } - $Global:LogCache.Add([pscustomobject]@{ - Timestamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') - Level = $Level - Message = $fmt - }) | Out-Null - - if ($PSBoundParameters.LogFile) { - try { - "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')) $fmt" | Out-File -FilePath $LogFile -Append -Encoding UTF8 - } catch { - Write-Host "[Warning] File log failed: $($_.Exception.Message)" -ForegroundColor Yellow - } - } - - if ($LogToEvent) { - try { - # Ensure the log & source exist and are mapped together. - $regBase = 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog' - $mappedLog = $null - if ([System.Diagnostics.EventLog]::SourceExists($EventSource)) { - $mappedLog = (Get-ChildItem $regBase -ErrorAction SilentlyContinue | - Where-Object { Test-Path (Join-Path $_.PSPath $EventSource) } | - Select-Object -First 1 - ).PSChildName - } - if (-not $mappedLog) { - New-EventLog -LogName $EventLog -Source $EventSource -ErrorAction Stop - } elseif ($mappedLog -ne $EventLog) { - # Source already exists but mapped to a different log. Create a new source mapped to target log. - $newSource = "$EventSource - $EventLog" - if (-not [System.Diagnostics.EventLog]::SourceExists($newSource)) { - New-EventLog -LogName $EventLog -Source $newSource -ErrorAction Stop - } - $EventSource = $newSource - } - } catch { - Write-Host "[Warning] Could not ensure event log/source ($EventLog / $EventSource): $($_.Exception.Message)" -ForegroundColor Yellow - # continue; we'll still try to write - } - - $entryType = if ($Level -in 'Warning','Error') { $Level } else { 'Information' } - try { - Write-EventLog -LogName $EventLog -Source $EventSource -EntryType $entryType -EventID $EventID -Message $fmt - } catch { - Write-Host "[Warning] EventLog write failed: $($_.Exception.Message)" -ForegroundColor Yellow - } - } - - if ($PassThru) { return $Global:LogCache[-1] } - } - - function Write-LogHybrid { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)][string]$Message, - [ValidateSet("Info","Warning","Error","Success","General")] - [string]$Level = "Info", - [string]$TaskCategory = "GeneralTask", - [switch]$LogToEvent, - [string]$EventSource = "Script Automation Monkey", - [string]$EventLog = "SVS Scripting", # <— default changed from Application - [ValidateSet("Black","DarkGray","Gray","White","Red","Green","Blue","Yellow","Magenta","Cyan")] - [string]$ForegroundColorOverride - ) - - $formatted = "[$Level] [$TaskCategory] $Message" - - if ($PSBoundParameters.ContainsKey('ForegroundColorOverride')) { - Write-Host $formatted -ForegroundColor $ForegroundColorOverride - $invokeParams = @{ - Message = $Message - Level = $Level - TaskCategory = $TaskCategory - LogToEvent = $LogToEvent - EventSource = $EventSource - EventLog = $EventLog - } - if (Get-Command Write-Log -ErrorAction SilentlyContinue) { Write-Log @invokeParams } else { Write-LogHelper @invokeParams } - } else { - if (Get-Command Write-Log -ErrorAction SilentlyContinue) { - Write-Log -Message $Message -Level $Level -TaskCategory $TaskCategory -LogToEvent:$LogToEvent -EventSource $EventSource -EventLog $EventLog - } else { - Write-LogHelper -Message $Message -Level $Level -TaskCategory $TaskCategory -LogToEvent:$LogToEvent -EventSource $EventSource -EventLog $EventLog - } - } - } - #endregion Write-Log - - #region building the Menus - $Global:Tasks = @( - # On-Boarding, left column - @{ Id='setSVSPowerplan'; Name='setSVSPowerplan'; Label='Set SVS Powerplan'; HandlerFn='Handle-setSVSPowerPlan'; Page='onboard'; Column='left' }, - @{ Id='installSVSMSPModule'; Name='installSVSMSPModule'; Label='Install SVSMSP Module'; HandlerFn='Handle-InstallSVSMSP'; Page='onboard'; Column='left' }, - @{ Id='installCyberQP'; Name='installCyberQP'; Label='Install CyberQP'; HandlerFn='Handle-InstallCyberQP'; Page='onboard'; Column='left' }, - @{ Id='installSVSHelpDesk'; Name='installSVSHelpDesk'; Label='Install SVS HelpDesk'; HandlerFn='Handle-InstallSVSHelpDesk'; Page='onboard'; Column='left' }, - @{ Id='installThreatLocker'; Name='installThreatLocker'; Label='Install ThreatLocker'; HandlerFn='Handle-InstallThreatLocker'; Page='onboard'; Column='left' }, - @{ Id='installRocketCyber'; Name='installRocketCyber'; Label='Install RocketCyber'; HandlerFn='Handle-InstallRocketCyber'; Page='onboard'; Column='left' }, - @{ Id='installDattoRMM'; Name='installDattoRMM'; Label='Install DattoRMM'; HandlerFn='Handle-InstallDattoRMM'; Page='onboard'; Column='left'; - SubOptions= @( - @{ Value='inputVar'; Label='Copy Site Variables' }, - @{ Value='rmm'; Label='Install RMM Agent' }, - @{ Value='exe'; Label='Download Executable' } - ) - }, - - # On-Boarding, right column (optional bits) - @{ Id='enableBitLocker'; Name='EnableBitLocker'; Label='Enable BitLocker'; HandlerFn='Set-SVSBitLocker'; Page='onboard'; Column='right' }, - @{ Id='setEdgeDefaultSearch';Name='setedgedefaultsearch';Label='Set Edge Default Search'; Tooltip='Will configure Edge to use Google as default search provider'; HandlerFn='set-EdgeDefaultSearchProvider'; Page='onboard'; Column='right' }, - - # Off-Boarding - @{ Id='uninstallCyberQP'; Name='uninstallCyberQP'; Label='Uninstall CyberQP'; HandlerFn='Uninstall-CyberQP'; Page='offboard' }, - @{ Id='uninstallSVSMSPModule'; Name='uninstallSVSMSPModule'; Label='Uninstall SVSMSP Module'; HandlerFn='Cleanup-SVSMSP'; Page='offboard' }, - - # Tweaks - @{ Id='disableAnimations'; Name='disableAnimations'; Label='Disable Animations'; HandlerFn='Disable-Animations'; Page='tweaks' }, - - # SVS Apps - @{ Id='wingetLastpass'; Name='wingetLastpass'; Label='LastPass Desktop App'; HandlerFn='Install-WingetLastPass'; Page='SVSApps' }, - @{ Id='wingetChrome'; Name='wingetChrome'; Label='Google Chrome'; HandlerFn='Handle-InstallChrome'; Page='SVSApps' }, - @{ Id='wingetAcrobat'; Name='wingetAcrobat'; Label='Adobe Acrobat Reader (64-bit)'; HandlerFn='Handle-InstallAcrobat'; Page='SVSApps' } - ) - #endregion building the Menus - - #region Build-Checkboxes - function Build-Checkboxes { - param($Page, $Column) - - ( - $Global:Tasks | - Where-Object Page -EQ $Page | - Where-Object Column -EQ $Column | - ForEach-Object { - $taskId = $_.Id - $tooltip = if ($_.PSObject.Properties.Name -contains 'Tooltip' -and $_.Tooltip) { " title='$($_.Tooltip)'" } else { '' } - - $html = " $($_.Label)" - - if ($_.SubOptions) { - $subHtml = ( - $_.SubOptions | ForEach-Object { - "" - } - ) -join "`n" - - $html += @" - -"@ - } - $html - } - ) -join "`n" - } - #endregion Build-Checkboxes - - #region Get-ModuleVersionHtml - function Get-ModuleVersionHtml { - $mod = Get-Module -ListAvailable -Name SVSMSP | Sort-Object Version -Descending | Select-Object -First 1 - if ($mod) { return "
Module Version: $($mod.Version)
" } - return "
SVSMSP_Module not found
" - } - #endregion Get-ModuleVersionHtml - - #region Strat-Server - function Get-NextFreePort { - param([int]$Start = $Port) - for ($p = [Math]::Max(1024,$Start); $p -lt 65535; $p++) { - $l = [System.Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, $p) - try { $l.Start(); $l.Stop(); return $p } catch {} - } - throw "No free TCP port available." - } - - # Starts the HTTP listener loop - function Start-Server { - $Global:Listener = [System.Net.HttpListener]::new() - $primaryPrefix = "http://localhost:$Port/" - $wildcardPrefix = "http://+:$Port/" - - try { - $Global:Listener.Prefixes.Add($primaryPrefix) - $Global:Listener.Start() - Write-LogHybrid "Listening on $primaryPrefix" Info Server -LogToEvent - } - catch [System.Net.HttpListenerException] { - if ($_.Exception.ErrorCode -eq 5) { - Write-LogHybrid "Access denied on $primaryPrefix. Attempting URL ACL…" Warning Server -LogToEvent - try { - $user = "$env:USERDOMAIN\$env:USERNAME" - if (-not $user.Trim()) { $user = $env:USERNAME } - Start-Process -FilePath "netsh" -ArgumentList "http add urlacl url=$wildcardPrefix user=`"$user`" listen=yes" -Verb RunAs -WindowStyle Hidden -Wait - $Global:Listener = [System.Net.HttpListener]::new() - $Global:Listener.Prefixes.Add($wildcardPrefix) - $Global:Listener.Start() - Write-LogHybrid "Listening on $wildcardPrefix (URL ACL added for $user)" Success Server -LogToEvent - } catch { - Write-LogHybrid "URL ACL registration failed: $($_.Exception.Message)" Error Server -LogToEvent - return - } - } - elseif ($_.Exception.NativeErrorCode -in 32,183) { - $old = $Port - $Port = Get-NextFreePort -Start ($Port + 1) - $Global:Listener = [System.Net.HttpListener]::new() - $primaryPrefix = "http://localhost:$Port/" - $Global:Listener.Prefixes.Add($primaryPrefix) - $Global:Listener.Start() - Write-LogHybrid "Port $old busy. Listening on $primaryPrefix" Warning Server -LogToEvent - } - else { - Write-LogHybrid "HttpListener start failed: $($_.Exception.Message)" Error Server -LogToEvent - return - } - } - - try { - while ($Global:Listener.IsListening) { - $ctx = $Global:Listener.GetContext() - try { - Dispatch-Request $ctx - } catch { - Write-LogHybrid "Dispatch error: $($_.Exception.Message)" Error Server -LogToEvent - } - } - } - finally { - $Global:Listener.Close() - Write-LogHybrid "Listener closed." Info Server -LogToEvent - } - } -#endregion Strat-Server - - - #region UIHtml - function Get-UIHtml { - param([string]$Page = 'onboard') - -$style = @' - -'@ - -$script = @' - -'@ - -$htmlTemplate = @" - - - - - -Script Monkey - -$style - - -
-
- SVS Logo - {{moduleVersion}} -
-
- SAMY Logo -
Script Automation Monkey (Yeah!)
-
-
-
- -
- -
-
-

On-Boarding

-

This new deployment method ensures everything is successfully deployed with greater ease!

-
-
-

SVSMSP Stack

- - {{onboardLeftColumn}} -
-
-

Optional

- - {{onboardRightColumn}} -
-
- - - - -
- -
-

Off-Boarding

-
- {{offboardCheckboxes}} -
-
- -
-

Tweaks

-
- {{tweaksCheckboxes}} -
-
- -
-

SVS APPs

-
- {{appsCheckboxes}} -
-
-
-
-$script -
- - -
-
- - -"@ - - # Build the checkbox HTML/JS from $Global:Tasks - $onboardLeft = Build-Checkboxes -Page 'onboard' -Column 'left' - $onboardRight = Build-Checkboxes -Page 'onboard' -Column 'right' - $offboard = Build-Checkboxes -Page 'offboard' -Column '' - $tweaks = Build-Checkboxes -Page 'tweaks' -Column '' - $apps = Build-Checkboxes -Page 'SVSApps' -Column '' - - $tasksJsAll = ( - $Global:Tasks | ForEach-Object { - " { id: '$($_.Id)', handler: '/$($_.Name)', label: '$($_.Label)' }" - } - ) -join ",`n" - - $html = $htmlTemplate - $html = $html.Replace('{{moduleVersion}}', (Get-ModuleVersionHtml)) - $html = $html.Replace('{{onboardLeftColumn}}', $onboardLeft) - $html = $html.Replace('{{onboardRightColumn}}', $onboardRight) - $html = $html.Replace('{{offboardCheckboxes}}', $offboard) - $html = $html.Replace('{{tweaksCheckboxes}}', $tweaks) - $html = $html.Replace('{{appsCheckboxes}}', $apps) - $html = $html.Replace('{{tasksJsAll}}', $tasksJsAll) - $html = $html.Replace('{{defaultPage}}', $Page) - - return $html - } - #endregion UIHtml - - #region HTTP helpers + Handlers - function Respond-Text { - param($Context, $Text) - $bytes = [Text.Encoding]::UTF8.GetBytes($Text) - $Context.Response.ContentType = 'text/plain' - $Context.Response.ContentLength64 = $bytes.Length - $Context.Response.OutputStream.Write($bytes,0,$bytes.Length) - $Context.Response.OutputStream.Close() - } - - function Respond-HTML { - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)][object] $Context, - [Parameter(Mandatory = $true)][string] $Html - ) - $bytes = [Text.Encoding]::UTF8.GetBytes($Html) - $Context.Response.ContentType = 'text/html' - $Context.Response.ContentLength64 = $bytes.Length - $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length) - $Context.Response.OutputStream.Close() - } - - function Respond-JSON { - param($Context, $Object) - $json = $Object | ConvertTo-Json -Depth 5 - $bytes = [Text.Encoding]::UTF8.GetBytes($json) - $Context.Response.ContentType = 'application/json' - $Context.Response.ContentLength64 = $bytes.Length - $Context.Response.OutputStream.Write($bytes,0,$bytes.Length) - $Context.Response.OutputStream.Close() - } - - function Handle-FetchSites { - param($Context) - try { - $raw = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd() - $pw = (ConvertFrom-Json $raw).password - $Global:WebhookPassword = $pw - $sites = Install-DattoRMM -UseWebhook -WebhookPassword $pw -FetchSites - Respond-JSON $Context $sites - } catch { - Write-LogHybrid "Handle-FetchSites error: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent - $Context.Response.StatusCode = 500 - Respond-Text $Context "Internal server error fetching sites." - } - } - - function Handle-SetSVSPowerPlan { param($Context) - Set-SVSPowerPlan - Write-LogHybrid "PowerPlan set" "Success" "OnBoard" - Respond-Text $Context "PowerPlan applied" - } - - function Handle-InstallSVSMSP { - param($Context) - Write-LogHybrid "HTTP trigger: Handle-InstallSVSMSP" "Info" "OnBoard" - try { - Install-SVSMSP -InstallToolkit - Respond-Text $Context "SVSMSP Module installed/updated." - } catch { - Write-LogHybrid "Error in Install-SVSMSP: $($_.Exception.Message)" "Error" "OnBoard" - Respond-Text $Context "ERROR: $($_.Exception.Message)" - } - } - - function Handle-InstallCyberQP { param($Context) - Install-CyberQP - Write-LogHybrid "CyberQP installed" "Success" "OnBoard" - Respond-Text $Context "CyberQP installed" - } - - function Handle-InstallThreatLocker { param($Context) - Install-ThreatLocker - Write-LogHybrid "ThreatLocker installed" "Success" "OnBoard" - Respond-Text $Context "ThreatLocker installed" - } - - function Handle-InstallRocketCyber { param($Context) - Install-RocketCyber - Write-LogHybrid "RocketCyber installed" "Success" "OnBoard" - Respond-Text $Context "RocketCyber installed" - } - - function Handle-InstallSVSHelpDesk { param($Context) - Install-SVSHelpDesk - Write-LogHybrid "SVS HelpDesk installed" "Success" "OnBoard" - Respond-Text $Context "SVS HelpDesk installed" - } - - # NEW: Winget app handlers - function Handle-InstallChrome { param($Context) - try { - winget install --id=Google.Chrome --silent --accept-package-agreements --accept-source-agreements - Write-LogHybrid "Installed Google Chrome via winget" "Success" "SVSApps" -LogToEvent - Respond-Text $Context "Chrome installed" - } catch { - Write-LogHybrid "Chrome install failed: $($_.Exception.Message)" "Error" "SVSApps" -LogToEvent - Respond-Text $Context "ERROR: $($_.Exception.Message)" - } - } - - function Handle-InstallAcrobat { param($Context) - try { - winget install --id=Adobe.Acrobat.Reader.64-bit --silent --accept-package-agreements --accept-source-agreements - Write-LogHybrid "Installed Adobe Acrobat Reader (64-bit) via winget" "Success" "SVSApps" -LogToEvent - Respond-Text $Context "Acrobat Reader installed" - } catch { - Write-LogHybrid "Acrobat install failed: $($_.Exception.Message)" "Error" "SVSApps" -LogToEvent - Respond-Text $Context "ERROR: $($_.Exception.Message)" - } - } - - function Handle-InstallDattoRMM { - param($Context) - try { - if ($Context.Request.HttpMethod -ne 'POST') { - $Context.Response.StatusCode = 405 - Respond-Text $Context 'Use POST' - return - } - $body = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd() - $data = ConvertFrom-Json $body - Install-DattoRMM ` - -UseWebhook ` - -WebhookPassword $Global:WebhookPassword ` - -SiteUID $data.UID ` - -SiteName $data.Name ` - -PushSiteVars:($data.checkedValues -contains 'inputVar') ` - -InstallRMM: ($data.checkedValues -contains 'rmm') ` - -SaveCopy: ($data.checkedValues -contains 'exe') - Respond-Text $Context "Triggered DattoRMM for $($data.Name)" - } catch { - Write-LogHybrid "Handle-InstallDattoRMM error: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent - $Context.Response.StatusCode = 500 - Respond-Text $Context "Internal server error during DattoRMM install." - } - } - #endregion HTTP helpers + Handlers - - #region Install-DattoRMM -function Install-DattoRMM { - [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] - param ( - [switch]$UseWebhook, - [string]$WebhookPassword, - [string]$WebhookUrl = $Global:DattoWebhookUrl, - [string]$ApiUrl, - [string]$ApiKey, - [string]$ApiSecretKey, - [switch]$FetchSites, - [switch]$SaveSitesList, - [string]$OutputFile = 'datto_sites.csv', - [switch]$PushSiteVars, - [switch]$InstallRMM, - [switch]$SaveCopy, - [string]$SiteUID, - [string]$SiteName - ) - - if ($SaveSitesList -and -not $FetchSites) { - Write-LogHybrid "-SaveSitesList requires -FetchSites." "Error" "DattoRMM" -LogToEvent - return - } - - if ($UseWebhook) { - if (-not $WebhookPassword) { - Write-LogHybrid "Webhook password missing." "Error" "DattoRMM" -LogToEvent - return - } - try { - $resp = Invoke-RestMethod -Uri $WebhookUrl -Headers @{ SVSMSPKit = $WebhookPassword } -Method GET - $ApiUrl = $resp.ApiUrl - $ApiKey = $resp.ApiKey - $ApiSecretKey = $resp.ApiSecretKey - Write-LogHybrid "Webhook credentials fetched." "Success" "DattoRMM" -LogToEvent - } catch { - Write-LogHybrid "Failed to fetch webhook credentials: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent - return - } - } - - if (-not $ApiUrl -or -not $ApiKey -or -not $ApiSecretKey) { - Write-LogHybrid "Missing required API parameters." "Error" "DattoRMM" -LogToEvent - return - } - - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - try { - $publicCred = New-Object System.Management.Automation.PSCredential('public-client',(ConvertTo-SecureString 'public' -AsPlainText -Force)) - $tokenResp = Invoke-RestMethod -Uri "$ApiUrl/auth/oauth/token" -Credential $publicCred -Method Post -ContentType 'application/x-www-form-urlencoded' -Body "grant_type=password&username=$ApiKey&password=$ApiSecretKey" - $token = $tokenResp.access_token - Write-LogHybrid "OAuth token acquired." "Success" "DattoRMM" -LogToEvent - } catch { - Write-LogHybrid "OAuth token fetch failed: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent - return - } - $headers = @{ Authorization = "Bearer $token" } - - if ($FetchSites) { - try { - $sitesResp = Invoke-RestMethod -Uri "$ApiUrl/api/v2/account/sites" -Method Get -Headers $headers - $siteList = $sitesResp.sites | ForEach-Object { [PSCustomObject]@{ Name = $_.name; UID = $_.uid } } - Write-LogHybrid "Fetched $($siteList.Count) sites." "Success" "DattoRMM" -LogToEvent - - if ($SaveSitesList) { - $desktop = [Environment]::GetFolderPath('Desktop') - $path = Join-Path $desktop $OutputFile - $ext = [IO.Path]::GetExtension($OutputFile).ToLower() - if ($ext -eq '.json') { - $siteList | ConvertTo-Json -Depth 3 | Out-File -FilePath $path -Encoding UTF8 - } else { - $siteList | Export-Csv -Path $path -NoTypeInformation -Encoding UTF8 - } - Write-LogHybrid "Wrote $($siteList.Count) sites to $path" "Success" "DattoRMM" -LogToEvent - } - - return $siteList - } catch { - Write-LogHybrid "Failed to fetch sites: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent - return @() - } - } - - if ($PushSiteVars) { - try { - $varsResp = Invoke-RestMethod -Uri "$ApiUrl/api/v2/site/$SiteUID/variables" -Method Get -Headers $headers - Write-LogHybrid "Fetched variables for '$SiteName'." "Success" "DattoRMM" -LogToEvent - } catch { - Write-LogHybrid "Variable fetch failed: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent - } - $regPath = "HKLM:\Software\SVS\Deployment" - foreach ($v in $varsResp.variables) { - try { - if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null } - New-ItemProperty -Path $regPath -Name $v.name -Value $v.value -PropertyType String -Force | Out-Null - Write-LogHybrid "Wrote '$($v.name)' to registry." "Success" "DattoRMM" -LogToEvent - } catch { - Write-LogHybrid "Failed to write '$($v.name)': $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent - } - } - } - - if ($InstallRMM) { - if ($PSCmdlet.ShouldProcess("Site '$SiteName'", "Install RMM agent")) { - try { - $dlUrl = "https://zinfandel.centrastage.net/csm/profile/downloadAgent/$SiteUID" - $tmp = "$env:TEMP\AgentInstall.exe" - Invoke-WebRequest -Uri $dlUrl -OutFile $tmp -UseBasicParsing - Write-LogHybrid "Downloaded agent to $tmp." "Info" "DattoRMM" -LogToEvent - Start-Process -FilePath $tmp -NoNewWindow - Write-LogHybrid "RMM agent installer launched." "Success" "DattoRMM" -LogToEvent - } catch { - Write-LogHybrid "Agent install failed: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent - } - } - } - - if ($SaveCopy) { - try { - $dlUrl = "https://zinfandel.centrastage.net/csm/profile/downloadAgent/$SiteUID" - $path = "C:\Temp\AgentInstall.exe" - if (-not (Test-Path 'C:\Temp')) { New-Item -Path 'C:\Temp' -ItemType Directory | Out-Null } - Invoke-WebRequest -Uri $dlUrl -OutFile $path -UseBasicParsing - Write-LogHybrid "Saved installer copy to $path." "Info" "DattoRMM" -LogToEvent - } catch { - Write-LogHybrid "Save-copy failed: $($_.Exception.Message)" "Error" "DattoRMM" -LogToEvent - } - } - - if (-not ($PushSiteVars -or $InstallRMM -or $SaveCopy)) { - Write-LogHybrid "No action specified. Use -FetchSites, -SaveSitesList, -PushSiteVars, -InstallRMM, or -SaveCopy." "Warning" "DattoRMM" -LogToEvent - } -} - #endregion Install-DattoRMM - - #region Dispatch-Request - function Dispatch-Request { - param($Context) - - $path = $Context.Request.Url.AbsolutePath.TrimStart('/') - - if ($path -eq 'quit') { - Write-LogHybrid "Shutdown requested" "Info" "Server" -LogToEvent - Respond-Text $Context "Server shutting down." - $Global:Listener.Stop() - return - } - - if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'getpw') { - Handle-FetchSites $Context - return - } - - if ($path -in @('', 'onboard', 'offboard', 'tweaks', 'SVSApps')) { - $page = if ($path -eq '') { 'onboard' } else { $path } - $html = Get-UIHtml -Page $page - Respond-HTML $Context $html - return - } - - $task = $Global:Tasks | Where-Object Name -EQ $path - if ($task) { & $task.HandlerFn $Context; return } - - $Context.Response.StatusCode = 404 - Respond-Text $Context '404 - Not Found' - } - #endregion Dispatch-Request - - #region MAIN MODE SWITCH - switch ($PSCmdlet.ParameterSetName) { - 'Toolkit' { - Write-LogHybrid "Toolkit-only mode" "Info" "Startup" -LogToEvent - Install-SVSMSP -InstallToolkit - return - } - 'Cleanup' { - Write-LogHybrid "Running Toolkit cleanup mode" "Info" "Startup" -LogToEvent - Install-SVSMSP -Cleanup - return - } - 'DattoFetch' { - Write-LogHybrid "Fetching site list only…" "Info" "DattoAuth" -LogToEvent - $sites = Install-DattoRMM -UseWebhook -WebhookPassword $WebhookPassword -FetchSites -SaveSitesList:$SaveSitesList -OutputFile $OutputFile - Write-LogHybrid "Done." "Success" "DattoAuth" -LogToEvent - return - } - 'DattoInstall' { - Write-LogHybrid "Headless DattoRMM deploy" "Info" "DattoAuth" -LogToEvent - if ($PSCmdlet.ShouldProcess("Datto site '$SiteName'", "Headless install")) { - Install-DattoRMM -UseWebhook -WebhookPassword $WebhookPassword -SiteUID $SiteUID -SiteName $SiteName -PushSiteVars:$PushSiteVars -InstallRMM:$InstallRMM -SaveCopy:$SaveCopy - } - return - } - 'UI' { - Write-LogHybrid "Starting ScriptMonkey UI on http://localhost:$Port/" Info Startup - try { - Start-Server - try { - Start-Process "msedge.exe" -ArgumentList "--app=http://localhost:$Port" -ErrorAction Stop - } catch { - Start-Process "http://localhost:$Port" - } - } catch { - Write-LogHybrid "Failed to start server: $($_.Exception.Message)" Error Startup -LogToEvent - } - return - } - - } - #endregion MAIN MODE SWITCH - - #region Bootstrap NuGet/PSGallery silently - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - $ProgressPreference = 'SilentlyContinue' - $ConfirmPreference = 'None' - - $provPath = "$env:ProgramData\PackageManagement\ProviderAssemblies" - if (-not (Test-Path $provPath)) { - try { - New-Item -Path $provPath -ItemType Directory -Force -ErrorAction Stop | Out-Null - Write-LogHybrid "Created missing provider folder: $provPath" "Info" "Bootstrap" -LogToEvent - } catch { - Write-LogHybrid "Failed to create provider folder: $($_.Exception.Message)" "Warning" "Bootstrap" -LogToEvent - } - } - - if (-not (Get-Command Install-PackageProvider -ErrorAction SilentlyContinue)) { - try { - Install-Module PowerShellGet -Force -AllowClobber -Confirm:$false -ErrorAction Stop - Write-LogHybrid "Installed PowerShellGet module" "Info" "Bootstrap" -LogToEvent - } catch { - Write-LogHybrid "PowerShellGet install failed: $($_.Exception.Message)" "Error" "Bootstrap" -LogToEvent - } - } - - $pkgMgmtVersion = (Get-Module PackageManagement -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Version - if ($pkgMgmtVersion -lt [Version]"1.3.1") { - try { - Install-Module PackageManagement -Force -AllowClobber -Confirm:$false -ErrorAction Stop - Write-LogHybrid "Updated PackageManagement to latest version" "Info" "Bootstrap" -LogToEvent - } catch { - Write-LogHybrid "PackageManagement update failed: $($_.Exception.Message)" "Warning" "Bootstrap" -LogToEvent - } - } - - Import-Module PackageManagement -Force -ErrorAction SilentlyContinue | Out-Null - Import-Module PowerShellGet -Force -ErrorAction SilentlyContinue | Out-Null - - $gallery = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue - if ($gallery -and $gallery.InstallationPolicy -ne 'Trusted') { - try { - Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop - Write-LogHybrid "PSGallery marked as Trusted" "Info" "Bootstrap" -LogToEvent - } catch { - Write-LogHybrid "Failed to trust PSGallery: $($_.Exception.Message)" "Warning" "Bootstrap" -LogToEvent - } - } - - $nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue - if (-not $nuget) { - try { - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false -ErrorAction Stop - $nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue - Write-LogHybrid "Installed NuGet provider v$($nuget.Version)" "Info" "Bootstrap" -LogToEvent - } catch { - Write-LogHybrid "NuGet install failed: $($_.Exception.Message)" "Error" "Bootstrap" -LogToEvent - } - } else { - Write-LogHybrid "NuGet provider already present (v$($nuget.Version))" "Info" "Bootstrap" -LogToEvent - } - - try { Import-PackageProvider -Name NuGet -Force -ErrorAction Stop | Out-Null } - catch { Write-LogHybrid "NuGet provider import failed: $($_.Exception.Message)" "Error" "Bootstrap" -LogToEvent } - #endregion Bootstrap NuGet/PSGallery silently -} -# end function Invoke-ScriptMonkey - -# Entrypoint -if ($MyInvocation.InvocationName -eq '.') { - # dot-sourced, don't invoke -} elseif ($PSCommandPath) { - Invoke-ScriptMonkey @PSBoundParameters -} else { - if ($args.Count -gt 0) { - $namedArgs = @{} - for ($i = 0; $i -lt $args.Count; $i++) { - if ($args[$i] -is [string] -and $args[$i].StartsWith('-')) { - $key = $args[$i].TrimStart('-') - $next = $args[$i + 1] - if ($next -and ($next -notlike '-*')) { $namedArgs[$key] = $next; $i++ } else { $namedArgs[$key] = $true } - } - } - Invoke-ScriptMonkey @namedArgs - } else { - Invoke-ScriptMonkey - } -}