From 50fee51725d64cb679a0f1b8ce15ee829d639fe0 Mon Sep 17 00:00:00 2001 From: Stephan Yelle Date: Wed, 2 Jul 2025 21:25:19 -0400 Subject: [PATCH] added install-dattormm and folded multiple function into it --- StackMonkey_Beta.ps1 | 734 ++++++++++++++++++------------------------- 1 file changed, 300 insertions(+), 434 deletions(-) diff --git a/StackMonkey_Beta.ps1 b/StackMonkey_Beta.ps1 index 838734b..05de089 100644 --- a/StackMonkey_Beta.ps1 +++ b/StackMonkey_Beta.ps1 @@ -97,19 +97,23 @@ # ───────────────────────────────────────────────────────── # Datto headless mode - # Both Datto sets share the webhook password - # Shared webhook password for both Datto modes + # ─── DattoFetch & DattoInstall share the webhook creds ───────────── [Parameter(Mandatory,ParameterSetName='DattoFetch')] [Parameter(Mandatory,ParameterSetName='DattoInstall')] - [string]$N8nPassword, - - # ───────────────────────────────────────────────────────── - # Fetch only set write sites and exit - [Parameter(ParameterSetName='DattoFetch')][switch] $SaveSitesOnly, - [Parameter(ParameterSetName='DattoFetch')][string] $OutputFile = 'datto_sites.csv', + [switch]$UseWebhook, - # ───────────────────────────────────────────────────────── - # Install set: target site must be provided + [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$','i')][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, @@ -175,101 +179,7 @@ #endregion SVS Module - #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 - } - } - - #endregion Get-DattoApiCredentials - - #region Get-DattoRMMSites - function Get-DattoRmmSites { - [CmdletBinding()] - param( - [Parameter(Mandatory)] - [string] $Password, - - [Parameter()] - [string] $WebhookUrl = $Global:DattoWebhookUrl - ) - - # 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: $_" - } - - $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: $_" - } - - # 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' - - $siteList = $sitesResp.sites | Select-Object ` - @{ Name = 'Name'; Expression = { $_.name } }, ` - @{ Name = 'UID'; Expression = { $_.uid } } - - if (-not $siteList) { - Write-Warning "No sites were returned by the API." - return @() - } - - return $siteList - } - catch { - Throw "Failed to fetch sites from API: $_" - } - } - #endregion Get-DattoRMMSites + #region Write-Log @@ -1083,7 +993,6 @@ $script $Context.Response.OutputStream.Close() } - # new helper to return JSON function Respond-JSON { param($Context, $Object) $json = $Object | ConvertTo-Json -Depth 5 @@ -1095,266 +1004,289 @@ $script } 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') + # 1) Read the incoming JSON payload (contains only the webhook password) + $raw = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd() + $pw = (ConvertFrom-Json $raw).password - Write-LogHybrid "RMM install triggered for $name" "Success" "DattoRMM" - $resp.StatusCode = 200 - $responseString = "Triggered DattoRMM for $name" + # 2) Delegate to your unified function + $sites = Install-DattoRMM ` + -UseWebhook ` + -WebhookPassword $pw ` + -FetchSites ` + -SaveSitesList:$SaveSitesList ` + -OutputFile $OutputFile + + # 3) Return JSON array of sites + Respond-JSON $Context $sites } catch { - Write-LogHybrid "Error in Install-DattoRMM: $_" "Error" "DattoRMM" - $resp.StatusCode = 500 - $responseString = "ERROR: $($_.Exception.Message)" + # Log the exception and return HTTP 500 + Write-LogHybrid "Handle-FetchSites error: $($_.Exception.Message)" Error DattoRMM + $Context.Response.StatusCode = 500 + Respond-Text $Context "Internal server error fetching sites." + } + } + +function Handle-InstallDattoRMM { + param($Context) + + try { + if ($Context.Request.HttpMethod -ne 'POST') { + $Context.Response.StatusCode = 405 + Respond-Text $Context 'Use POST' + return + } + + # 1) Read and parse the JSON body + $body = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd() + $data = ConvertFrom-Json $body + + # 2) Delegate to your unified function for the install + 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') + + # 3) Acknowledge to the client + Respond-Text $Context "Triggered DattoRMM for $($data.Name)" + } + catch { + # Log the exception and return HTTP 500 + Write-LogHybrid "Handle-InstallDattoRMM error: $($_.Exception.Message)" Error DattoRMM + $Context.Response.StatusCode = 500 + Respond-Text $Context "Internal server error during DattoRMM install." + } +} + #endregion Handler Stubs + + #region Install-DattoRMM + + <# +.SYNOPSIS + Installs/configures the Datto RMM agent, fetches site lists, and optionally saves the site list to disk. + +.DESCRIPTION + Centralizes Datto RMM operations in one function: + - Fetch API credentials from a webhook (-UseWebhook) + - Acquire OAuth token + - Fetch site list (-FetchSites) + - Save site list to Desktop as JSON or CSV (-FetchSites + -SaveSitesList) + - Write site variables to registry (-PushSiteVars) + - Download & launch the RMM agent installer (-InstallRMM) + - Save a copy of the installer (-SaveCopy) + +.PARAMETER UseWebhook + Fetches ApiUrl, ApiKey, and ApiSecretKey from the webhook when used with WebhookPassword. + +.PARAMETER WebhookPassword + Password for authenticating to the credentials webhook. + +.PARAMETER WebhookUrl + URL of the credentials webhook. Defaults to $Global:DattoWebhookUrl. + +.PARAMETER ApiUrl + Direct Datto API endpoint URL (if not using webhook). + +.PARAMETER ApiKey + Direct Datto API key (if not using webhook). + +.PARAMETER ApiSecretKey + Direct Datto API secret (if not using webhook). + +.PARAMETER FetchSites + Fetches the list of sites and skips all install steps. + +.PARAMETER SaveSitesList + Saves the fetched site list to Desktop using OutputFile. Must be used with -FetchSites. + +.PARAMETER OutputFile + Filename for saving the site list (.json or .csv). Defaults to 'datto_sites.csv'. + +.PARAMETER PushSiteVars + Writes fetched site variables into HKLM:\Software\SVS\Deployment. + +.PARAMETER InstallRMM + Downloads and runs the Datto RMM agent installer. + +.PARAMETER SaveCopy + Saves a copy of the downloaded agent installer to C:\Temp. + +.PARAMETER SiteUID + Unique identifier of the Datto site (required for install and registry push). + +.PARAMETER SiteName + Friendly name of the Datto site (used for logging). + +.EXAMPLE + # Fetch and save site list via webhook + Install-DattoRMM -UseWebhook -WebhookPassword 'Tndmeeisdwge!' -FetchSites -SaveSitesList -OutputFile 'sites.csv' + +.EXAMPLE + # Headless install with site variables + Install-DattoRMM -ApiUrl 'https://api.example.com' -ApiKey 'KeyHere' -ApiSecretKey 'SecretHere' \ + -SiteUID 'site-123' -SiteName 'Acme Corp' -PushSiteVars -InstallRMM + +.EXAMPLE + # Download and save installer to C:\Temp without installing + Install-DattoRMM -ApiUrl 'https://api.example.com' -ApiKey 'KeyHere' -ApiSecretKey 'SecretHere' \ + -SiteUID 'site-123' -SiteName 'Acme Corp' -SaveCopy +#> +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 + ) + + # Validate mutually-dependent switches + if ($SaveSitesList -and -not $FetchSites) { + Write-LogHybrid "-SaveSitesList requires -FetchSites." Error DattoRMM; return } - $b = [Text.Encoding]::UTF8.GetBytes($responseString) - $resp.ContentType = 'text/plain' - $resp.ContentLength64 = $b.Length - $resp.OutputStream.Write($b,0,$b.Length) - $resp.OutputStream.Close() - } + # 1) Optionally fetch credentials from webhook + if ($UseWebhook) { + if (-not $WebhookPassword) { + Write-LogHybrid "Webhook password missing." Error DattoRMM; 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 + } catch { + Write-LogHybrid "Failed to fetch webhook credentials: $($_.Exception.Message)" Error DattoRMM; return + } + } + # 2) Validate API parameters + if (-not $ApiUrl -or -not $ApiKey -or -not $ApiSecretKey) { + Write-LogHybrid "Missing required API parameters." Error DattoRMM; return + } - # Off-boarding handlers - function Handle-UninstallCyberQP { - param($Context) + # 3) Acquire OAuth token + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + try { + $publicCred = New-Object System.Management.Automation.PSCredential( + 'public-client', (ConvertTo-SecureString 'public' -AsPlainText -Force) + ) + $tokenResp = Invoke-RestMethod -Uri "$ApiUrl/auth/oauth/token" ` + -Credential $publicCred ` + -Method Post ` + -ContentType 'application/x-www-form-urlencoded' ` + -Body "grant_type=password&username=$ApiKey&password=$ApiSecretKey" + $token = $tokenResp.access_token + Write-LogHybrid "OAuth token acquired." Success DattoRMM + } catch { + Write-LogHybrid "OAuth token fetch failed: $($_.Exception.Message)" Error DattoRMM; return + } + $headers = @{ Authorization = "Bearer $token" } - # 1) call into your module - Uninstall-CyberQP + # 4) Fetch site list only + if ($FetchSites) { + try { + $sitesResp = Invoke-RestMethod -Uri "$ApiUrl/api/v2/account/sites" -Method Get -Headers $headers + $siteList = $sitesResp.sites | ForEach-Object { + [PSCustomObject]@{ Name = $_.name; UID = $_.uid } + } + Write-LogHybrid "Fetched $($siteList.Count) sites." Success DattoRMM - Write-LogHybrid "CyberQP uninstalled" "Success" "OffBoard" - Respond-Text $Context "CyberQP uninstalled" - } + if ($SaveSitesList) { + $desktop = [Environment]::GetFolderPath('Desktop') + $path = Join-Path $desktop $OutputFile + $ext = [IO.Path]::GetExtension($OutputFile).ToLower() + if ($ext -eq '.json') { + $siteList | ConvertTo-Json -Depth 3 | Out-File -FilePath $path -Encoding UTF8 + } else { + $siteList | Export-Csv -Path $path -NoTypeInformation -Encoding UTF8 + } + Write-LogHybrid "Wrote $($siteList.Count) sites to $path" Success DattoRMM + } - function Cleanup-SVSMSP { - param($Context) - Write-LogHybrid "SVSMSP cleaned up" "Success" "OffBoard" - Respond-Text $Context "SVSMSP cleaned up" - } + return $siteList + } catch { + Write-LogHybrid "Failed to fetch sites: $($_.Exception.Message)" Error DattoRMM; return @() + } + } - # Tweaks handler - function Disable-Animations { - param($Context) - Write-LogHybrid "Animations disabled" "Success" "Tweaks" - Respond-Text $Context "Animations disabled" - } + # 5) Push site variables to registry + if ($PushSiteVars) { + try { + $varsResp = Invoke-RestMethod -Uri "$ApiUrl/api/v2/site/$SiteUID/variables" -Method Get -Headers $headers + Write-LogHybrid "Fetched variables for '$SiteName'." Success DattoRMM + } catch { + Write-LogHybrid "Variable fetch failed: $($_.Exception.Message)" Error DattoRMM + } + $regPath = "HKLM:\Software\SVS\Deployment" + foreach ($v in $varsResp.variables) { + try { + if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null } + New-ItemProperty -Path $regPath -Name $v.name -Value $v.value -PropertyType String -Force | Out-Null + Write-LogHybrid "Wrote '$($v.name)' to registry." Success DattoRMM + } catch { + Write-LogHybrid "Failed to write '$($v.name)': $($_.Exception.Message)" Error DattoRMM + } + } + } - # SVSApps handler - function Install-WingetLastPass { - param($Context) - Write-LogHybrid "Winget LastPass installed" "Success" "SVSApps" - Respond-Text $Context "Winget LastPass installed" - } + # 6) Download & install RMM agent + if ($InstallRMM) { + if ($PSCmdlet.ShouldProcess("Site '$SiteName'", "Install RMM agent")) { + try { + $dlUrl = "https://zinfandel.centrastage.net/csm/profile/downloadAgent/$SiteUID" + $tmp = "$env:TEMP\AgentInstall.exe" + Invoke-WebRequest -Uri $dlUrl -OutFile $tmp -UseBasicParsing + Write-LogHybrid "Downloaded agent to $tmp." Info DattoRMM + Start-Process -FilePath $tmp -NoNewWindow + Write-LogHybrid "RMM agent installer launched." Success DattoRMM + } catch { + Write-LogHybrid "Agent install failed: $($_.Exception.Message)" Error DattoRMM + } + } + } - #endregion Handler Stubs + # 7) Save a copy of installer to C:\Temp + if ($SaveCopy) { + try { + $dlUrl = "https://zinfandel.centrastage.net/csm/profile/downloadAgent/$SiteUID" + $path = "C:\Temp\AgentInstall.exe" + if (-not (Test-Path 'C:\Temp')) { New-Item -Path 'C:\Temp' -ItemType Directory | Out-Null } + Invoke-WebRequest -Uri $dlUrl -OutFile $path -UseBasicParsing + Write-LogHybrid "Saved installer copy to $path." Info DattoRMM + } catch { + Write-LogHybrid "Save-copy failed: $($_.Exception.Message)" Error DattoRMM + } + } + + # 8) Warn if no action was taken + if (-not ($PushSiteVars -or $InstallRMM -or $SaveCopy)) { + Write-LogHybrid "No action specified. Use -FetchSites, -SaveSitesList, -PushSiteVars, -InstallRMM, or -SaveCopy." Warning DattoRMM + } +} + + + #endregion Install-DattoRMM #region Dispatch-Request @@ -1426,30 +1358,16 @@ $script 'DattoFetch' { Write-LogHybrid "Fetching site list only…" Info DattoAuth - $sites = Get-DattoRmmSites -Password $N8nPassword + $sites = Install-DattoRMM ` + -UseWebhook ` + -WebhookPassword $WebhookPassword ` + -FetchSites ` + -SaveSitesList:$SaveSitesList ` + -OutputFile $OutputFile - if ($SaveSitesOnly) { - # If SaveSitesOnly is true, save the output to a file on desktop - $desktopPath = [System.Environment]::GetFolderPath('Desktop') - $filePath = Join-Path -Path $desktopPath -ChildPath $OutputFile - - $ext = [IO.Path]::GetExtension($OutputFile).ToLower() - if ($ext -eq '.json') { - # Save the file to the desktop using the full path - $sites | ConvertTo-Json -Depth 3 | Out-File -FilePath $filePath -Encoding UTF8 - } else { - # Save the file to the desktop using the full path - $sites | Export-Csv -Path $filePath -NoTypeInformation -Encoding UTF8 - } - - Write-LogHybrid "Wrote $($sites.Count) sites to $filePath" Success DattoAuth - - } else { - # If SaveSitesOnly is not true, just fetch sites (for UI purposes or silent mode without saving) - Write-LogHybrid "Sites fetched successfully, but not saved." Success DattoAuth - } - return - } + Write-LogHybrid "Done." Success DattoAuth + return + } # ──────────────────────────────────────────── @@ -1458,16 +1376,16 @@ $script 'DattoInstall' { Write-LogHybrid "Headless DattoRMM deploy" Info DattoAuth + if ($PSCmdlet.ShouldProcess("Datto site '$SiteName'", "Headless install")) { Install-DattoRMM ` - -ApiUrl $Global:ApiUrl ` - -ApiKey $Global:ApiKey ` - -ApiSecretKey $Global:ApiSecretKey ` - -SiteUID $SiteUID ` - -SiteName $SiteName ` - -PushSiteVars:$PushSiteVars ` - -InstallRMM:$InstallRMM ` - -SaveCopy:$SaveCopy + -UseWebhook ` + -WebhookPassword $WebhookPassword ` + -SiteUID $SiteUID ` + -SiteName $SiteName ` + -PushSiteVars:$PushSiteVars ` + -InstallRMM:$InstallRMM ` + -SaveCopy:$SaveCopy } return @@ -1532,59 +1450,7 @@ $script #endregion - #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 HTTP Listener & Routing