975 lines
43 KiB
PowerShell
975 lines
43 KiB
PowerShell
#region ScriptMonkey.ps1 (full updated script)
|
|
|
|
#region changes to be done (now implemented)
|
|
# - Event logging uses custom Windows log "SVS Scripting".
|
|
# - Added SVS APPs tasks:
|
|
# 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
|
|
|
|
<#
|
|
.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 + Elevation
|
|
# ─── Relaunch elevated (Admin) + with ExecutionPolicy Bypass when needed ───
|
|
$needBypass = ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage' -or (Get-ExecutionPolicy) -eq 'Restricted')
|
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
$principal = [Security.Principal.WindowsPrincipal]$identity
|
|
$needAdmin = -not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
|
|
if ($needBypass -or $needAdmin) {
|
|
Write-Host "[Info] Relaunching elevated (Admin) with ExecutionPolicy Bypass..." -ForegroundColor Yellow
|
|
$args = if ($PSCommandPath) { "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" } else { "-NoProfile -ExecutionPolicy Bypass -Command `"& { iwr 'https://sm.svstools.com' -UseBasicParsing | iex }`"" }
|
|
Start-Process -FilePath "powershell.exe" -ArgumentList $args -Verb RunAs | Out-Null
|
|
exit
|
|
}
|
|
|
|
# ─── TLS and silent defaults ───
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
$ProgressPreference = 'SilentlyContinue'
|
|
$ConfirmPreference = 'None'
|
|
#endregion Safely bypass Restricted Execution Policy + Elevation
|
|
|
|
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"
|
|
|
|
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",
|
|
[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: $_" -ForegroundColor Yellow }
|
|
}
|
|
|
|
if ($LogToEvent) {
|
|
try {
|
|
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
|
|
}
|
|
|
|
$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 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",
|
|
[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 Tasks registry
|
|
|
|
$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 Tasks registry
|
|
|
|
#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$tooltip><input type='checkbox' id='$taskId' name='$($_.Name)' data-column='$Column'> $($_.Label)</label>"
|
|
|
|
if ($_.SubOptions) {
|
|
$subHtml = (
|
|
$_.SubOptions |
|
|
ForEach-Object {
|
|
"<label style='margin-left:20px; display:block;'>
|
|
<input type='checkbox' class='sub-option-$taskId' name='$($_.Value)' value='$($_.Value)'> $($_.Label)
|
|
</label>"
|
|
}
|
|
) -join "`n"
|
|
|
|
$html += @"
|
|
<div id='${taskId}OptionsContainer' style='display:none; margin-top:4px;'>
|
|
$subHtml
|
|
</div>
|
|
"@
|
|
}
|
|
$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 "<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>"
|
|
}
|
|
#endregion Get-ModuleVersionHtml
|
|
|
|
#region HTTP prereqs (URL ACL + port check)
|
|
|
|
function Ensure-HttpPrereqs {
|
|
param(
|
|
[int]$Port,
|
|
[string[]]$Prefixes
|
|
)
|
|
|
|
# Port already bound?
|
|
try {
|
|
$inUse = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction Stop
|
|
} catch { $inUse = $null }
|
|
if ($inUse) {
|
|
$pids = ($inUse | Select-Object -ExpandProperty OwningProcess -Unique) -join ','
|
|
throw "Port $Port is already in use (PID(s): $pids). Choose another port or stop the process."
|
|
}
|
|
|
|
# Ensure URL ACLs
|
|
$current = (& netsh http show urlacl) 2>$null
|
|
foreach ($p in $Prefixes) {
|
|
$pfx = if ($p.EndsWith('/')) { $p } else { "$p/" }
|
|
if ($null -eq $current -or ($current -join "`n") -notmatch [regex]::Escape($pfx)) {
|
|
$user = "$env:USERDOMAIN\$env:USERNAME"
|
|
$args = "http add urlacl url=$pfx user=""$user"" listen=yes"
|
|
$psi = New-Object System.Diagnostics.ProcessStartInfo -Property @{
|
|
FileName = 'netsh.exe'
|
|
Arguments = $args
|
|
Verb = 'runas'
|
|
UseShellExecute = $true
|
|
WindowStyle = 'Hidden'
|
|
}
|
|
$proc = [System.Diagnostics.Process]::Start($psi); $proc.WaitForExit()
|
|
if ($proc.ExitCode -ne 0) {
|
|
throw "Failed to add URL ACL for $pfx (netsh exit $($proc.ExitCode))."
|
|
} else {
|
|
Write-LogHybrid "Registered URL ACL for $pfx" "Success" "Server" -LogToEvent
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion HTTP prereqs
|
|
|
|
#region Start-Server (hardened for 25H2)
|
|
|
|
function Start-Server {
|
|
$prefixes = @("http://localhost:$Port/","http://127.0.0.1:$Port/")
|
|
try {
|
|
Ensure-HttpPrereqs -Port $Port -Prefixes $prefixes
|
|
} catch {
|
|
Write-LogHybrid "HTTP prereq check failed: $($_.Exception.Message)" "Error" "Server" -LogToEvent
|
|
throw
|
|
}
|
|
|
|
$Global:Listener = [System.Net.HttpListener]::new()
|
|
foreach ($p in $prefixes) { $Global:Listener.Prefixes.Add($p) }
|
|
|
|
try {
|
|
$Global:Listener.Start()
|
|
Write-LogHybrid "Listening on $($prefixes -join ', ')" "Info" "Server" -LogToEvent
|
|
} catch {
|
|
Write-LogHybrid "HttpListener failed to start: $($_.Exception.Message)" "Error" "Server" -LogToEvent
|
|
if ($_.Exception.InnerException) {
|
|
Write-LogHybrid "Inner: $($_.Exception.InnerException.Message)" "Error" "Server" -LogToEvent
|
|
}
|
|
throw
|
|
}
|
|
|
|
try {
|
|
while ($Global:Listener.IsListening) {
|
|
$ctx = $Global:Listener.GetContext()
|
|
try {
|
|
Dispatch-Request $ctx
|
|
} catch {
|
|
Write-LogHybrid "Dispatch error: $($_.Exception.Message)" "Error" "Server" -LogToEvent
|
|
$ctx.Response.StatusCode = 500
|
|
Respond-Text $ctx "Internal server error."
|
|
}
|
|
}
|
|
} finally {
|
|
$Global:Listener.Close()
|
|
Write-LogHybrid "Listener closed." "Info" "Server" -LogToEvent
|
|
}
|
|
}
|
|
|
|
#endregion Start-Server
|
|
|
|
#region UI HTML/JS
|
|
|
|
function Get-UIHtml {
|
|
param([string]$Page = 'onboard')
|
|
|
|
$style = @'
|
|
<style>
|
|
:root {
|
|
--background-color: rgba(18, 18, 18, 1);
|
|
--border-color: rgba(255,127,0,0.25);
|
|
--white-color: rgba(255,255,255);
|
|
--gray-color: rgba(102,102,102);
|
|
--dark-gray-color: rgba(51,51,51);
|
|
--light-gray-color: rgba(187,187,187);
|
|
--btn-sidebar-light-gray: rgba(68,68,68);
|
|
--btn-sidebar-blue: rgba(30,144,255,1);
|
|
--btn-hover: rgba(0,86,179,1);
|
|
--btn-hover-scale: 1.05;
|
|
--btn-success: rgba(40,167,69);
|
|
--btn-danger: rgba(220,53,69);
|
|
}
|
|
body { font-family: Arial, sans-serif; margin:0; padding:0; background-color:var(--background-color); color:var(--white-color); height:100%; overflow:hidden; }
|
|
.logo-container { display:grid; grid-template-columns:auto 1fr; align-items:center; padding:20px; }
|
|
.logo-container img { max-width:300px; height:auto; }
|
|
.subtitle { font-size:1.2rem; color:var(--gray-color); margin-top:0.5em; }
|
|
.container { display:flex; height:100vh; overflow:hidden; }
|
|
.sidebar { width:200px; background:var(--background-color); padding:10px; }
|
|
.sidebar button { display:block; width:100%; margin-bottom:10px; padding:10px; color:var(--white-color); background:var(--btn-sidebar-light-gray); border:none; border-radius:5px; cursor:pointer; text-align:left; transition:background-color 0.3s, transform 0.2s; }
|
|
.sidebar button.active { background:var(--btn-sidebar-blue); }
|
|
.sidebar button:hover { background:var(--btn-hover); transform:scale(var(--btn-hover-scale)); }
|
|
.content { position:relative; flex:1; padding:20px; overflow-y:auto; max-height:calc(100vh - 50px); }
|
|
.fixed-buttons { position:fixed; bottom:20px; right:20px; display:flex; gap:10px; z-index:1000; }
|
|
.exit-button, .run-button { border:none; border-radius:5px; padding:10px 20px; cursor:pointer; color:var(--white-color); }
|
|
.exit-button { background-color:var(--btn-danger); }
|
|
.run-button { background-color:var(--btn-success); }
|
|
.tab-content { display:none; } .tab-content.active { display:block; }
|
|
.columns-container { display:flex; gap:20px; flex-wrap:wrap; align-items:flex-start; }
|
|
.column { flex:1; max-width:45%; border:2px solid var(--border-color); border-radius:8px; padding:10px; background-color:var(--dark-gray-color); box-shadow:0 4px 6px rgba(0,0,0,0.3); }
|
|
.checkbox-group label { display:flex; align-items:center; margin-bottom:8px; }
|
|
#PasswordContainer, #dattoRmmContainer { margin-top: 1em; }
|
|
#PasswordContainer input, #PasswordContainer button, #dattoRmmContainer select { background-color:var(--dark-gray-color); color:var(--white-color); border:1px solid var(--border-color); border-radius:4px; padding:8px; font-size:14px; display:block; width:40%; max-width:200px; }
|
|
#PasswordContainer button { background-color:var(--btn-sidebar-blue); cursor:pointer; transition:background-color 0.3s ease; }
|
|
#PasswordContainer button:hover { background-color:var(--btn-hover); }
|
|
#tagline { font-size:1.2rem; color:var(--light-gray-color); font-weight:bold; justify-self:center; }
|
|
@media (max-width:768px){ .container{flex-direction:column;} .sidebar{width:100%;} }
|
|
</style>
|
|
'@
|
|
|
|
$script = @'
|
|
<script>
|
|
let completedTasks = 0;
|
|
let totalTasks = 0;
|
|
function setTotalTaskCount(count){ totalTasks=count; completedTasks=0; updateTitle(); }
|
|
function logProgress(label,isSuccess){
|
|
const statusBox=document.getElementById('status-box');
|
|
completedTasks++; updateTitle();
|
|
const msg=isSuccess?`✅ ${completedTasks}/${totalTasks} done: ${label}`:`❌ ${completedTasks}/${totalTasks} failed: ${label}`;
|
|
const div=document.createElement('div'); div.style.color=isSuccess?'lime':'red'; div.textContent=msg; statusBox?.appendChild(div);
|
|
if(completedTasks===totalTasks){
|
|
const finalMsg=document.createElement('div'); finalMsg.style.marginTop='10px'; finalMsg.innerHTML=`<strong>✅ All tasks complete (${completedTasks}/${totalTasks})</strong>`; statusBox?.appendChild(finalMsg);
|
|
document.title=`✅ ScriptMonkey - Complete (${completedTasks}/${totalTasks})`;
|
|
const sound=new Audio('data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAESsAACJWAAACABAAZGF0YQAAAAA='); sound.play().catch(()=>{});
|
|
flashTitle(document.title);
|
|
}
|
|
}
|
|
function updateTitle(){ document.title=`ScriptMonkey - ${completedTasks}/${totalTasks} Done`; }
|
|
function flashTitle(finalTitle){ let flashes=0; const interval=setInterval(()=>{ document.title=document.title===''?finalTitle:''; if(++flashes>=10){ clearInterval(interval); document.title=finalTitle;} },800); }
|
|
|
|
// Tabs
|
|
const tabButtons=document.querySelectorAll(".tab-button");
|
|
const tabContents=document.querySelectorAll(".tab-content");
|
|
tabButtons.forEach(btn=>{ btn.addEventListener("click",()=>{ tabButtons.forEach(b=>b.classList.remove("active")); tabContents.forEach(c=>c.classList.remove("active")); btn.classList.add("active"); document.getElementById(btn.dataset.tab).classList.add("active"); }); });
|
|
document.querySelector(".tab-button[data-tab='{{defaultPage}}Tab']").classList.add("active");
|
|
document.getElementById("{{defaultPage}}Tab").classList.add("active");
|
|
|
|
// Tasks array
|
|
const tasks = [
|
|
{{tasksJsAll}}
|
|
];
|
|
|
|
// Select-All per column
|
|
function toggleColumn(col){
|
|
const master=document.getElementById(`selectAll${col[0].toUpperCase()+col.slice(1)}Checkbox`);
|
|
const children=document.querySelectorAll(`#onboardTab input[type=checkbox][data-column=${col}]`);
|
|
children.forEach(cb=>{ cb.checked=master.checked; });
|
|
setTimeout(()=>{ children.forEach(cb=>cb.dispatchEvent(new Event('change'))); },0);
|
|
}
|
|
function updateSelectAll(col){
|
|
const master=document.getElementById(`selectAll${col[0].toUpperCase()+col.slice(1)}Checkbox`);
|
|
const children=document.querySelectorAll(`#onboardTab input[type=checkbox][data-column=${col}]`);
|
|
master.checked=Array.from(children).every(cb=>cb.checked);
|
|
}
|
|
['left','right'].forEach(col=>{
|
|
document.querySelectorAll(`#onboardTab input[type=checkbox][data-column=${col}]`).forEach(cb=>cb.addEventListener('change',()=>updateSelectAll(col)));
|
|
});
|
|
|
|
// DattoRMM Master + Enter key shortcuts
|
|
function toggleDattoRMMOptions(){
|
|
const master=document.getElementById('installDattoRMM');
|
|
const container=document.getElementById('installDattoRMMOptionsContainer');
|
|
if(!container) return;
|
|
container.style.display=master.checked?'block':'none';
|
|
container.querySelectorAll('input[type="checkbox"]').forEach(cb=>cb.checked=master.checked);
|
|
}
|
|
document.addEventListener('DOMContentLoaded',()=>{
|
|
const master=document.getElementById('installDattoRMM'); if(master) master.addEventListener('change',toggleDattoRMMOptions);
|
|
const passwordField=document.getElementById('Password'); const goButton=document.querySelector("button[onclick='fetchSites()']");
|
|
passwordField.addEventListener('keydown',(e)=>{ if(e.key==='Enter'){ goButton.click(); }});
|
|
const siteDropdown=document.getElementById('dattoDropdown'); const runButton=document.querySelector('.run-button');
|
|
siteDropdown.addEventListener('keydown',(e)=>{ if(e.key==='Enter' && siteDropdown.value){ runButton.click(); }});
|
|
});
|
|
|
|
// Fetch Sites
|
|
async function fetchSites(){
|
|
const pwd=document.getElementById("Password").value;
|
|
if(!pwd){ alert("Please enter the password."); return; }
|
|
const dropdown=document.getElementById("dattoDropdown");
|
|
dropdown.innerHTML='<option disabled selected>Loading sites...</option>';
|
|
try{
|
|
const resp=await fetch("/getpw",{ method:"POST", headers:{ "Content-Type":"application/json" }, body:JSON.stringify({ password: pwd }) });
|
|
if(!resp.ok) throw("HTTP "+resp.status);
|
|
const sites=await resp.json(); dropdown.innerHTML='';
|
|
sites.forEach(site=>{ const option=document.createElement("option"); option.value=site.UID; option.textContent=site.Name; dropdown.appendChild(option); });
|
|
document.getElementById("dattoRmmContainer").style.display="block";
|
|
}catch(e){ console.error(e); dropdown.innerHTML='<option disabled selected>Error loading sites</option>'; alert("Failed to fetch sites. Check password and try again."); }
|
|
}
|
|
|
|
// Run Selected
|
|
async function triggerInstall(){
|
|
const runBtn=document.querySelector('.run-button'); runBtn.disabled=true;
|
|
const statusBox=document.getElementById('status-box'); if(statusBox) statusBox.innerHTML='';
|
|
try{
|
|
const checkedTasks=tasks.filter(t=>{ const cb=document.getElementById(t.id); return cb && cb.checked; });
|
|
setTotalTaskCount(checkedTasks.length);
|
|
|
|
// DattoRMM first
|
|
const dattoCB=document.getElementById('installDattoRMM');
|
|
if(dattoCB && dattoCB.checked){
|
|
try{
|
|
const sub=Array.from(document.querySelectorAll('.sub-option-installDattoRMM:checked')).map(x=>x.value);
|
|
const dropdown=document.getElementById('dattoDropdown'); const uid=dropdown?.value; const name=dropdown?.selectedOptions?.[0]?.text || 'Datto';
|
|
await fetch('/installDattoRMM',{ method:'POST', headers:{ 'Content-Type':'application/json' }, body:JSON.stringify({ checkedValues: sub, UID: uid, Name: name }) });
|
|
logProgress('Install DattoRMM', true);
|
|
}catch(e){ logProgress('Install DattoRMM', false); console.error(e); }
|
|
}
|
|
|
|
// SVSMSP second
|
|
const svsCB=document.getElementById('installSVSMSPModule');
|
|
if(svsCB && svsCB.checked){
|
|
try{ await fetch('/installSVSMSPModule',{ method:'GET' }); logProgress('Install SVSMSP Module', true); }
|
|
catch(e){ logProgress('Install SVSMSP Module', false); console.error(e); }
|
|
}
|
|
|
|
// Remaining tasks
|
|
for(const t of tasks){
|
|
if(['installDattoRMM','installSVSMSPModule'].includes(t.id)) continue;
|
|
const cb=document.getElementById(t.id); if(!cb || !cb.checked) continue;
|
|
try{ await fetch(t.handler,{ method:'GET' }); logProgress(t.label||t.id, true); }
|
|
catch(e){ logProgress(t.label||t.id, false); console.error(`Error running ${t.id}:`, e); }
|
|
}
|
|
}catch(e){ console.error('triggerInstall fatal error:', e); }
|
|
finally{ runBtn.disabled=false; }
|
|
}
|
|
|
|
// Quit
|
|
function endSession(){ fetch("/quit",{ method:"GET" }).finally(()=>window.close()); }
|
|
|
|
// Sub-options visibility on load
|
|
document.addEventListener('DOMContentLoaded', function(){
|
|
const tasksWithSubOptions=document.querySelectorAll('[id$="OptionsContainer"]');
|
|
tasksWithSubOptions.forEach(container=>{
|
|
const taskId=container.id.replace('OptionsContainer','');
|
|
const masterCheckbox=document.getElementById(taskId);
|
|
if(!masterCheckbox) return;
|
|
function updateVisibility(){
|
|
const checked=masterCheckbox.checked;
|
|
container.style.display=checked?'block':'none';
|
|
container.querySelectorAll('input[type="checkbox"]').forEach(cb=>cb.checked=checked);
|
|
if(taskId==='installDattoRMM'){
|
|
const pwdBox=document.getElementById('PasswordContainer');
|
|
const rmmBox=document.getElementById('dattoRmmContainer');
|
|
if(pwdBox) pwdBox.style.display=checked?'block':'none';
|
|
if(rmmBox) rmmBox.style.display=checked?'block':'none';
|
|
}
|
|
}
|
|
masterCheckbox.addEventListener('change', updateVisibility);
|
|
updateVisibility();
|
|
});
|
|
});
|
|
|
|
// rotating tagline
|
|
document.addEventListener('DOMContentLoaded', ()=>{
|
|
const taglines=[
|
|
"Fast deployments, no monkey business.",
|
|
"Bananas for better builds.",
|
|
"Deploy without flinging code.",
|
|
"Tame your stack. Unleash the monkey.",
|
|
"Monkey see, monkey deploy.",
|
|
"Deploy smarter -- with a monkey on your team.",
|
|
"Don't pass the monkey -- let it deploy.",
|
|
"No more monkeying around. Stack handled.",
|
|
"Own your stack. But let the monkey do the work.",
|
|
"Why throw code when the monkey's got it?",
|
|
"Deployments so easy, a monkey could do it. Ours does.",
|
|
"Monkey in the stack, not on your back."
|
|
];
|
|
const el=document.getElementById("tagline"); let idx=Math.floor(Math.random()*taglines.length); el.textContent=taglines[idx];
|
|
setInterval(()=>{ idx=(idx+1)%taglines.length; el.textContent=taglines[idx]; },10000);
|
|
});
|
|
|
|
// notify server on unload
|
|
window.addEventListener('beforeunload', ()=>{ fetch('/quit', { method:'GET', keepalive:true }); });
|
|
</script>
|
|
'@
|
|
|
|
$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>
|
|
<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">
|
|
{{moduleVersion}}
|
|
</div>
|
|
<div class="logo-right" style="text-align:right;">
|
|
<img src="https://git.svstools.com/syelle/Logo/raw/branch/main/SAMY.png" alt="SAMY Logo" style="max-width:180px;">
|
|
<div id="tagline" style="font-size:1.1rem; color:var(--light-gray-color); font-weight:bold;">Script Automation Monkey (Yeah!)</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<div class="sidebar">
|
|
<button class="tab-button" data-tab="onboardTab">On-Boarding</button>
|
|
<button class="tab-button" data-tab="offboardTab">Off-Boarding</button>
|
|
<button class="tab-button" data-tab="tweaksTab">Tweaks</button>
|
|
<button class="tab-button" data-tab="SVSAppsTab">SVS APPs</button>
|
|
</div>
|
|
<div class="content">
|
|
<div id="onboardTab" class="tab-content">
|
|
<h2>On-Boarding</h2>
|
|
<h3 class="subtitle">This new deployment method ensures everything is successfully deployed with greater ease!</h3>
|
|
|
|
<div class="columns-container">
|
|
<div class="checkbox-group column">
|
|
<h3>SVSMSP Stack</h3>
|
|
<label><input type="checkbox" id="selectAllLeftCheckbox" onclick="toggleColumn('left')"> Select All</label>
|
|
{{onboardLeftColumn}}
|
|
</div>
|
|
<div class="checkbox-group column">
|
|
<h3>Optional</h3>
|
|
<label><input type="checkbox" id="selectAllRightCheckbox" onclick="toggleColumn('right')"> Select All</label>
|
|
{{onboardRightColumn}}
|
|
</div>
|
|
</div>
|
|
|
|
<div id="PasswordContainer" style="display:none; margin-bottom:1em;">
|
|
<label for="Password">Enter Password:</label>
|
|
<div style="display:flex; gap:5px;">
|
|
<input type="password" id="Password" placeholder="Enter Password" style="flex:1;" />
|
|
<button onclick="fetchSites()" style="padding:4px 10px;">GO!</button>
|
|
</div>
|
|
</div>
|
|
|
|
<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}}
|
|
</div>
|
|
</div>
|
|
|
|
<div id="tweaksTab" class="tab-content">
|
|
<h2>Tweaks</h2>
|
|
<div class="columns-container">
|
|
{{tweaksCheckboxes}}
|
|
</div>
|
|
</div>
|
|
|
|
<div id="SVSAppsTab" class="tab-content">
|
|
<h2>SVS APPs</h2>
|
|
<div class="columns-container">
|
|
{{appsCheckboxes}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
$script
|
|
<div class="fixed-buttons">
|
|
<button class="exit-button" onclick="endSession()">Exit</button>
|
|
<button class="run-button" onclick="triggerInstall()">Run Selected</button>
|
|
</div>
|
|
<div id="status-box" style="margin-top: 1em; font-family: monospace;"></div>
|
|
</body>
|
|
</html>
|
|
"@
|
|
|
|
# Build dynamic sections
|
|
$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 UI HTML/JS
|
|
|
|
#region HTTP response 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)][object]$Context,[Parameter(Mandatory)][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
|
|
|
|
# store for next call
|
|
$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: $_" "Error" "OnBoard"
|
|
Respond-Text $Context "ERROR: $_"
|
|
}
|
|
}
|
|
|
|
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 response 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" -L_
|