diff --git a/StackMonkey.ps1 b/StackMonkey.ps1 index 5b711d4..ece368f 100644 --- a/StackMonkey.ps1 +++ b/StackMonkey.ps1 @@ -87,1480 +87,1497 @@ # ───────────────────────────────────────────────────────────────────────── # 1) ENTRYPOINT + PARAMETER DECLARATION # ───────────────────────────────────────────────────────────────────────── - - [CmdletBinding( - DefaultParameterSetName='UI', - SupportsShouldProcess=$true, - ConfirmImpact= 'Medium' - )] - param( - # ───────────────────────────────────────────────────────── - # Toolkit-only mode - [Parameter(Mandatory,ParameterSetName='Toolkit')][switch]$SilentInstall, - # ───────────────────────────────────────────────────────── - # Datto headless mode - - # Both Datto sets share the webhook password - # Shared webhook password for both Datto modes +function Invoke-ScriptMonkey { + [CmdletBinding( + DefaultParameterSetName='UI', + SupportsShouldProcess=$true, + ConfirmImpact='Medium' + )] + param( + [Parameter(Mandatory,ParameterSetName='Toolkit')] [switch]$SilentInstall, [Parameter(Mandatory,ParameterSetName='DattoFetch')] [Parameter(Mandatory,ParameterSetName='DattoInstall')] - [string]$N8nPassword, - - # ───────────────────────────────────────────────────────── - # Fetch only set write sites and exit - [Parameter(ParameterSetName='DattoFetch')][switch] $FetchSitesOnly, - [Parameter(ParameterSetName='DattoFetch')][string] $OutputFile = 'datto_sites.csv', - - # ───────────────────────────────────────────────────────── - # Install set: target site must be provided - [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 — guarantee NuGet provider is present without prompting - -# ─── Top of script ─── -Import-Module PackageManagement -Force -ErrorAction SilentlyContinue | Out-Null -Import-Module PowerShellGet -Force -ErrorAction SilentlyContinue | Out-Null - -# ─── ensure TLS 1.2 + no prompts ─── -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$ProgressPreference = 'SilentlyContinue' -$ConfirmPreference = 'None' - -# check if NuGet exists (no output—assigned to $nuget) -$nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue - -if (-not $nuget) { - # install it (again, assignment suppresses the table) - Install-PackageProvider ` - -Name NuGet ` - -MinimumVersion 2.8.5.201 ` - -Force ` - -Confirm:$false - - - # re-query just for version info - $found = Get-PackageProvider -Name NuGet -ListAvailable - Write-Host "Installed NuGet provider v$($found.Version)" -ForegroundColor Green -} -else { - Write-Host "NuGet provider already present (v$($found.Version))" -ForegroundColor DarkGray -} - -# now import it silently -Import-PackageProvider -Name NuGet -Force -ErrorAction SilentlyContinue | Out-Null - -# ensure trust PSGallery without its own output (so you don't get “untrusted repository” prompt -$gallery = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue -if ($gallery.InstallationPolicy -ne 'Trusted') { - Set-PSRepository ` - -Name PSGallery ` - -InstallationPolicy Trusted ` - -ErrorAction SilentlyContinue | Out-Null - - Write-Host "PSGallery marked as Trusted" -ForegroundColor Green -} - -#endregion - - - # ───────────────────────────────────────────────────────────────────────── - # 2) GLOBAL SETTINGS & HELPERS - # ───────────────────────────────────────────────────────────────────────── - - # Listening port for HTTP UI - $Port = 8082 - - # Configurable endpoints - $Global:DattoWebhookUrl = 'https://automate.svstools.ca/webhook/svsmspkit' - -#region Get-DattoApiCredentials -function Get-DattoApiCredentials { - [CmdletBinding()] - param ( - [Parameter(Mandatory)][string]$Password + [string]$N8nPassword, + … ) - $headers = @{ "SVSMSPKit" = $Password } - try { - $resp = Invoke-RestMethod -Uri $Global:DattoWebhookUrl ` - -Headers $headers ` - -Method GET - return @{ - ApiUrl = $resp.ApiUrl - ApiKey = $resp.ApiKey - ApiSecretKey = $resp.ApiSecretKey - } - } - catch { - Write-LogHybrid "Failed to fetch API credentials: $($_.Exception.Message)" Error DattoAuth - return $null - } -} -function Get-DattoRmmSites { - [CmdletBinding()] + # — all of your modules, helpers, functions, etc. go here — + + [CmdletBinding( + DefaultParameterSetName='UI', + SupportsShouldProcess=$true, + ConfirmImpact= 'Medium' + )] param( - [Parameter(Mandatory)] - [string] $Password, + # ───────────────────────────────────────────────────────── + # Toolkit-only mode + [Parameter(Mandatory,ParameterSetName='Toolkit')][switch]$SilentInstall, + # ───────────────────────────────────────────────────────── + # Datto headless mode - [Parameter()] - [string] $WebhookUrl = $Global:DattoWebhookUrl + # Both Datto sets share the webhook password + # Shared webhook password for both Datto modes + [Parameter(Mandatory,ParameterSetName='DattoFetch')] + [Parameter(Mandatory,ParameterSetName='DattoInstall')] + [string]$N8nPassword, + + # ───────────────────────────────────────────────────────── + # Fetch only set write sites and exit + [Parameter(ParameterSetName='DattoFetch')][switch] $FetchSitesOnly, + [Parameter(ParameterSetName='DattoFetch')][string] $OutputFile = 'datto_sites.csv', + + # ───────────────────────────────────────────────────────── + # Install set: target site must be provided + [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 ) - # 1) Fetch Datto API credentials from your webhook - Write-Verbose "Fetching Datto API credentials from $WebhookUrl" - try { - $headers = @{ 'SVSMSPKit' = $Password } - $creds = Invoke-RestMethod -Uri $WebhookUrl -Headers $headers -Method GET - } - catch { - Throw "Failed to fetch credentials from webhook: $_" - } + #region — guarantee NuGet provider is present without prompting - $apiUrl = $creds.ApiUrl - $apiKey = $creds.ApiKey - $apiSecretKey = $creds.ApiSecretKey + # ─── Top of script ─── + Import-Module PackageManagement -Force -ErrorAction SilentlyContinue | Out-Null + Import-Module PowerShellGet -Force -ErrorAction SilentlyContinue | Out-Null - # 2) Request an OAuth token - Write-Verbose "Requesting OAuth token from $apiUrl/auth/oauth/token" - try { - $securePwd = ConvertTo-SecureString -String 'public' -AsPlainText -Force - $credObj = New-Object System.Management.Automation.PSCredential('public-client', $securePwd) + # ─── ensure TLS 1.2 + no prompts ─── + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + $ProgressPreference = 'SilentlyContinue' + $ConfirmPreference = 'None' - $tokenResp = Invoke-RestMethod ` - -Uri "$apiUrl/auth/oauth/token" ` - -Credential $credObj ` - -Method 'POST' ` - -ContentType 'application/x-www-form-urlencoded' ` - -Body "grant_type=password&username=$apiKey&password=$apiSecretKey" + # check if NuGet exists (no output—assigned to $nuget) + $nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue - $token = $tokenResp.access_token - } - catch { - Throw "Failed to obtain OAuth token: $_" - } + if (-not $nuget) { + # install it (again, assignment suppresses the table) + Install-PackageProvider ` + -Name NuGet ` + -MinimumVersion 2.8.5.201 ` + -Force ` + -Confirm:$false - # 3) Fetch the list of RMM sites - Write-Verbose "Fetching RMM sites from $apiUrl/api/v2/account/sites" - try { - $authHeader = @{ Authorization = "Bearer $token" } - $sitesResp = Invoke-RestMethod ` - -Uri "$apiUrl/api/v2/account/sites" ` - -Method 'GET' ` - -Headers $authHeader ` - -ContentType 'application/json' + + # re-query just for version info + $found = Get-PackageProvider -Name NuGet -ListAvailable + Write-Host "Installed NuGet provider v$($found.Version)" -ForegroundColor Green + } + else { + Write-Host "NuGet provider already present (v$($found.Version))" -ForegroundColor DarkGray + } - $siteList = $sitesResp.sites | Select-Object ` - @{ Name = 'Name'; Expression = { $_.name } }, ` - @{ Name = 'UID'; Expression = { $_.uid } } + # now import it silently + Import-PackageProvider -Name NuGet -Force -ErrorAction SilentlyContinue | Out-Null - if (-not $siteList) { - Write-Warning "No sites were returned by the API." - return @() - } + # ensure trust PSGallery without its own output (so you don't get “untrusted repository” prompt + $gallery = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue + if ($gallery.InstallationPolicy -ne 'Trusted') { + Set-PSRepository ` + -Name PSGallery ` + -InstallationPolicy Trusted ` + -ErrorAction SilentlyContinue | Out-Null - return $siteList - } - catch { - Throw "Failed to fetch sites from API: $_" - } -} + Write-Host "PSGallery marked as Trusted" -ForegroundColor Green + } + + #endregion -# 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() -} + # ───────────────────────────────────────────────────────────────────────── + # 2) GLOBAL SETTINGS & HELPERS + # ───────────────────────────────────────────────────────────────────────── -# Core Write-Log function (advanced with event-log support) - function Write-LogHelper { + # Listening port for HTTP UI + $Port = 8082 + + # Configurable endpoints + $Global:DattoWebhookUrl = 'https://automate.svstools.ca/webhook/svsmspkit' + + #region Get-DattoApiCredentials + function Get-DattoApiCredentials { + [CmdletBinding()] + param ( + [Parameter(Mandatory)][string]$Password + ) + $headers = @{ "SVSMSPKit" = $Password } + try { + $resp = Invoke-RestMethod -Uri $Global:DattoWebhookUrl ` + -Headers $headers ` + -Method GET + return @{ + ApiUrl = $resp.ApiUrl + ApiKey = $resp.ApiKey + ApiSecretKey = $resp.ApiSecretKey + } + } + catch { + Write-LogHybrid "Failed to fetch API credentials: $($_.Exception.Message)" Error DattoAuth + return $null + } + } + + function Get-DattoRmmSites { [CmdletBinding()] param( - [Parameter(Mandatory)][string]$Message, - [ValidateSet("Info","Warning","Error","Success","General")][string]$Level = "Info", - [string]$TaskCategory = "GeneralTask", - [switch]$LogToEvent, [string]$EventSource="SVSMSP_Module", [string]$EventLog="Application", - [int]$CustomEventID, [string]$LogFile, [switch]$PassThru + [Parameter(Mandatory)] + [string] $Password, + + [Parameter()] + [string] $WebhookUrl = $Global:DattoWebhookUrl ) - # IDs & colors - $idMap = @{ Info=1000; Warning=2000; Error=3000; Success=4000; General=1000 } - $colMap= @{ Info="Cyan"; Warning="Yellow"; Error="Red"; Success="Green"; General="White" } - $EventID = if ($PSBoundParameters.CustomEventID) { $CustomEventID } else { $idMap[$Level] } - $color = $colMap[$Level] - $fmt = "[$Level] [$TaskCategory] $Message (Event ID: $EventID)" - Write-Host $fmt -ForegroundColor $color - # cache - if (-not $Global:LogCache) { $Global:LogCache = @() } - $entry = [pscustomobject]@{ Timestamp=(Get-Date -Format "yyyy-MM-dd HH:mm:ss"); Level=$Level; Message=$fmt } - $Global:LogCache += $entry - - # file - if ($PSBoundParameters.LogFile) { - try { "$($entry.Timestamp) $fmt" | Out-File $LogFile -Append -Encoding UTF8 } - catch { Write-Host "[Warning] File log failed: $_" -ForegroundColor Yellow } + # 1) Fetch Datto API credentials from your webhook + Write-Verbose "Fetching Datto API credentials from $WebhookUrl" + try { + $headers = @{ 'SVSMSPKit' = $Password } + $creds = Invoke-RestMethod -Uri $WebhookUrl -Headers $headers -Method GET + } + catch { + Throw "Failed to fetch credentials from webhook: $_" } - # event log - if ($LogToEvent) { - $etype = if ($Level -in 'Warning','Error') { $Level } else { 'Information' } - try { - if (-not (Get-EventLog -LogName $EventLog -Source $EventSource -ErrorAction SilentlyContinue)) { - New-EventLog -LogName $EventLog -Source $EventSource - } - $msg = "TaskCategory:$TaskCategory | Message:$Message" - Write-EventLog -LogName $EventLog -Source $EventSource -EntryType $etype -EventID $EventID -Message $msg - } catch { Write-Host "[Warning] EventLog failed: $_" -ForegroundColor Yellow } + $apiUrl = $creds.ApiUrl + $apiKey = $creds.ApiKey + $apiSecretKey = $creds.ApiSecretKey + + # 2) Request an OAuth token + Write-Verbose "Requesting OAuth token from $apiUrl/auth/oauth/token" + try { + $securePwd = ConvertTo-SecureString -String 'public' -AsPlainText -Force + $credObj = New-Object System.Management.Automation.PSCredential('public-client', $securePwd) + + $tokenResp = Invoke-RestMethod ` + -Uri "$apiUrl/auth/oauth/token" ` + -Credential $credObj ` + -Method 'POST' ` + -ContentType 'application/x-www-form-urlencoded' ` + -Body "grant_type=password&username=$apiKey&password=$apiSecretKey" + + $token = $tokenResp.access_token + } + catch { + Throw "Failed to obtain OAuth token: $_" } - if ($PassThru) { return $entry } -} - -# ───────────────────────────────────────────────────────────────────────── -# WRITE-LOG HYBRID (single definition, chooses at runtime) -# ───────────────────────────────────────────────────────────────────────── -function Write-LogHybrid { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)][string]$Message, - [ValidateSet("Info","Warning","Error","Success","General")] - [string]$Level = "Info", - [string]$TaskCategory = "GeneralTask", - [switch]$LogToEvent - ) - - if ( Get-Command -Name Write-Log -ErrorAction SilentlyContinue ) { - # SVSMSP module's Write-Log is available - Write-Log -Message $Message -Level $Level -TaskCategory $TaskCategory -LogToEvent:$LogToEvent - } - else { - # fall back to your helper - Write-LogHelper -Message $Message -Level $Level -TaskCategory $TaskCategory -LogToEvent:$LogToEvent - } -} - - -#endregion - - -# STACK = Scripted Tooling for Automated Client Kickoff -# MONKEY = Module-based Onboarding & Next-step Kickoff Engine Yoke -# Conveys the idea of coupling tasks together and keeping them under control. - -#region Config & Task Definitions - -# Define every task once here: -# Id → checkbox HTML `id` -# Name → URL path (`/Name`) -# Label → user-visible text -# HandlerFn → the PowerShell function to invoke -# Page → which tab/page it appears on - -$Global:Tasks = @( - # On-Boarding, left column - @{ Id='setSVSPowerplan'; Name='setSVSPowerplan'; Label='Set SVS Powerplan'; HandlerFn='Handle-setSVSPowerPlan'; Page='onboard'; Column='left' }, - @{ Id='installSVSMSPModule'; Name='installSVSMSPModule'; Label='Install SVSMSP Module'; HandlerFn='Handle-InstallSVSMSP'; Page='onboard'; Column='left' }, - @{ Id='installCyberQP'; Name='installCyberQP'; Label='Install CyberQP'; HandlerFn='Handle-InstallCyberQP'; Page='onboard'; Column='left' }, - @{ Id='installSVSHelpDesk'; Name='installSVSHelpDesk'; Label='Install SVS HelpDesk'; HandlerFn='Handle-InstallSVSHelpDesk'; Page='onboard'; Column='left' }, - @{ Id='installThreatLocker'; Name='installThreatLocker'; Label='Install ThreatLocker'; HandlerFn='Handle-InstallThreatLocker'; Page='onboard'; Column='left' }, - @{ Id='installRocketCyber'; Name='installRocketCyber'; Label='Install RocketCyber'; HandlerFn='Handle-InstallRocketCyber'; Page='onboard'; Column='left' }, - @{ Id='installDattoRMM'; Name='installDattoRMM'; Label='Install DattoRMM'; HandlerFn='Handle-InstallDattoRMM'; Page='onboard'; Column='left'; - SubOptions= @( - @{ Value='inputVar'; Label='Copy Site Variables' }, - @{ Value='rmm'; Label='Install RMM Agent' }, - @{ Value='exe'; Label='Download Executable' } - ) - }, - - - # On-Boarding, right column (optional bits) - @{ Id='enableBitLocker'; Name='EnableBitLocker'; Label='Enable BitLocker'; HandlerFn='Set-SVSBitLocker'; Page='onboard'; Column='right' }, - @{ Id='setEdgeDefaultSearch';Name='setedgedefaultsearch';Label='Set Edge Default Search'; Tooltip='Will configure Edge to use Google as default search provider'; HandlerFn='set-EdgeDefaultSearchProvider';Page='onboard'; Column='right' }, - - # Off-Boarding - @{ Id='uninstallCyberQP'; Name='uninstallCyberQP'; Label='Uninstall CyberQP'; HandlerFn='Uninstall-CyberQP'; Page='offboard' }, - @{ Id='uninstallSVSMSPModule'; Name='uninstallSVSMSPModule'; Label='Uninstall SVSMSP Module'; HandlerFn='Cleanup-SVSMSP'; Page='offboard' }, - - # Tweaks - @{ Id='disableAnimations'; Name='disableAnimations'; Label='Disable Animations'; HandlerFn='Disable-Animations'; Page='tweaks' }, - - # SVS Apps - @{ Id='wingetLastpass'; Name='wingetLastpass'; Label='LastPass Desktop App'; HandlerFn='Install-WingetLastPass'; Page='SVSApps' } -) - -#endregion - - -# If we got here, it's the UI set—launch browser + listener: - # ——— UI fallback starts here ——— - Write-LogHybrid "Launching UI" Info Startup - - -#region Handler Stubs - -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() -} - -# new helper to return JSON -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() -} - - -#region Install-DattoRMM-Helper -function Install-DattoRMM-Helper { - param ( - [string]$ApiUrl, - [string]$ApiKey, - [string]$ApiSecretKey, - [switch]$FetchSitesOnly, - [string]$SiteName, - [string]$SiteUID - ) - if (-not $ApiUrl -or -not $ApiKey -or -not $ApiSecretKey) { - Write-LogHybrid -Message "Missing required parameters. Please provide ApiUrl, ApiKey, and ApiSecretKey." -Level "Error" -LogToEvent - return - } - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Write-LogHybrid -Message "Fetching OAuth token..." -Level "Info" - try { - $securePassword = ConvertTo-SecureString -String 'public' -AsPlainText -Force - $apiGenToken = Invoke-WebRequest -Credential (New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ('public-client', $securePassword)) ` - -Uri ('{0}/auth/oauth/token' -f $ApiUrl) ` - -Method 'POST' ` - -ContentType 'application/x-www-form-urlencoded' ` - -Body ('grant_type=password&username={0}&password={1}' -f $ApiKey, $ApiSecretKey) ` - | ConvertFrom-Json - $requestToken = $apiGenToken.access_token - Write-LogHybrid -Message "OAuth token fetched successfully." -Level "Success" -LogToEvent - } catch { - Write-LogHybrid -Message "Failed to fetch OAuth token. Details: $($_.Exception.Message)" -Level "Error" -LogToEvent - return - } - $getHeaders = @{"Authorization" = "Bearer $requestToken"} - if ($FetchSitesOnly) { - Write-Host "Fetching list of sites from the Datto RMM API..." -ForegroundColor Cyan - try { - $getHeaders = @{"Authorization" = "Bearer $requestToken" } - $getSites = Invoke-WebRequest -Uri "$ApiUrl/api/v2/account/sites" -Method Get -Headers $getHeaders -ContentType "application/json" - $sitesJson = $getSites.Content | ConvertFrom-Json - $siteList = $sitesJson.sites | ForEach-Object { - [PSCustomObject]@{ - Name = $_.name - UID = $_.uid - } - } - Write-Host "Successfully fetched list of sites." -ForegroundColor Green - return $siteList - } - catch { - Write-Host "Failed to fetch sites from the API. Details: $($_.Exception.Message)" -ForegroundColor Red - return - } - } -} -#endregion - -#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" - # …your old cleanup logic here… - } - - function Perform-ToolkitInstallation { - Perform-Cleanup - Write-LogHybrid "Registering repo $NewRepositoryName…" "Info" "SVSModule" - if (-not (Get-PSRepository -Name $NewRepositoryName -ErrorAction SilentlyContinue)) { - Register-PSRepository -Name $NewRepositoryName -SourceLocation $NewRepositoryURL -InstallationPolicy Trusted - } - Write-LogHybrid "Installing module $NewModuleName…" "Info" "SVSModule" - Install-Module -Name $NewModuleName -Repository $NewRepositoryName -Scope AllUsers -Force - Write-LogHybrid "Toolkit installation complete." "Success" "SVSModule" - } - - Write-LogHybrid "Install-SVSMSP called" "Info" "SVSModule" - if ($Cleanup) { - Perform-Cleanup; return - } - if ($InstallToolkit) { - Perform-ToolkitInstallation; return - } - # default if no switch passed: - Perform-ToolkitInstallation -} - -#endregion - -# POST /getpw → read JSON body, call helper, return JSON - - -function Handle-FetchSites { - param($Context) - - # 1) Read incoming JSON (using block auto-disposes the reader) - - $reader = [IO.StreamReader]::new($Context.Request.InputStream) - try { - $raw = $reader.ReadToEnd() - } finally { - $reader.Close() - } - - try { - $pw = (ConvertFrom-Json $raw).password - } catch { - Write-LogHybrid "Invalid JSON in /getpw payload: $($_.Exception.Message)" "Error" "FetchSites" - returnRespondEmpty $Context - return - } - - # 2) Fetch your Datto API creds from the webhook - Write-LogHybrid "Calling webhook for Datto credentials…" "Info" "FetchSites" - -try { - $creds = Get-DattoApiCredentials -Password $pw - if (-not $creds) { - Write-LogHybrid "Webhook returned no credentials" Error FetchSites - returnRespondEmpty $Context 403 - return - } - - # reuse the same globals from the entrypoint - $Global:ApiUrl = $creds.ApiUrl - $Global:ApiKey = $creds.ApiKey - $Global:ApiSecretKey = $creds.ApiSecretKey - - Write-LogHybrid "Fetched and stored API credentials." Success FetchSites -} catch { - Write-LogHybrid "Credential-fetch error: $($_.Exception.Message)" Error FetchSites -LogToEvent - returnRespondEmpty $Context 500 - return -} - - - # 3) Exchange for a bearer token - Write-LogHybrid "Requesting OAuth token" "Info" "FetchSites" - try { - $securePublic = ConvertTo-SecureString 'public' -AsPlainText -Force - $creds = New-Object System.Management.Automation.PSCredential('public-client',$securePublic) - $tokenResp = Invoke-RestMethod ` - -Uri "$Global:ApiUrl/auth/oauth/token" ` - -Credential $creds ` - -Method Post ` - -ContentType 'application/x-www-form-urlencoded' ` - -Body "grant_type=password&username=$Global:ApiKey&password=$Global:ApiSecretKey" - $token = $tokenResp.access_token - Write-LogHybrid "OAuth token acquired." "Success" "FetchSites" - } catch { - Write-LogHybrid "OAuth request failed: $($_.Exception.Message)" "Error" "FetchSites" - returnRespondEmpty $Context 500 - return - } - - # 4) Pull the site list - Write-LogHybrid "Fetching Datto RMM site list" "Info" "FetchSites" - try { - $hdr = @{ Authorization = "Bearer $token" } - $sitesResp = Invoke-RestMethod -Uri "$Global:ApiUrl/api/v2/account/sites" ` - -Method Get ` - -Headers $hdr ` - -ContentType 'application/json' - - $siteList = $sitesResp.sites | ForEach-Object { - [PSCustomObject]@{ Name = $_.name; UID = $_.uid } - } - Write-LogHybrid "Site list retrieved ($($siteList.Count) sites)." "Success" "FetchSites" - } catch { - Write-LogHybrid "Failed to fetch site list: $($_.Exception.Message)" "Error" "FetchSites" - returnRespondEmpty $Context 500 - return - } - - # 5) Return JSON array - $json = $siteList | ConvertTo-Json -Depth 2 - $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() -} - - - -# Helper function to consistently return an empty JSON array -function returnRespondEmpty { - param( - [Parameter(Mandatory)][object]$Context, - [Parameter(Mandatory)][ValidateRange(100,599)][int]$StatusCode = 500 - ) - # Always return an empty JSON array body - $empty = [Text.Encoding]::UTF8.GetBytes("[]") - - # Set the desired status code and headers - $Context.Response.StatusCode = $StatusCode - $Context.Response.ContentType = 'application/json' - $Context.Response.ContentLength64 = $empty.Length - - # Write and close - $Context.Response.OutputStream.Write($empty, 0, $empty.Length) - $Context.Response.OutputStream.Close() -} - - - -# On-boarding handlers -function Handle-SetSVSPowerPlan { - param($Context) - - # 1) call into your module - Set-SVSPowerPlan - - # 2) log & write back a simple text response - Write-LogHybrid "PowerPlan set" "Success" "OnBoard" - Respond-Text $Context "PowerPlan applied" -} - -function Handle-InstallSVSMSP { - param($Context) - Write-LogHybrid "HTTP trigger: Handle-InstallSVSMSP" "Info" "OnBoard" - try { - Install-SVSMSP -InstallToolkit - Respond-Text $Context "SVSMSP Module installed/updated." - } catch { - Write-LogHybrid "Error in Install-SVSMSP: $_" "Error" "OnBoard" - Respond-Text $Context "ERROR: $_" - } -} - -function Handle-InstallCyberQP { - param($Context) - - # 1) call into your module - Install-CyberQP - - # 2) log & write back a simple text response - Write-LogHybrid "CyberQP installed" "Success" "OnBoard" - Respond-Text $Context "CyberQP installed" -} - -function Handle-InstallThreatLocker { - param($Context) - - # 1) call into your module - Install-ThreatLocker - - # 2) log & write back a simple text response - Write-LogHybrid "ThreatLocker installed" "Success" "OnBoard" - Respond-Text $Context "ThreatLocker installed" -} - -function Handle-InstallRocketCyber { - param($Context) - - # 1) call into your module - Install-RocketCyber - - # 2) log & write back a simple text response - Write-LogHybrid "RocketCyber installed" "Success" "OnBoard" - Respond-Text $Context "RocketCyber installed" -} - -function Handle-InstallSVSHelpDesk { - param($Context) - - # 1) call into your module - Install-SVSHelpDesk - - # 2) log & write back a simple text response - Write-LogHybrid "SVS HelpDesk installed" "Success" "OnBoard" - Respond-Text $Context "SVS HelpDesk installed" -} - -function Handle-InstallDattoRMM { - param($Context) - $req = $Context.Request - $resp = $Context.Response - - if ($req.HttpMethod -ne 'POST') { - $resp.StatusCode = 405; $resp.ContentType = 'text/plain' - $resp.OutputStream.Write([Text.Encoding]::UTF8.GetBytes('Use POST'),0,7) - $resp.OutputStream.Close(); return - } - - # parse JSON body - $body = (New-Object IO.StreamReader $req.InputStream).ReadToEnd() - $data = $body | ConvertFrom-Json - $checked = $data.checkedValues - $uid = $data.UID - $name = $data.Name - - try { - Install-DattoRMM ` - -ApiUrl $Global:ApiUrl ` - -ApiKey $Global:ApiKey ` - -ApiSecretKey $Global:ApiSecretKey ` - -SiteUID $uid ` - -SiteName $name ` - -PushSiteVars:($checked -contains 'inputVar') ` - -InstallRMM: ($checked -contains 'rmm') ` - -SaveCopy: ($checked -contains 'exe') - - Write-LogHybrid "RMM install triggered for $name" "Success" "DattoRMM" - $resp.StatusCode = 200 - $responseString = "Triggered DattoRMM for $name" - } - catch { - Write-LogHybrid "Error in Install-DattoRMM: $_" "Error" "DattoRMM" - $resp.StatusCode = 500 - $responseString = "ERROR: $($_.Exception.Message)" - } - - $b = [Text.Encoding]::UTF8.GetBytes($responseString) - $resp.ContentType = 'text/plain' - $resp.ContentLength64 = $b.Length - $resp.OutputStream.Write($b,0,$b.Length) - $resp.OutputStream.Close() -} - - -# Off-boarding handlers -function Handle-UninstallCyberQP { - param($Context) - - # 1) call into your module - Uninstall-CyberQP - - Write-LogHybrid "CyberQP uninstalled" "Success" "OffBoard" - Respond-Text $Context "CyberQP uninstalled" -} - -function Cleanup-SVSMSP { - param($Context) - Write-LogHybrid "SVSMSP cleaned up" "Success" "OffBoard" - Respond-Text $Context "SVSMSP cleaned up" -} - -# Tweaks handler -function Disable-Animations { - param($Context) - Write-LogHybrid "Animations disabled" "Success" "Tweaks" - Respond-Text $Context "Animations disabled" -} - -# SVSApps handler -function Install-WingetLastPass { - param($Context) - Write-LogHybrid "Winget LastPass installed" "Success" "SVSApps" - Respond-Text $Context "Winget LastPass installed" -} - -#endregion - -#region UI Generation - -function Build-Checkboxes { - param($Page, $Column) - - ( - $Global:Tasks | - Where-Object Page -EQ $Page | - Where-Object Column -EQ $Column | - ForEach-Object { - $taskId = $_.Id - $tooltip = if ($_.PSObject.Properties.Name -contains 'Tooltip' -and $_.Tooltip) { - " title='$($_.Tooltip)'" - } else { '' } - - $html = "" - - if ($_.SubOptions) { - # join inside the code block is fine - $subHtml = ( - $_.SubOptions | - ForEach-Object { - "" - } - ) -join "`n" - - $html += @" -
-"@ - } - - $html - } - ) -join "`n" -} - - - -### Get SVSMSP module version to display in the UI -function Get-ModuleVersionHtml { - $mod = Get-Module -ListAvailable -Name SVSMSP | Sort-Object Version -Descending | Select-Object -First 1 - if ($mod) { - return "