<# .SYNOPSIS Script Automation Monkey (SAMY) ... .NOTES Full documentation: https://git.svstools.ca/.../docs/SAMY.help.md #> #region Safely bypass Restricted Execution Policy if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage' -or (Get-ExecutionPolicy) -eq 'Restricted') { Write-Host "[Info] Relaunching with ExecutionPolicy Bypass..." -ForegroundColor Yellow # Build token list (NO manual quoting) $argList = foreach ($a in $args) { [string]$a } if ($PSCommandPath) { powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$PSCommandPath" @argList } else { # Download -> create ScriptBlock -> INVOKE it with @args so $args survives the relaunch $bootstrap = "& { `$sb = [ScriptBlock]::Create((Invoke-WebRequest 'https://samybeta.svstools.ca' -UseBasicParsing).Content); & `$sb @args }" powershell.exe -NoProfile -ExecutionPolicy Bypass -Command $bootstrap @argList # temp Write-Host "[Debug] Script saw args: $($args -join ' | ')" -ForegroundColor Magenta } exit } #endregion Safely bypass Restricted Execution Policy # TLS and silent install defaults [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $ProgressPreference = 'SilentlyContinue' $ConfirmPreference = 'None' function Initialize-SamyConfig { [CmdletBinding()] param() # Listening port for HTTP UI $Script:Port = 8082 # Endpoints $Global:DattoWebhookUrl = 'https://bananas.svstools.ca/dattormm' # Hint text shown in UI $Script:SamyHintText = "" # IMPORTANT: ui.ps1 expects $Script:SamyBranch (not SamyGitBranch) if ([string]::IsNullOrWhiteSpace($Script:SamyBranch)) { $Script:SamyBranch = $Script:SamyGitBranch } } #region Remote chunk loader function Import-SamyChunk { [CmdletBinding()] param( [Parameter(Mandatory)][string]$Url, [Parameter(Mandatory)][string]$Name ) Write-Host "[Info] Loading chunk: $Name" -ForegroundColor Cyan $content = (Invoke-WebRequest -UseBasicParsing -Uri $Url -ErrorAction Stop).Content if ([string]::IsNullOrWhiteSpace($content)) { throw "Downloaded content was empty." } Write-Host "[Success] Downloaded: $Name" -ForegroundColor Green return $content } # Single source of truth for where chunks live # (Matches your current inline values) $Script:SamyGitURL = 'https://git.svstools.ca/SVS_Public_Repo/SAMY/raw/branch' $Script:SamyGitBranch = 'beta' # 'main' or 'beta' $Script:SamyGitRepo = "$Script:SamyGitURL/$Script:SamyGitBranch" Initialize-SamyConfig $Script:SamyTopLogoUrl = "$Script:SamyGitRepo/SVS_logo.svg?raw=1" $Script:SamyBgLogoUrl = "$Script:SamyGitRepo/SAMY.png?raw=1" $Script:SamyFaviconUrl = "$Script:SamyGitRepo/SVS_Favicon.ico?raw=1" $Script:SamyCssUrl = "$Script:SamyGitRepo/samy.css?raw=1" $Script:SamyJsUrl = "$Script:SamyGitRepo/samy.js?raw=1" $Script:SamyHtmlUrl = "$Script:SamyGitRepo/samy.html?raw=1" $Script:SamyTasksUrl = "$Script:SamyGitRepo/samy.tasks.json?raw=1" $Script:ChunkBase = "$Script:SamyGitRepo/src" # Load chunks (dependencies first) $chunks = @( # 1) Logging next: used everywhere @{ Name = 'logging.fallback.ps1'; Url = "$Script:ChunkBase/logging.fallback.ps1?raw=1" }, # 2) Core helpers/utilities that others depend on #@{ Name = 'helpers.ps1'; Url = "$Script:ChunkBase/helpers.ps1?raw=1" }, @{ Name = 'http.ps1'; Url = "$Script:ChunkBase/http.ps1?raw=1" }, #@{ Name = 'remote.ps1'; Url = "$Script:ChunkBase/remote.ps1?raw=1" }, # 3) Core data loaders / domain logic @{ Name = 'tasks.ps1'; Url = "$Script:ChunkBase/tasks.ps1?raw=1" }, # 4) Integrations + installers @{ Name = 'svsmsp.install.ps1'; Url = "$Script:ChunkBase/svsmsp.install.ps1?raw=1" }, @{ Name = 'integrations.datto.ps1';Url = "$Script:ChunkBase/integrations.datto.ps1?raw=1" }, # 5) UI renderer (Get-UIHtml depends on URLs + http helpers) @{ Name = 'ui.ps1'; Url = "$Script:ChunkBase/ui.ps1?raw=1" }, # 6) Handlers (depend on tasks/helpers/logging) @{ Name = 'handlers.datto.ps1'; Url = "$Script:ChunkBase/handlers.datto.ps1?raw=1" }, @{ Name = 'handlers.onboard.ps1'; Url = "$Script:ChunkBase/handlers.onboard.ps1?raw=1" }, @{ Name = 'handlers.offboard.ps1'; Url = "$Script:ChunkBase/handlers.offboard.ps1?raw=1" }, @{ Name = 'handlers.printers.ps1'; Url = "$Script:ChunkBase/handlers.printers.ps1?raw=1" }, # 7) Router and server last (depend on UI + handlers + http) #@{ Name = 'router.ps1'; Url = "$Script:ChunkBase/router.ps1?raw=1" }, @{ Name = 'server.ps1'; Url = "$Script:ChunkBase/server.ps1?raw=1" }, # 8) Functions file can be early OR late, but safest is after config/logging. @{ Name = 'samy.functions.ps1'; Url = "$Script:ChunkBase/samy.functions.ps1?raw=1" }, # 9) Entry point last @{ Name = 'core.ps1'; Url = "$Script:ChunkBase/core.ps1?raw=1" } ) foreach ($c in $chunks) { $content = Import-SamyChunk -Url $c.Url -Name $c.Name . ([ScriptBlock]::Create($content)) Write-Host "[Success] Loaded: $($c.Name)" -ForegroundColor Green } if (-not (Get-Command Invoke-ScriptAutomationMonkey -ErrorAction SilentlyContinue)) { throw "Bootstrap loaded chunks, but Invoke-ScriptAutomationMonkey was not found. Ensure src/core.ps1 defines it." } #endregion Remote chunk loader #region Entry behavior (same intent as your original) if ($MyInvocation.InvocationName -eq '.') { # dot-sourced, don't invoke return } elseif ($PSCommandPath) { # script was saved and run directly Invoke-ScriptAutomationMonkey @args } else { # iwr | iex fallback if ($args.Count -gt 0) { $namedArgs = @{} for ($i = 0; $i -lt $args.Count; $i++) { if ($args[$i] -is [string] -and $args[$i].StartsWith('-')) { $key = $args[$i].TrimStart('-') $next = $args[$i + 1] if ($next -and ($next -notlike '-*')) { $namedArgs[$key] = $next $i++ } else { $namedArgs[$key] = $true } } } Invoke-ScriptAutomationMonkey @namedArgs } else { Invoke-ScriptAutomationMonkey } } #endregion Entry behavior