Add test/samy.ps1
This commit is contained in:
352
test/samy.ps1
Normal file
352
test/samy.ps1
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Script Automation Monkey (SAMY) main entry point.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This file is now the orchestration layer only. It handles:
|
||||||
|
- Execution policy bypass for restricted environments
|
||||||
|
- Global config (branch, repo base, URLs)
|
||||||
|
- Loading subsystem scripts (logging, SVSMSP, Datto, printers, UI, HTTP, etc.)
|
||||||
|
- Exposing Invoke-ScriptAutomationMonkey with parameter sets for:
|
||||||
|
- UI
|
||||||
|
- Toolkit-only install
|
||||||
|
- Toolkit cleanup
|
||||||
|
- Headless Datto site fetch
|
||||||
|
- Headless Datto install
|
||||||
|
- Headless offboarding
|
||||||
|
- The iwr | iex glue at the bottom so remote calls still work.
|
||||||
|
|
||||||
|
All heavy logic lives in the Samy.*.ps1 subsystem files that are dot-sourced or
|
||||||
|
loaded from your Git repo.
|
||||||
|
#>
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
|
if ($PSCommandPath) {
|
||||||
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "`"$PSCommandPath`""
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& { iwr 'https://samy.svstools.com' -UseBasicParsing | iex }"
|
||||||
|
}
|
||||||
|
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Safely bypass Restricted Execution Policy
|
||||||
|
|
||||||
|
#region Global defaults and config
|
||||||
|
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
$ConfirmPreference = 'None'
|
||||||
|
|
||||||
|
# Default HTTP listening port for UI
|
||||||
|
$Script:SamyPort = 8082
|
||||||
|
|
||||||
|
# SAMY asset config (change branch or base once and it updates everything)
|
||||||
|
$Script:SamyBranch = 'beta' # or 'main'
|
||||||
|
$Script:SamyRepoBase = 'https://git.svstools.ca/SVS_Public_Repo/SAMY/raw/branch'
|
||||||
|
|
||||||
|
# Top level assets
|
||||||
|
$Script:SamyTopLogoUrl = "$Script:SamyRepoBase/$Script:SamyBranch/SVS_logo.svg"
|
||||||
|
$Script:SamyBgLogoUrl = "$Script:SamyRepoBase/$Script:SamyBranch/SAMY.png"
|
||||||
|
$Script:SamyFaviconUrl = "$Script:SamyRepoBase/$Script:SamyBranch/SVS_Favicon.ico"
|
||||||
|
$Script:SamyCssUrl = "$Script:SamyRepoBase/$Script:SamyBranch/samy.css?raw=1"
|
||||||
|
$Script:SamyJsUrl = "$Script:SamyRepoBase/$Script:SamyBranch/samy.js?raw=1"
|
||||||
|
|
||||||
|
# Datto webhook URL (used by Datto subsystem)
|
||||||
|
$Global:DattoWebhookUrl = 'https://automate.svstools.ca/webhook/svsmspkit'
|
||||||
|
|
||||||
|
# In-memory log cache
|
||||||
|
if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) {
|
||||||
|
$Global:LogCache = [System.Collections.ArrayList]::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Global defaults and config
|
||||||
|
|
||||||
|
#region Module loader
|
||||||
|
|
||||||
|
function Import-SamyModule {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Loads a SAMY subsystem script from local disk or from the Git repo.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
1. If running from a saved script (PSCommandPath) and the file exists next to it,
|
||||||
|
dot-sources that local file (dev mode).
|
||||||
|
2. Otherwise, downloads the module from $Script:SamyRepoBase / $Script:SamyBranch
|
||||||
|
and Invoke-Expression on its content (remote iwr|iex mode).
|
||||||
|
|
||||||
|
.PARAMETER Name
|
||||||
|
File name of the module, for example "Samy.Logging.ps1".
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)][string]$Name
|
||||||
|
)
|
||||||
|
|
||||||
|
# 1) Local dev mode: script saved to disk, use PSScriptRoot
|
||||||
|
if ($PSCommandPath) {
|
||||||
|
$localPath = Join-Path -Path $PSScriptRoot -ChildPath $Name
|
||||||
|
if (Test-Path $localPath) {
|
||||||
|
. $localPath
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2) Remote mode (iwr | iex): pull module from repo
|
||||||
|
$url = "$Script:SamyRepoBase/$Script:SamyBranch/$Name?raw=1"
|
||||||
|
|
||||||
|
try {
|
||||||
|
$resp = Invoke-WebRequest -Uri $url -UseBasicParsing -ErrorAction Stop
|
||||||
|
$content = $resp.Content
|
||||||
|
if (-not $content) {
|
||||||
|
Write-Host "[Error] Module $Name from $url returned empty content." -ForegroundColor Red
|
||||||
|
throw "Empty module content."
|
||||||
|
}
|
||||||
|
Invoke-Expression $content
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "[Error] Failed to load module $Name from $url: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load subsystems in a predictable order
|
||||||
|
Import-SamyModule -Name 'Samy.Logging.ps1' # Write-LogHelper, Write-LogHybrid
|
||||||
|
Import-SamyModule -Name 'Samy.SVSBootstrap.ps1' # Install-SVSMSP, cleanup, NuGet bootstrap
|
||||||
|
Import-SamyModule -Name 'Samy.UI.ps1' # $Global:SamyTasks, UI HTML, Get-UIHtml, etc.
|
||||||
|
Import-SamyModule -Name 'Samy.Datto.ps1' # Install-DattoRMM, Datto HTTP handlers
|
||||||
|
Import-SamyModule -Name 'Samy.Printers.ps1' # Printer config, drivers, HTTP handlers
|
||||||
|
Import-SamyModule -Name 'Samy.Apps.ps1' # Winget app handlers (Chrome, Acrobat, etc.)
|
||||||
|
Import-SamyModule -Name 'Samy.Offboard.ps1' # Offboarding handlers and full offboard flow
|
||||||
|
Import-SamyModule -Name 'Samy.Onboarding.ps1' # Onboarding handlers, RenameComputer, etc.
|
||||||
|
Import-SamyModule -Name 'Samy.Http.ps1' # Send-Text/JSON, Dispatch-Request, Start-SamyHttpServer
|
||||||
|
|
||||||
|
#endregion Module loader
|
||||||
|
|
||||||
|
#region Simple helpers that are local to this main file
|
||||||
|
|
||||||
|
function Test-ComputerName {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Name
|
||||||
|
)
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($Name)) { return $false }
|
||||||
|
if ($Name.Length -gt 15) { return $false }
|
||||||
|
if ($Name -notmatch '^[A-Za-z0-9-]+$') { return $false }
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Simple helpers
|
||||||
|
|
||||||
|
#region Main entry point: Invoke-ScriptAutomationMonkey
|
||||||
|
|
||||||
|
function Invoke-ScriptAutomationMonkey {
|
||||||
|
[CmdletBinding(
|
||||||
|
DefaultParameterSetName = 'UI',
|
||||||
|
SupportsShouldProcess = $true,
|
||||||
|
ConfirmImpact = 'Medium'
|
||||||
|
)]
|
||||||
|
param(
|
||||||
|
# Toolkit-only mode
|
||||||
|
[Parameter(Mandatory, ParameterSetName = 'Toolkit')]
|
||||||
|
[switch]$SilentInstall,
|
||||||
|
|
||||||
|
# Remove Toolkit
|
||||||
|
[Parameter(Mandatory, ParameterSetName = 'Cleanup')]
|
||||||
|
[switch]$Cleanup,
|
||||||
|
|
||||||
|
# Headless offboarding
|
||||||
|
[Parameter(Mandatory, ParameterSetName = 'Offboard')]
|
||||||
|
[switch]$Offboard,
|
||||||
|
|
||||||
|
# Datto headless mode shared params
|
||||||
|
[Parameter(Mandatory, ParameterSetName = 'DattoFetch')]
|
||||||
|
[Parameter(Mandatory, ParameterSetName = 'DattoInstall')]
|
||||||
|
[switch]$UseWebhook,
|
||||||
|
|
||||||
|
[Parameter(Mandatory, ParameterSetName = 'DattoFetch')]
|
||||||
|
[Parameter(Mandatory, ParameterSetName = 'DattoInstall')]
|
||||||
|
[string]$WebhookPassword,
|
||||||
|
|
||||||
|
[string]$WebhookUrl = $Global:DattoWebhookUrl,
|
||||||
|
|
||||||
|
# DattoFetch only
|
||||||
|
[Parameter(ParameterSetName = 'DattoFetch')]
|
||||||
|
[switch]$FetchSites,
|
||||||
|
|
||||||
|
[Parameter(ParameterSetName = 'DattoFetch')]
|
||||||
|
[switch]$SaveSitesList,
|
||||||
|
|
||||||
|
[Parameter(ParameterSetName = 'DattoFetch')]
|
||||||
|
[ValidatePattern('\.csv$|\.json$')]
|
||||||
|
[string]$OutputFile = 'datto_sites.csv',
|
||||||
|
|
||||||
|
# DattoInstall only
|
||||||
|
[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
|
||||||
|
)
|
||||||
|
|
||||||
|
switch ($PSCmdlet.ParameterSetName) {
|
||||||
|
|
||||||
|
'Toolkit' {
|
||||||
|
Write-LogHybrid "Toolkit-only mode requested." Info Startup -LogToEvent
|
||||||
|
Install-SVSMSP -InstallToolkit
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
'Cleanup' {
|
||||||
|
Write-LogHybrid "Toolkit cleanup requested." Info Startup -LogToEvent
|
||||||
|
Install-SVSMSP -Cleanup
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
'DattoFetch' {
|
||||||
|
Write-LogHybrid "DattoFetch mode: fetching site list." Info DattoAuth -LogToEvent
|
||||||
|
|
||||||
|
$sites = Install-DattoRMM `
|
||||||
|
-UseWebhook `
|
||||||
|
-WebhookPassword $WebhookPassword `
|
||||||
|
-WebhookUrl $WebhookUrl `
|
||||||
|
-FetchSites `
|
||||||
|
-SaveSitesList:$SaveSitesList `
|
||||||
|
-OutputFile $OutputFile
|
||||||
|
|
||||||
|
$count = if ($sites) { $sites.Count } else { 0 }
|
||||||
|
Write-LogHybrid "DattoFetch completed with $count sites." Success DattoAuth -LogToEvent
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
'DattoInstall' {
|
||||||
|
Write-LogHybrid "DattoInstall mode: headless RMM deploy to '$SiteName'." Info DattoAuth -LogToEvent
|
||||||
|
|
||||||
|
if ($PSCmdlet.ShouldProcess("Datto site '$SiteName'", "Headless Install-DattoRMM")) {
|
||||||
|
Install-DattoRMM `
|
||||||
|
-UseWebhook `
|
||||||
|
-WebhookPassword $WebhookPassword `
|
||||||
|
-WebhookUrl $WebhookUrl `
|
||||||
|
-SiteUID $SiteUID `
|
||||||
|
-SiteName $SiteName `
|
||||||
|
-PushSiteVars:$PushSiteVars `
|
||||||
|
-InstallRMM:$InstallRMM `
|
||||||
|
-SaveCopy:$SaveCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-LogHybrid "DattoInstall completed for '$SiteName'." Success DattoAuth -LogToEvent
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
'Offboard' {
|
||||||
|
Write-LogHybrid "Headless offboarding requested." Info OffBoard -LogToEvent
|
||||||
|
Invoke-SamyFullOffboard
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
'UI' {
|
||||||
|
# Default UI mode: launch browser and start HTTP listener
|
||||||
|
$port = $Script:SamyPort
|
||||||
|
$url = "http://localhost:$port/"
|
||||||
|
|
||||||
|
Write-LogHybrid "Starting ScriptAutomationMonkey UI on $url" Info Startup -LogToEvent
|
||||||
|
|
||||||
|
# Resolve Edge path explicitly
|
||||||
|
$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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Launch Edge (app mode) or default browser in a background job
|
||||||
|
Start-Job -Name 'OpenScriptAutomationMonkeyUI' -ScriptBlock {
|
||||||
|
param([string]$u, [string]$edgeExe)
|
||||||
|
Start-Sleep -Milliseconds 400
|
||||||
|
try {
|
||||||
|
if ($edgeExe -and (Test-Path $edgeExe)) {
|
||||||
|
Start-Process -FilePath $edgeExe -ArgumentList @('--new-window', "--app=$u")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Start-Process -FilePath $u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
} -ArgumentList $url, $edgePath | Out-Null
|
||||||
|
|
||||||
|
# Start HTTP listener loop (implemented in Samy.Http.ps1)
|
||||||
|
# Expected exported function:
|
||||||
|
# Start-SamyHttpServer -Port <int>
|
||||||
|
Start-SamyHttpServer -Port $port
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Main entry point: Invoke-ScriptAutomationMonkey
|
||||||
|
|
||||||
|
#region Auto invoke for direct execution and iwr | iex
|
||||||
|
|
||||||
|
if ($MyInvocation.InvocationName -eq '.') {
|
||||||
|
# Dot-sourced, just expose functions
|
||||||
|
}
|
||||||
|
elseif ($PSCommandPath) {
|
||||||
|
# Script was saved and run directly
|
||||||
|
Invoke-ScriptAutomationMonkey @PSBoundParameters
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# iwr | iex fallback with simple -Param value parsing
|
||||||
|
if ($args.Count -gt 0) {
|
||||||
|
$namedArgs = @{}
|
||||||
|
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||||
|
$current = $args[$i]
|
||||||
|
if ($current -is [string] -and $current.StartsWith('-')) {
|
||||||
|
$key = $current.TrimStart('-')
|
||||||
|
$next = $null
|
||||||
|
if ($i + 1 -lt $args.Count) {
|
||||||
|
$next = $args[$i + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($next -and ($next -notlike '-*')) {
|
||||||
|
$namedArgs[$key] = $next
|
||||||
|
$i++
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$namedArgs[$key] = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-ScriptAutomationMonkey @namedArgs
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Invoke-ScriptAutomationMonkey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Auto invoke for direct execution and iwr | iex
|
||||||
Reference in New Issue
Block a user