#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 = "" 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 "