function Invoke-ScriptAutomationMonkey { [CmdletBinding( DefaultParameterSetName='UI', SupportsShouldProcess=$true, ConfirmImpact= 'Medium' )] param( [Parameter(Mandatory,ParameterSetName='Toolkit')][switch]$SilentInstall, [Parameter(Mandatory,ParameterSetName='Cleanup')][switch]$Cleanup, [Parameter(Mandatory,ParameterSetName='Offboard')][switch]$Offboard, [Parameter(Mandatory,ParameterSetName='DattoFetch')] [Parameter(Mandatory,ParameterSetName='DattoInstall')] [switch]$UseWebhook, [Parameter(Mandatory,ParameterSetName='DattoFetch')] [Parameter(Mandatory,ParameterSetName='DattoInstall')] [String]$WebhookPassword, [string]$WebhookUrl = $Global:DattoWebhookUrl, [Parameter(ParameterSetName='DattoFetch')][switch]$FetchSites, [Parameter(ParameterSetName='DattoFetch')][switch] $SaveSitesList, [Parameter(ParameterSetName='DattoFetch')][ValidatePattern('\.csv$|\.json$')][string] $OutputFile = 'datto_sites.csv', [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 ) # Initialize config + URLs (moved out of core body) Initialize-SamyConfig # Load remote functions (kept as-is, including verification style) try { Write-Host "[Info] Loading functions from: $Script:SamyFunctionsUrl" -ForegroundColor Cyan $functionsContent = (Invoke-WebRequest -UseBasicParsing $Script:SamyFunctionsUrl -ErrorAction Stop).Content if ([string]::IsNullOrWhiteSpace($functionsContent)) { throw "Downloaded content was empty." } if ($functionsContent -notmatch '(?im)^\s*function\s+Initialize-NuGetProvider\b') { Write-Host "[Warning] samy.functions.ps1 loaded, but Initialize-NuGetProvider not found in content." -ForegroundColor Yellow } . ([ScriptBlock]::Create($functionsContent)) if (Get-Command Initialize-NuGetProvider -ErrorAction SilentlyContinue) { Write-Host "[Success] Initialize-NuGetProvider is loaded and available." -ForegroundColor Green } else { throw "Dot-sourcing completed, but Initialize-NuGetProvider is still not available." } } catch { throw "Failed to load samy.functions.ps1 from $Script:SamyFunctionsUrl. $($_.Exception.Message)" } if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) { $Global:LogCache = [System.Collections.ArrayList]::new() } # Load tasks $Global:SamyTasks = Get-SamyTasks -Url $Script:SamyTasksUrl if (-not $Global:SamyTasks) { throw "SAMY cannot continue: failed to load tasks from $Script:SamyTasksUrl" } # Debug checks (kept) $Global:SamyTasks | ForEach-Object { if ($_.PSObject.Properties.Name -contains 'Tooltip' -and $_.Tooltip -is [System.Collections.IEnumerable] -and -not ($_.Tooltip -is [string])) { Write-LogHybrid "BAD TOOLTIP TYPE: Id=$($_.Id) Type=$($_.Tooltip.GetType().FullName) ValueCount=$(@($_.Tooltip).Count)" Warning UI -LogToEvent } } $Global:SamyTasks | ForEach-Object { $tip = if ($_.PSObject.Properties.Name -contains 'Tooltip') { [string]$_.Tooltip } else { "" } $len = $tip.Length if ($len -gt 80) { $preview = $tip.Substring(0, [Math]::Min(160, $len)) Write-LogHybrid "LONG TOOLTIP: Id=$($_.Id) Len=$len Preview='$preview'" Warning UI -LogToEvent } } switch ($PSCmdlet.ParameterSetName) { 'Toolkit' { Write-LogHybrid "Toolkit-only mode" Info Startup -LogToEvent Install-SVSMSP -InstallToolkit return } 'Cleanup' { Write-LogHybrid "Running Toolkit cleanup mode" Info Startup -LogToEvent Install-SVSMSP -Cleanup return } 'DattoFetch' { Write-LogHybrid "Fetching site list only…" Info DattoAuth -LogToEvent $sites = Install-DattoRMM ` -UseWebhook ` -WebhookPassword $WebhookPassword ` -FetchSites ` -SaveSitesList:$SaveSitesList ` -OutputFile $OutputFile Write-LogHybrid "Done." Success DattoAuth -LogToEvent return } 'DattoInstall' { Write-LogHybrid "Headless DattoRMM deploy" Info DattoAuth -LogToEvent if ($PSCmdlet.ShouldProcess("Datto site '$SiteName'", "Headless install")) { Install-DattoRMM ` -UseWebhook ` -WebhookPassword $WebhookPassword ` -SiteUID $SiteUID ` -SiteName $SiteName ` -PushSiteVars:$PushSiteVars ` -InstallRMM:$InstallRMM ` -SaveCopy:$SaveCopy } return } 'Offboard' { Write-LogHybrid "Headless offboarding requested" Info OffBoard -LogToEvent $offboardTasks = $Global:SamyTasks | Where-Object { $_.Page -eq 'offboard' -and -not [string]::IsNullOrWhiteSpace([string]$_.Name) } if (-not $offboardTasks) { Write-LogHybrid "No offboard tasks configured (or none with a Name)." Warning OffBoard -LogToEvent return } if (-not $PSCmdlet.ShouldProcess("Full off-boarding flow", "Execute every offboard task")) { return } foreach ($task in $offboardTasks) { try { Write-LogHybrid "Running offboard task: $($task.Label)" Info OffBoard -LogToEvent $fn = Get-TaskHandlerName -Task $task if ([string]::IsNullOrWhiteSpace($fn)) { Write-LogHybrid "Skipping task with missing handler (Id=$($task.Id) Name='$($task.Name)' Label='$($task.Label)')" Error OffBoard -LogToEvent continue } $cmd = Get-Command -Name $fn -ErrorAction SilentlyContinue if (-not $cmd) { Write-LogHybrid "Skipping task: handler not found '$fn' (task '$($task.Label)')" Error OffBoard -LogToEvent continue } if ($cmd.Parameters.ContainsKey('Context')) { & $fn -Context $null } else { & $fn } } catch { Write-LogHybrid "Offboard task '$($task.Label)' failed: $($_.Exception.Message)" Error OffBoard -LogToEvent } } Write-LogHybrid "Headless offboarding completed" Success OffBoard -LogToEvent return } 'UI' { $url = "http://localhost:$Script:Port/" Write-LogHybrid "Starting ScriptAutomationMonkey UI on $url" Info Startup $edgeCandidates = @( "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe", "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe" ) $edgePath = $edgeCandidates | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1 if (-not $edgePath) { $cmd = Get-Command -Name 'msedge.exe' -ErrorAction SilentlyContinue if ($cmd) { $edgePath = $cmd.Path } } Start-Job -Name 'OpenScriptAutomationMonkeyUI' -ScriptBlock { param([string]$u, [string]$edge) Start-Sleep -Milliseconds 400 try { if ($edge -and (Test-Path $edge)) { Start-Process -FilePath $edge -ArgumentList @('--new-window', "--app=$u") } else { Start-Process -FilePath $u } } catch { } } -ArgumentList $url, $edgePath | Out-Null Start-Server return } default { Write-LogHybrid "Unknown ParameterSetName '$($PSCmdlet.ParameterSetName)'" Error Startup -LogToEvent throw "Unknown mode." } } }