#region Config & Task Definitions # Listening port for HTTP UI $Port = 8082 # 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='Set-SVSPowerPlan'; Page='onboard'; Column='left' }, @{ Id='installSVSMSPModule'; Name='installSVSMSPModule'; Label='Install SVSMSP Module'; HandlerFn='Install-SVSMSPModule'; Page='onboard'; Column='left' }, @{ Id='installCyberQP'; Name='installCyberQP'; Label='Install CyberQP'; HandlerFn='Install-CyberQP'; Page='onboard'; Column='left' }, @{ Id='installSplashtop'; Name='installSplashtop'; Label='Install Splashtop'; HandlerFn='Install-Splashtop'; Page='onboard'; Column='left' }, @{ Id='installSVSHelpDesk'; Name='installSVSHelpDesk'; Label='Install SVS HelpDesk'; HandlerFn='Install-SVSHelpDesk'; Page='onboard'; Column='left' }, @{ Id='installThreatLocker'; Name='installThreatLocker'; Label='Install ThreatLocker'; HandlerFn='Install-ThreatLocker'; Page='onboard'; Column='left' }, @{ Id='installRocketCyber'; Name='installRocketCyber'; Label='Install RocketCyber'; HandlerFn='Install-RocketCyber'; Page='onboard'; Column='left' }, @{ Id='installDattoRMM'; Name='installDattoRMM'; Label='Install DattoRMM'; HandlerFn='Handle-InstallRMM'; Page='onboard'; Column='left'; SubOptions= @( @{ Value='inputVar'; Label='Copy Site Variables' }, @{ Value='rmm'; Label='Install DRMM Agent' }, @{ Value='exe'; Label='Download the 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';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 #region Logging Helpers # 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() } # Core Write-Log function (advanced with event-log support) 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 = "SVSMSP_Module", [string]$EventLog = "Application", [int]$CustomEventID ) $EventID = @{ Info=1000; Warning=2000; Error=3000; Success=4000; General=1000 }[$Level] $Icon = @{ Info="📝"; Warning="⚠️"; Error="❌"; Success="✅"; General="📦" }[$Level] $logEntry = [PSCustomObject]@{ Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") Level = $Level Message = "$Icon [$Level] [$TaskCategory] $Message (EventID:$EventID)" } [void]$Global:LogCache.Add($logEntry) if ($LogToEvent) { try { if (-not (Get-EventLog -LogName $EventLog -Source $EventSource -ErrorAction SilentlyContinue)) { New-EventLog -LogName $EventLog -Source $EventSource -ErrorAction SilentlyContinue } Write-EventLog -LogName $EventLog -Source $EventSource ` -EntryType $Level -EventId $EventID ` -Message $Message } catch { Write-Host "⚠️ [EventLog] Failed to write: $($_.Exception.Message)" -ForegroundColor Yellow } } } # Hybrid wrapper: uses your module's Write-Log if available, else falls back if (Get-Command Write-Log -ErrorAction SilentlyContinue) { function Write-LogHybrid { param($Message,$Level,$TaskCategory,$LogToEvent) Write-Log @PSBoundParameters } } else { function Write-LogHybrid { param($Message,$Level,$TaskCategory,$LogToEvent) Write-LogHelper @PSBoundParameters } } #endregion #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 # POST /getpw → read JSON body, call helper, return JSON function Handle-FetchSites { param($Context) Write-Host "[Debug] Handle-FetchSites invoked" # ← add this try { $body = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd() $pw = (ConvertFrom-Json $body).password $sites = Install-DattoRMM-Helper ` -ApiUrl $ApiUrl ` -ApiKey $ApiKey ` -ApiSecretKey $ApiSecretKey ` -FetchSitesOnly if (-not $sites) { $Context.Response.StatusCode = 500; $sites = @() } $json = $sites | ConvertTo-Json -Depth 2 $bytes = [Text.Encoding]::UTF8.GetBytes($json) $Context.Response.ContentType = 'application/json' $Context.Response.OutputStream.Write($bytes,0,$bytes.Length) } catch { $Context.Response.StatusCode = 500 $Context.Response.ContentType = 'application/json' $Context.Response.OutputStream.Write([Text.Encoding]::UTF8.GetBytes('[]'),0,2) } finally { $Context.Response.OutputStream.Close() } } # On-boarding handlers function Set-SVSPowerPlan { param($Context) Write-LogHybrid "PowerPlan set" "Success" "OnBoard" Respond-Text $Context "Powerplan applied" } function Install-SVSMSPModule { param($Context) Write-LogHybrid "SVSMSP Module installed" "Success" "OnBoard" Respond-Text $Context "SVSMSP Module installed" } function Install-CyberQP { param($Context) Write-LogHybrid "CyberQP installed" "Success" "OnBoard" Respond-Text $Context "CyberQP installed" } # Off-boarding handlers function Uninstall-CyberQP { param($Context) 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 $html = "" if ($_.SubOptions) { Write-Host "👉 Rendering SubOptions for task $taskId" $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 "