From 820140655e9402c5f06c5b54376d484972950d0b Mon Sep 17 00:00:00 2001 From: Stephan Yelle Date: Sun, 29 Jun 2025 16:38:22 -0400 Subject: [PATCH] test --- StackMonkey.ps1 | 533 ++++++++++++++++++++++++------------------------ 1 file changed, 263 insertions(+), 270 deletions(-) diff --git a/StackMonkey.ps1 b/StackMonkey.ps1 index 257bdd1..b73ef0c 100644 --- a/StackMonkey.ps1 +++ b/StackMonkey.ps1 @@ -1037,6 +1037,268 @@ $script $Context.Response.OutputStream.Close() } + 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 + # Sends the HTML for a given page or invokes a task handler function Dispatch-Request { param($Context) @@ -1312,276 +1574,7 @@ $script # 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 - - - - #endregion - - - + #region HTTP Listener & Routing