added install-dattormm and folded multiple function into it
This commit is contained in:
@@ -97,19 +97,23 @@
|
|||||||
# ─────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────
|
||||||
# Datto headless mode
|
# Datto headless mode
|
||||||
|
|
||||||
# Both Datto sets share the webhook password
|
# ─── DattoFetch & DattoInstall share the webhook creds ─────────────
|
||||||
# Shared webhook password for both Datto modes
|
|
||||||
[Parameter(Mandatory,ParameterSetName='DattoFetch')]
|
[Parameter(Mandatory,ParameterSetName='DattoFetch')]
|
||||||
[Parameter(Mandatory,ParameterSetName='DattoInstall')]
|
[Parameter(Mandatory,ParameterSetName='DattoInstall')]
|
||||||
[string]$N8nPassword,
|
[switch]$UseWebhook,
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────
|
|
||||||
# Fetch only set write sites and exit
|
|
||||||
[Parameter(ParameterSetName='DattoFetch')][switch] $SaveSitesOnly,
|
|
||||||
[Parameter(ParameterSetName='DattoFetch')][string] $OutputFile = 'datto_sites.csv',
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────
|
[Parameter(Mandatory,ParameterSetName='DattoFetch')]
|
||||||
# Install set: target site must be provided
|
[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] $SiteUID,
|
||||||
[Parameter(Mandatory,ParameterSetName='DattoInstall')][string] $SiteName,
|
[Parameter(Mandatory,ParameterSetName='DattoInstall')][string] $SiteName,
|
||||||
[Parameter(ParameterSetName='DattoInstall')][switch] $PushSiteVars,
|
[Parameter(ParameterSetName='DattoInstall')][switch] $PushSiteVars,
|
||||||
@@ -175,101 +179,7 @@
|
|||||||
|
|
||||||
#endregion SVS Module
|
#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
|
#region Write-Log
|
||||||
|
|
||||||
@@ -1083,7 +993,6 @@ $script
|
|||||||
$Context.Response.OutputStream.Close()
|
$Context.Response.OutputStream.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
# new helper to return JSON
|
|
||||||
function Respond-JSON {
|
function Respond-JSON {
|
||||||
param($Context, $Object)
|
param($Context, $Object)
|
||||||
$json = $Object | ConvertTo-Json -Depth 5
|
$json = $Object | ConvertTo-Json -Depth 5
|
||||||
@@ -1095,266 +1004,289 @@ $script
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Handle-FetchSites {
|
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)
|
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 {
|
try {
|
||||||
Install-DattoRMM `
|
# 1) Read the incoming JSON payload (contains only the webhook password)
|
||||||
-ApiUrl $Global:ApiUrl `
|
$raw = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd()
|
||||||
-ApiKey $Global:ApiKey `
|
$pw = (ConvertFrom-Json $raw).password
|
||||||
-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"
|
# 2) Delegate to your unified function
|
||||||
$resp.StatusCode = 200
|
$sites = Install-DattoRMM `
|
||||||
$responseString = "Triggered DattoRMM for $name"
|
-UseWebhook `
|
||||||
|
-WebhookPassword $pw `
|
||||||
|
-FetchSites `
|
||||||
|
-SaveSitesList:$SaveSitesList `
|
||||||
|
-OutputFile $OutputFile
|
||||||
|
|
||||||
|
# 3) Return JSON array of sites
|
||||||
|
Respond-JSON $Context $sites
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-LogHybrid "Error in Install-DattoRMM: $_" "Error" "DattoRMM"
|
# Log the exception and return HTTP 500
|
||||||
$resp.StatusCode = 500
|
Write-LogHybrid "Handle-FetchSites error: $($_.Exception.Message)" Error DattoRMM
|
||||||
$responseString = "ERROR: $($_.Exception.Message)"
|
$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)
|
# 1) Optionally fetch credentials from webhook
|
||||||
$resp.ContentType = 'text/plain'
|
if ($UseWebhook) {
|
||||||
$resp.ContentLength64 = $b.Length
|
if (-not $WebhookPassword) {
|
||||||
$resp.OutputStream.Write($b,0,$b.Length)
|
Write-LogHybrid "Webhook password missing." Error DattoRMM; return
|
||||||
$resp.OutputStream.Close()
|
}
|
||||||
}
|
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
|
# 3) Acquire OAuth token
|
||||||
function Handle-UninstallCyberQP {
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
param($Context)
|
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
|
# 4) Fetch site list only
|
||||||
Uninstall-CyberQP
|
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"
|
if ($SaveSitesList) {
|
||||||
Respond-Text $Context "CyberQP uninstalled"
|
$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 {
|
return $siteList
|
||||||
param($Context)
|
} catch {
|
||||||
Write-LogHybrid "SVSMSP cleaned up" "Success" "OffBoard"
|
Write-LogHybrid "Failed to fetch sites: $($_.Exception.Message)" Error DattoRMM; return @()
|
||||||
Respond-Text $Context "SVSMSP cleaned up"
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Tweaks handler
|
# 5) Push site variables to registry
|
||||||
function Disable-Animations {
|
if ($PushSiteVars) {
|
||||||
param($Context)
|
try {
|
||||||
Write-LogHybrid "Animations disabled" "Success" "Tweaks"
|
$varsResp = Invoke-RestMethod -Uri "$ApiUrl/api/v2/site/$SiteUID/variables" -Method Get -Headers $headers
|
||||||
Respond-Text $Context "Animations disabled"
|
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
|
# 6) Download & install RMM agent
|
||||||
function Install-WingetLastPass {
|
if ($InstallRMM) {
|
||||||
param($Context)
|
if ($PSCmdlet.ShouldProcess("Site '$SiteName'", "Install RMM agent")) {
|
||||||
Write-LogHybrid "Winget LastPass installed" "Success" "SVSApps"
|
try {
|
||||||
Respond-Text $Context "Winget LastPass installed"
|
$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
|
#region Dispatch-Request
|
||||||
|
|
||||||
@@ -1426,30 +1358,16 @@ $script
|
|||||||
|
|
||||||
'DattoFetch' {
|
'DattoFetch' {
|
||||||
Write-LogHybrid "Fetching site list only…" Info DattoAuth
|
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) {
|
Write-LogHybrid "Done." Success DattoAuth
|
||||||
# If SaveSitesOnly is true, save the output to a file on desktop
|
return
|
||||||
$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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ────────────────────────────────────────────
|
# ────────────────────────────────────────────
|
||||||
@@ -1458,16 +1376,16 @@ $script
|
|||||||
|
|
||||||
'DattoInstall' {
|
'DattoInstall' {
|
||||||
Write-LogHybrid "Headless DattoRMM deploy" Info DattoAuth
|
Write-LogHybrid "Headless DattoRMM deploy" Info DattoAuth
|
||||||
|
|
||||||
if ($PSCmdlet.ShouldProcess("Datto site '$SiteName'", "Headless install")) {
|
if ($PSCmdlet.ShouldProcess("Datto site '$SiteName'", "Headless install")) {
|
||||||
Install-DattoRMM `
|
Install-DattoRMM `
|
||||||
-ApiUrl $Global:ApiUrl `
|
-UseWebhook `
|
||||||
-ApiKey $Global:ApiKey `
|
-WebhookPassword $WebhookPassword `
|
||||||
-ApiSecretKey $Global:ApiSecretKey `
|
-SiteUID $SiteUID `
|
||||||
-SiteUID $SiteUID `
|
-SiteName $SiteName `
|
||||||
-SiteName $SiteName `
|
-PushSiteVars:$PushSiteVars `
|
||||||
-PushSiteVars:$PushSiteVars `
|
-InstallRMM:$InstallRMM `
|
||||||
-InstallRMM:$InstallRMM `
|
-SaveCopy:$SaveCopy
|
||||||
-SaveCopy:$SaveCopy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -1532,59 +1450,7 @@ $script
|
|||||||
|
|
||||||
#endregion
|
#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
|
#region HTTP Listener & Routing
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user