BIN
Drivers/HP/UPD/package.zip
Normal file
BIN
Drivers/HP/UPD/package.zip
Normal file
Binary file not shown.
BIN
Drivers/Sharp/MX-C428F/package.zip
Normal file
BIN
Drivers/Sharp/MX-C428F/package.zip
Normal file
Binary file not shown.
BIN
Drivers/Toshiba/e-STUDIO2525AC/package.zip
Normal file
BIN
Drivers/Toshiba/e-STUDIO2525AC/package.zip
Normal file
Binary file not shown.
BIN
Drivers/Xerox/Global/package.zip
Normal file
BIN
Drivers/Xerox/Global/package.zip
Normal file
Binary file not shown.
BIN
Drivers/Zebra/GK420d/package.zip
Normal file
BIN
Drivers/Zebra/GK420d/package.zip
Normal file
Binary file not shown.
167
New-SamyPrinterProfileJson.ps1
Normal file
167
New-SamyPrinterProfileJson.ps1
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
function New-SamyPrinterProfileJson {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Generates a SAMY printer profile JSON template from existing printers
|
||||||
|
and optionally uploads it to a Git (Gitea) repository.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Enumerates local printers via Get-Printer, maps them into SAMY printer
|
||||||
|
profile objects, and writes them to a JSON file. The JSON is intended
|
||||||
|
as a starting point / template for building printers.json used by SAMY.
|
||||||
|
|
||||||
|
Each profile includes:
|
||||||
|
- ClientCode (from parameter)
|
||||||
|
- Location (from parameter)
|
||||||
|
- ProfileName (defaults to printer Name)
|
||||||
|
- DisplayName (printer Name)
|
||||||
|
- Type (TcpIp or Shared, best-effort guess)
|
||||||
|
- Address (for TCP/IP printers)
|
||||||
|
- PrintServer (for shared printers)
|
||||||
|
- ShareName (for shared printers)
|
||||||
|
- DriverName (printer DriverName)
|
||||||
|
- DriverInfPath, DriverPackagePath, DriverInfName (empty placeholders)
|
||||||
|
- IsDefault (true if this printer is default)
|
||||||
|
|
||||||
|
Optionally, the generated JSON can be uploaded to a Git repo using
|
||||||
|
a personal access token (PAT) passed as a SecureString.
|
||||||
|
|
||||||
|
.PARAMETER ClientCode
|
||||||
|
MSP/client code to stamp into each profile (for example "SVS").
|
||||||
|
|
||||||
|
.PARAMETER Location
|
||||||
|
Human-friendly location (for example "Embrun"). Used both as a field in
|
||||||
|
each profile and as part of the default JSON file name.
|
||||||
|
|
||||||
|
.PARAMETER OutputPath
|
||||||
|
Folder where the JSON file will be saved. Default is:
|
||||||
|
C:\ProgramData\SVS\Samy\Printers
|
||||||
|
|
||||||
|
.PARAMETER UploadToGit
|
||||||
|
When set, the function will attempt to upload the generated JSON file
|
||||||
|
to the specified Git (Gitea) repository and path.
|
||||||
|
|
||||||
|
.PARAMETER GitApiBase
|
||||||
|
Base URL for the Git API, for example:
|
||||||
|
https://git.svstools.ca/api/v1
|
||||||
|
|
||||||
|
.PARAMETER GitRepo
|
||||||
|
Repository identifier in the form "Owner/Repo", for example:
|
||||||
|
SVS_Public_Repo/SAMY
|
||||||
|
|
||||||
|
.PARAMETER GitBranch
|
||||||
|
Branch name to write to. Default is "beta".
|
||||||
|
|
||||||
|
.PARAMETER GitPath
|
||||||
|
Path inside the repo where the JSON should be written, for example:
|
||||||
|
Printers/SVS/Embrun/printers.json
|
||||||
|
|
||||||
|
.PARAMETER GitToken
|
||||||
|
Personal access token as a SecureString. Recommended source:
|
||||||
|
a secret environment variable (for example $env:GIT_PAT) converted via
|
||||||
|
ConvertTo-SecureString.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
New-SamyPrinterProfileJson -ClientCode "SVS" -Location "Embrun"
|
||||||
|
|
||||||
|
Generates a printers_SVS_Embrun.json in:
|
||||||
|
C:\ProgramData\SVS\Samy\Printers
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
$secureToken = ConvertTo-SecureString $env:GIT_PAT -AsPlainText -Force
|
||||||
|
|
||||||
|
New-SamyPrinterProfileJson `
|
||||||
|
-ClientCode "SVS" `
|
||||||
|
-Location "Embrun" `
|
||||||
|
-UploadToGit `
|
||||||
|
-GitApiBase "https://git.svstools.ca/api/v1" `
|
||||||
|
-GitRepo "SVS_Public_Repo/SAMY" `
|
||||||
|
-GitBranch "beta" `
|
||||||
|
-GitPath "Printers/SVS/Embrun/printers.json" `
|
||||||
|
-GitToken $secureToken
|
||||||
|
|
||||||
|
Generates the JSON locally and uploads it to the specified path
|
||||||
|
in the Git repository.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$ClientCode,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Location,
|
||||||
|
|
||||||
|
[string]$OutputPath = "C:\ProgramData\SVS\Samy\Printers",
|
||||||
|
|
||||||
|
[switch]$UploadToGit,
|
||||||
|
|
||||||
|
[string]$GitApiBase,
|
||||||
|
[string]$GitRepo,
|
||||||
|
[string]$GitBranch = "beta",
|
||||||
|
[string]$GitPath,
|
||||||
|
[SecureString]$GitToken
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Log "Starting New-SamyPrinterProfileJson for ClientCode='$ClientCode' Location='$Location'." "Info" "PrinterJson" -LogToEvent
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 1) Ensure output folder exists and build a safe file name
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
if (-not (Test-Path $OutputPath)) {
|
||||||
|
Write-Log "Creating output folder '$OutputPath'." "Info" "PrinterJson" -LogToEvent
|
||||||
|
New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$safeLocation = $Location -replace '[^A-Za-z0-9_-]', '_'
|
||||||
|
$fileName = "printers_{0}_{1}.json" -f $ClientCode, $safeLocation
|
||||||
|
$filePath = Join-Path $OutputPath $fileName
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 2) Enumerate printers and build profile objects
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
$printers = Get-Printer -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if (-not $printers) {
|
||||||
|
Write-Log "No printers found on this system. JSON will be empty." "Warning" "PrinterJson" -LogToEvent
|
||||||
|
} else {
|
||||||
|
Write-Log ("Found {0} printer(s)." -f $printers.Count) "Info" "PrinterJson" -LogToEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
$profiles = @()
|
||||||
|
|
||||||
|
foreach ($p in $printers) {
|
||||||
|
$profileName = $p.Name
|
||||||
|
$displayName = $p.Name
|
||||||
|
$driverName = $p.DriverName
|
||||||
|
$portName = $p.PortName
|
||||||
|
$isDefault = $p.Shared -eq $false -and $p.Default -eq $true
|
||||||
|
|
||||||
|
# Try to resolve port details
|
||||||
|
$port = $null
|
||||||
|
if ($portName) {
|
||||||
|
$port = Get-PrinterPort -Name $portName -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = "TcpIp"
|
||||||
|
$address = $null
|
||||||
|
$printServer = $null
|
||||||
|
$shareName = $null
|
||||||
|
|
||||||
|
if ($port -and $port.PrinterHostAddress) {
|
||||||
|
# Standard TCP/IP port
|
||||||
|
$type = "TcpIp"
|
||||||
|
$address = $port.PrinterHostAddress
|
||||||
|
}
|
||||||
|
elseif ($p.Shared -and $p.ShareName) {
|
||||||
|
# Best guess at a shared printer
|
||||||
|
$type = "Shared"
|
||||||
|
$shareName = $p.ShareName
|
||||||
|
$printServer = $env:COMPUTERNAME
|
||||||
|
}
|
||||||
|
|
||||||
|
$profiles += [PSCustomObject]@{
|
||||||
|
ClientCode = $ClientCode
|
||||||
|
Location = $Location
|
||||||
|
|
||||||
|
ProfileName = $profileName
|
||||||
|
DisplayName = $displayName
|
||||||
BIN
SVS_Favicon.ico
Normal file
BIN
SVS_Favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
23
SVS_logo.svg
Normal file
23
SVS_logo.svg
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 480 140">
|
||||||
|
<!-- Generator: Adobe Illustrator 29.3.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 91) -->
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.st0 {
|
||||||
|
fill: #750000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st1 {
|
||||||
|
fill: #267eb2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="st1" d="M155.08,115.14c-5.99,0-11.27-.66-15.86-1.99s-8.6-3.2-12.05-5.63c-3.45-2.42-6.54-5.27-9.27-8.53l14.21-15.92c3.79,4.85,7.75,8.05,11.88,9.61,4.13,1.55,8.01,2.33,11.65,2.33,1.44,0,2.73-.13,3.87-.4,1.14-.26,2.01-.7,2.61-1.31.61-.61.91-1.44.91-2.5,0-.98-.32-1.82-.97-2.5s-1.5-1.27-2.56-1.76-2.22-.91-3.47-1.25-2.46-.62-3.64-.85c-1.18-.23-2.22-.46-3.13-.68-4.55-1.06-8.53-2.35-11.94-3.87-3.41-1.51-6.25-3.33-8.53-5.46-2.27-2.12-3.96-4.55-5.06-7.28s-1.65-5.8-1.65-9.21c0-3.87.89-7.39,2.67-10.57s4.17-5.91,7.16-8.19c2.99-2.27,6.4-4.02,10.23-5.23,3.83-1.21,7.79-1.82,11.88-1.82,5.99,0,10.99.55,15.01,1.65,4.02,1.1,7.39,2.67,10.12,4.72,2.73,2.05,5.08,4.43,7.05,7.16l-14.32,13.76c-1.67-1.59-3.41-2.9-5.23-3.92-1.82-1.02-3.69-1.78-5.63-2.27-1.93-.49-3.85-.74-5.74-.74-1.74,0-3.22.13-4.43.4s-2.16.68-2.84,1.25-1.02,1.35-1.02,2.33.44,1.8,1.31,2.44,1.97,1.19,3.3,1.65c1.32.46,2.65.82,3.98,1.08,1.33.27,2.44.47,3.35.62,4.17.76,8,1.8,11.48,3.13,3.49,1.33,6.54,3,9.15,5,2.61,2.01,4.62,4.51,6.03,7.5,1.4,3,2.1,6.54,2.1,10.63,0,5.84-1.46,10.73-4.38,14.67s-6.84,6.92-11.77,8.92c-4.93,2.01-10.42,3.01-16.48,3.01l.02.02Z"/>
|
||||||
|
<path class="st1" d="M219.31,114.01l-31.72-79.58h24.56l12.73,34.79c.91,2.35,1.65,4.36,2.22,6.03s1.08,3.24,1.53,4.72c.46,1.48.89,3.05,1.31,4.72.42,1.67.89,3.71,1.42,6.14h-3.98c.76-3.18,1.42-5.8,1.99-7.84.57-2.05,1.21-4.07,1.93-6.08.72-2.01,1.65-4.56,2.79-7.67l12.73-34.79h23.76l-31.95,79.58h-19.33v-.02Z"/>
|
||||||
|
<path class="st1" d="M302.98,115.14c-5.99,0-11.27-.66-15.86-1.99-4.59-1.33-8.6-3.2-12.05-5.63-3.45-2.42-6.54-5.27-9.27-8.53l14.21-15.92c3.79,4.85,7.75,8.05,11.88,9.61,4.13,1.55,8.02,2.33,11.65,2.33,1.44,0,2.73-.13,3.87-.4,1.14-.26,2.01-.7,2.62-1.31s.91-1.44.91-2.5c0-.98-.32-1.82-.97-2.5-.64-.68-1.5-1.27-2.56-1.76s-2.22-.91-3.47-1.25-2.46-.62-3.64-.85-2.22-.46-3.13-.68c-4.55-1.06-8.53-2.35-11.94-3.87-3.41-1.51-6.25-3.33-8.53-5.46-2.27-2.12-3.96-4.55-5.06-7.28s-1.65-5.8-1.65-9.21c0-3.87.89-7.39,2.67-10.57,1.78-3.18,4.17-5.91,7.16-8.19,2.99-2.27,6.4-4.02,10.23-5.23,3.83-1.21,7.79-1.82,11.88-1.82,5.99,0,10.99.55,15.01,1.65s7.39,2.67,10.12,4.72,5.08,4.43,7.05,7.16l-14.33,13.76c-1.67-1.59-3.41-2.9-5.23-3.92-1.82-1.02-3.69-1.78-5.63-2.27-1.93-.49-3.85-.74-5.74-.74-1.74,0-3.22.13-4.43.4s-2.16.68-2.84,1.25c-.68.57-1.02,1.35-1.02,2.33s.44,1.8,1.31,2.44,1.97,1.19,3.3,1.65,2.65.82,3.98,1.08c1.33.27,2.44.47,3.35.62,4.17.76,8,1.8,11.48,3.13,3.49,1.33,6.54,3,9.15,5,2.62,2.01,4.62,4.51,6.03,7.5,1.4,3,2.1,6.54,2.1,10.63,0,5.84-1.46,10.73-4.38,14.67s-6.84,6.92-11.77,8.92c-4.93,2.01-10.42,3.01-16.48,3.01l.02.02Z"/>
|
||||||
|
<path class="st1" d="M345.72,72.6v-39.35h10.45l14.33,23.33-8.49-.06,14.5-23.27h10.12v39.35h-11.69v-9.39c0-3.37.08-6.41.25-9.11.17-2.7.46-5.38.87-8.04l1.35,3.54-9.5,14.73h-3.71l-9.33-14.73,1.41-3.54c.41,2.51.7,5.09.87,7.73s.25,5.78.25,9.42v9.39h-11.69.01Z"/>
|
||||||
|
<path class="st1" d="M409.4,73.16c-2.96,0-5.57-.33-7.84-.98-2.27-.66-4.25-1.58-5.96-2.78s-3.23-2.6-4.58-4.22l7.03-7.87c1.87,2.4,3.83,3.98,5.87,4.75s3.96,1.15,5.76,1.15c.71,0,1.35-.07,1.91-.2s.99-.35,1.29-.65.45-.71.45-1.24c0-.49-.16-.9-.48-1.24s-.74-.63-1.26-.87c-.53-.24-1.1-.45-1.71-.62-.62-.17-1.22-.31-1.8-.42-.58-.11-1.1-.22-1.55-.34-2.25-.52-4.22-1.16-5.9-1.91-1.69-.75-3.09-1.65-4.22-2.7-1.12-1.05-1.96-2.25-2.5-3.6s-.81-2.87-.81-4.55c0-1.91.44-3.65,1.32-5.23.88-1.57,2.06-2.92,3.54-4.05s3.17-1.99,5.06-2.58,3.85-.9,5.87-.9c2.96,0,5.43.27,7.42.81s3.65,1.32,5,2.33c1.35,1.01,2.51,2.19,3.49,3.54l-7.08,6.8c-.83-.79-1.69-1.43-2.59-1.94s-1.83-.88-2.78-1.12c-.96-.24-1.9-.37-2.84-.37-.86,0-1.59.07-2.19.2s-1.07.34-1.41.62c-.34.28-.51.67-.51,1.15s.21.89.65,1.21c.43.32.97.59,1.63.82.66.22,1.31.4,1.97.53s1.21.23,1.66.31c2.06.38,3.95.89,5.68,1.55,1.72.66,3.23,1.48,4.53,2.47s2.29,2.23,2.98,3.71c.69,1.48,1.04,3.23,1.04,5.26,0,2.89-.72,5.3-2.16,7.25s-3.38,3.42-5.82,4.41-5.15,1.49-8.15,1.49v.02Z"/>
|
||||||
|
<path class="st1" d="M431.66,72.6v-39.35h17.71c2.7,0,5.1.58,7.2,1.74,2.1,1.16,3.75,2.75,4.95,4.78,1.2,2.02,1.8,4.35,1.8,6.97s-.6,5.17-1.8,7.31c-1.2,2.14-2.85,3.81-4.95,5.03-2.1,1.22-4.5,1.83-7.2,1.83h-5.56v11.69h-12.15ZM443.58,50.57h3.54c.71,0,1.35-.14,1.91-.42s1-.68,1.32-1.21c.32-.52.48-1.18.48-1.97s-.16-1.42-.48-1.91-.76-.85-1.32-1.1c-.56-.24-1.2-.37-1.91-.37h-3.54v6.97h0Z"/>
|
||||||
|
<polyline class="st1" points="17.09 25.67 109.17 25.67 63.13 108.6"/>
|
||||||
|
<polyline class="st0" points="73.19 103.73 113.01 32 125.59 32 79.54 114.93 73.19 103.73"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.6 KiB |
70
docs/Datto.md
Normal file
70
docs/Datto.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<#
|
||||||
|
.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
|
||||||
|
#>
|
||||||
120
docs/SAMY.help.md
Normal file
120
docs/SAMY.help.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Script Automation Monkey (SAMY) is a unified MSP assistant that automates onboarding, headless offboarding,
|
||||||
|
Datto RMM deployments, printer provisioning, and toolkit management through a local UI, HTTP endpoints,
|
||||||
|
or direct PowerShell switches.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
SAMY can be used in three ways:
|
||||||
|
1) Local UI (default) - launches a local web UI and exposes endpoints for tasks.
|
||||||
|
2) Headless / RMM mode - run onboarding/offboarding/Datto flows using PowerShell switches.
|
||||||
|
3) Toolkit-only / cleanup - install or remove the SVSMSP module and related artifacts.
|
||||||
|
|
||||||
|
The Datto helper (Install-DattoRMM) centralizes:
|
||||||
|
- Credential retrieval (webhook or direct)
|
||||||
|
- OAuth token acquisition
|
||||||
|
- Site list retrieval and optional persistence
|
||||||
|
- Registry variable writes (HKLM:\Software\SVS\Deployment)
|
||||||
|
- Agent download/launch and optional installer archiving
|
||||||
|
|
||||||
|
Printer endpoints provide:
|
||||||
|
- /getprinters (fetch profiles from server; optionally updates local config)
|
||||||
|
- /installprinters (installs requested printers; supports -WhatIf when enabled)
|
||||||
|
|
||||||
|
Throughout, secrets are never written to logs or console, and all operations produce clear
|
||||||
|
success/failure messages via Write-LogHybrid.
|
||||||
|
|
||||||
|
.CHANGES
|
||||||
|
- Windows 11 25H2: execution policy relaunch logic updated to improve reliability.
|
||||||
|
- UI template: HTML has been moved to samy.html (pulled from repo at runtime).
|
||||||
|
- UI hint message: now controlled via the {{SamyHintText}} placeholder in samy.html.
|
||||||
|
Recommended: set $Script:SamyHintText in the script or via a config file (future enhancement).
|
||||||
|
- Printers: Invoke-InstallPrinters supports enabling -WhatIf on Invoke-SVSPrinterInstall
|
||||||
|
(see Invoke-InstallPrinters; the line is commented by default).
|
||||||
|
- Assets: branch/base configuration is centralized in the SAMY asset config section
|
||||||
|
(SamyBranch/SamyRepoBase). Update those values once to switch UI/CSS/JS/HTML assets.
|
||||||
|
|
||||||
|
.CONFIGURATION
|
||||||
|
SAMY asset config (branch + repo base):
|
||||||
|
$Script:SamyBranch = 'beta' # or 'main'
|
||||||
|
$Script:SamyRepoBase = 'https://git.svstools.ca/SVS_Public_Repo/SAMY/raw/branch'
|
||||||
|
|
||||||
|
UI hint message:
|
||||||
|
- samy.html contains: {{SamyHintText}}
|
||||||
|
- PowerShell sets: $Script:SamyHintText = "Please use samy.svstools.ca"
|
||||||
|
|
||||||
|
.PARAMETER SilentInstall
|
||||||
|
Toolkit-only mode. Installs/updates the SVSMSP toolkit module and exits (no UI).
|
||||||
|
|
||||||
|
.PARAMETER Cleanup
|
||||||
|
Removes the SVSMSP module, unregisters repos, and clears related artifacts (including registry keys).
|
||||||
|
|
||||||
|
.PARAMETER Offboard
|
||||||
|
Runs every offboarding task sequentially (same behavior as checking "Select All" in the Off-Boarding tab),
|
||||||
|
without launching the web UI.
|
||||||
|
|
||||||
|
.PARAMETER UseWebhook
|
||||||
|
Fetches Datto API credentials from the webhook at WebhookUrl using WebhookPassword.
|
||||||
|
|
||||||
|
.PARAMETER WebhookPassword
|
||||||
|
Password used to authenticate to the credentials webhook. Mandatory when -UseWebhook is set
|
||||||
|
for headless Datto modes. May be blank when using an allowlisted IP flow (server-side).
|
||||||
|
|
||||||
|
.PARAMETER WebhookUrl
|
||||||
|
URL of the credentials webhook endpoint. Defaults to $Global:DattoWebhookUrl.
|
||||||
|
|
||||||
|
.PARAMETER FetchSites
|
||||||
|
Fetches the list of Datto RMM sites and skips all install/variable-push actions.
|
||||||
|
|
||||||
|
.PARAMETER SaveSitesList
|
||||||
|
Saves the fetched site list to the desktop as OutputFile. Must be used with -FetchSites.
|
||||||
|
|
||||||
|
.PARAMETER OutputFile
|
||||||
|
Name of the file to write the site list to (must end in ".csv" or ".json").
|
||||||
|
Defaults to 'datto_sites.csv'.
|
||||||
|
|
||||||
|
.PARAMETER SiteUID
|
||||||
|
The unique identifier of the Datto RMM site. Mandatory when performing install or variable-push.
|
||||||
|
|
||||||
|
.PARAMETER SiteName
|
||||||
|
The friendly name of the Datto RMM site (used in logging). Mandatory when performing install or variable-push.
|
||||||
|
|
||||||
|
.PARAMETER PushSiteVars
|
||||||
|
Fetches site-specific variables and writes them under HKLM:\Software\SVS\Deployment.
|
||||||
|
|
||||||
|
.PARAMETER InstallRMM
|
||||||
|
Downloads and launches the Datto RMM agent installer for the specified site.
|
||||||
|
|
||||||
|
.PARAMETER SaveCopy
|
||||||
|
Saves a copy of the downloaded Datto RMM installer into C:\Temp.
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Default EventLog : SVSMSP Events
|
||||||
|
Default Source : SAMY
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
& ([ScriptBlock]::Create((iwr 'https://samy.svstools.com' -UseBasicParsing).Content)) -UseWebhook -WebhookPassword 'pwd' SiteUID 'site-123' -SiteName 'Acme Corp' -PushSiteVars -InstallRMM
|
||||||
|
|
||||||
|
# Headlessly installs the Datto RMM agent on “Acme Corp” and writes site variables to the registry.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
& ([ScriptBlock]::Create((iwr 'https://samy.svstools.com' -UseBasicParsing).Content)) -UseWebhook -WebhookPassword 'pwd' -FetchSites -SaveSitesList -OutputFile 'sites.json'
|
||||||
|
|
||||||
|
# Fetches the full site list via webhook and saves it as JSON to your Desktop.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
& ([ScriptBlock]::Create((iwr 'samy.svstools.ca' -UseBasicParsing).Content)) -SilentInstall
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
& ([ScriptBlock]::Create((iwr 'samy.svstools.com' -UseBasicParsing).Content)) -Cleanup
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
& ([ScriptBlock]::Create((iwr 'samy.svstools.ca' -UseBasicParsing).Content)) -Offboard
|
||||||
|
|
||||||
|
# Runs the off-boarding tasks in sequence without launching the UI.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
# Printer provisioning (called from RMM / scripts)
|
||||||
|
$profiles = Get-SvsPrinterProfilesFromServer -Uri 'https://bananas.svstools.ca/getprinters' -Password $pw
|
||||||
|
Set-SvsPrinterLocalConfig -PrinterProfiles $profiles -SkipIfEmpty
|
||||||
|
#>
|
||||||
6
docs/json.txt
Normal file
6
docs/json.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Define every task once here:
|
||||||
|
# Id → checkbox HTML `id`
|
||||||
|
# Name → URL path (`/Name`)
|
||||||
|
# Label → user-visible text
|
||||||
|
# HandlerFn → the PowerShell function to invoke
|
||||||
|
# Page → which tab/page it appears on
|
||||||
383
samy.css
Normal file
383
samy.css
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
:root {
|
||||||
|
/* Cool Palette */
|
||||||
|
--background-color: rgba(18, 18, 18, 1);
|
||||||
|
--border-color: rgba(255, 255, 255, 0.15);
|
||||||
|
|
||||||
|
/* Neutral Colors */
|
||||||
|
--white-color: rgba(255, 255, 255, 1);
|
||||||
|
--gray-color: rgba(102, 102, 102, 1);
|
||||||
|
--dark-gray-color: rgba(51, 51, 51, 1);
|
||||||
|
--light-gray-color: rgba(187, 187, 187, 1);
|
||||||
|
|
||||||
|
/* Sidebar Button Colors */
|
||||||
|
--btn-sidebar-light-gray: rgba(68, 68, 68, 1);
|
||||||
|
--btn-sidebar-blue: rgba(30, 144, 255, 1);
|
||||||
|
--btn-hover: rgba(0, 86, 179, 1);
|
||||||
|
--btn-hover-scale: 1.05;
|
||||||
|
|
||||||
|
/* Button Colors */
|
||||||
|
--btn-success: rgba(40, 167, 69, 1);
|
||||||
|
--btn-success-disabled: rgba(108, 117, 125, 1);
|
||||||
|
--btn-danger: rgba(220, 53, 69, 1);
|
||||||
|
|
||||||
|
/* Monkey + status panel settings */
|
||||||
|
--monkey-size: 160px; /* size of SAMY */
|
||||||
|
--monkey-bottom: 135px; /* how high from bottom of sidebar */
|
||||||
|
--status-gap: 20px; /* space between status box and monkey */
|
||||||
|
--status-height: 140px; /* max height of status box */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sizing easier to reason about */
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
color: var(--white-color);
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container img {
|
||||||
|
max-width: 300px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--gray-color);
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar */
|
||||||
|
.sidebar {
|
||||||
|
width: 200px;
|
||||||
|
background: var(--background-color);
|
||||||
|
padding: 10px;
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: calc(var(--monkey-bottom) + var(--monkey-size) + var(--status-gap) + 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status panel above monkey */
|
||||||
|
#status-box {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
bottom: calc(var(--monkey-bottom) + var(--monkey-size) + var(--status-gap));
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
max-height: var(--status-height);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
font-family: "Segoe UI", "Segoe UI Emoji", "Segoe UI Symbol", system-ui, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.35;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SAMY bottom-left */
|
||||||
|
.sidebar::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
bottom: var(--monkey-bottom);
|
||||||
|
width: var(--monkey-size);
|
||||||
|
height: var(--monkey-size);
|
||||||
|
background-image: url("SAMY.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
opacity: 0.95;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
color: var(--white-color);
|
||||||
|
background: var(--btn-sidebar-light-gray);
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
transition: background-color 0.3s, transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar button.active {
|
||||||
|
background: var(--btn-sidebar-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar button:hover {
|
||||||
|
background: var(--btn-hover);
|
||||||
|
transform: scale(var(--btn-hover-scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
padding: 20px;
|
||||||
|
padding-bottom: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(100vh - 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Floating buttons (Exit / Run) */
|
||||||
|
.fixed-buttons {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-button,
|
||||||
|
.run-button {
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--white-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit-button {
|
||||||
|
background-color: var(--btn-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-button {
|
||||||
|
background-color: var(--btn-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Standard buttons (shared look across the app) */
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
font-family: "Segoe UI", "Segoe UI Emoji", "Segoe UI Symbol", system-ui, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
padding: 10px 14px;
|
||||||
|
min-height: 36px;
|
||||||
|
|
||||||
|
color: var(--white-color);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
transition: transform 0.12s ease, background-color 0.2s ease, border-color 0.2s ease, opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover { transform: scale(1.02); }
|
||||||
|
.btn:active { transform: scale(0.99); }
|
||||||
|
|
||||||
|
.btn:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(255, 127, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Variants */
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--btn-sidebar-blue);
|
||||||
|
}
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: var(--btn-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background-color: var(--btn-success);
|
||||||
|
}
|
||||||
|
.btn-success:hover {
|
||||||
|
background-color: rgba(30, 140, 60, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: var(--btn-danger);
|
||||||
|
}
|
||||||
|
.btn-danger:hover {
|
||||||
|
background-color: rgba(190, 40, 55, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled,
|
||||||
|
.btn[aria-disabled="true"] {
|
||||||
|
background-color: var(--btn-success-disabled);
|
||||||
|
opacity: 0.75;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Tabs */
|
||||||
|
.tab-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Columns & checkboxes */
|
||||||
|
.columns-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 45%;
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--dark-gray-color);
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Datto password + site dropdown (On-Boarding) */
|
||||||
|
#PasswordContainer,
|
||||||
|
#dattoRmmContainer {
|
||||||
|
margin: 16px 0;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--dark-gray-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
|
||||||
|
/* Narrower so it fits under the stack column */
|
||||||
|
width: calc(25% - 10px);
|
||||||
|
max-width: 480px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Printer panels on Devices tab */
|
||||||
|
#printerPasswordContainer,
|
||||||
|
#printerClientContainer,
|
||||||
|
#printerListContainer {
|
||||||
|
margin: 16px 0;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--dark-gray-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
|
||||||
|
/* Full width in the Devices tab */
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#PasswordContainer input,
|
||||||
|
#PasswordContainer button,
|
||||||
|
#dattoRmmContainer select,
|
||||||
|
#printerPasswordContainer input,
|
||||||
|
#printerPasswordContainer button,
|
||||||
|
#printerClientContainer select {
|
||||||
|
background-color: var(--dark-gray-color);
|
||||||
|
color: var(--white-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input + GO button inline rows */
|
||||||
|
#PasswordContainer > div,
|
||||||
|
#printerPasswordContainer > div {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#PasswordContainer input,
|
||||||
|
#printerPasswordContainer input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown fills panel width */
|
||||||
|
#dattoRmmContainer select,
|
||||||
|
#printerClientContainer select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GO button styling */
|
||||||
|
#PasswordContainer button,
|
||||||
|
.go-button {
|
||||||
|
background-color: var(--btn-sidebar-blue);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#PasswordContainer button:hover,
|
||||||
|
.go-button:hover {
|
||||||
|
background-color: var(--btn-hover);
|
||||||
|
transform: scale(1.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tag line */
|
||||||
|
.tagline {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--light-gray-color);
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Big red notice under tagline */
|
||||||
|
.samy-hint {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
font-size: 3rem;
|
||||||
|
color: #ff4d4d;
|
||||||
|
font-weight: 900;
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: center;
|
||||||
|
grid-column: 1 / -1; /* span both grid columns */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#PasswordContainer,
|
||||||
|
#dattoRmmContainer {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
81
samy.functions.ps1
Normal file
81
samy.functions.ps1
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
function Initialize-NuGetProvider {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param()
|
||||||
|
|
||||||
|
#region — guarantee NuGet provider is present without prompting
|
||||||
|
|
||||||
|
# ─── Silent defaults ───
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
$ConfirmPreference = 'None'
|
||||||
|
|
||||||
|
# ─── Pre-create folder if running as SYSTEM (avoids NuGet install bug) ───
|
||||||
|
$provPath = "$env:ProgramData\PackageManagement\ProviderAssemblies"
|
||||||
|
if (-not (Test-Path $provPath)) {
|
||||||
|
try {
|
||||||
|
New-Item -Path $provPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||||
|
Write-LogHybrid "Created missing provider folder: $provPath" Info Bootstrap -LogToEvent
|
||||||
|
} catch {
|
||||||
|
Write-LogHybrid "Failed to create provider folder: $($_.Exception.Message)" Warning Bootstrap -LogToEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Ensure PowerShellGet is available ───
|
||||||
|
if (-not (Get-Command Install-PackageProvider -ErrorAction SilentlyContinue)) {
|
||||||
|
try {
|
||||||
|
Install-Module PowerShellGet -Force -AllowClobber -Confirm:$false -ErrorAction Stop
|
||||||
|
Write-LogHybrid "Installed PowerShellGet module" Info Bootstrap -LogToEvent
|
||||||
|
} catch {
|
||||||
|
Write-LogHybrid "PowerShellGet install failed: $($_.Exception.Message)" Error Bootstrap -LogToEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Ensure PackageManagement is up-to-date ───
|
||||||
|
$pkgMgmtVersion = (Get-Module PackageManagement -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Version
|
||||||
|
if ($pkgMgmtVersion -lt [Version]"1.3.1") {
|
||||||
|
try {
|
||||||
|
Install-Module PackageManagement -Force -AllowClobber -Confirm:$false -ErrorAction Stop
|
||||||
|
Write-LogHybrid "Updated PackageManagement to latest version" Info Bootstrap -LogToEvent
|
||||||
|
} catch {
|
||||||
|
Write-LogHybrid "PackageManagement update failed: $($_.Exception.Message)" Warning Bootstrap -LogToEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Import modules silently ───
|
||||||
|
Import-Module PackageManagement -Force -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
Import-Module PowerShellGet -Force -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
|
||||||
|
# ─── Trust PSGallery if not already ───
|
||||||
|
$gallery = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue
|
||||||
|
if ($gallery -and $gallery.InstallationPolicy -ne 'Trusted') {
|
||||||
|
try {
|
||||||
|
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop
|
||||||
|
Write-LogHybrid "PSGallery marked as Trusted" Info Bootstrap -LogToEvent
|
||||||
|
} catch {
|
||||||
|
Write-LogHybrid "Failed to trust PSGallery: $($_.Exception.Message)" Warning Bootstrap -LogToEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Ensure NuGet is installed silently ───
|
||||||
|
$nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue
|
||||||
|
if (-not $nuget) {
|
||||||
|
try {
|
||||||
|
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false -ErrorAction Stop
|
||||||
|
$nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue
|
||||||
|
Write-LogHybrid "Installed NuGet provider v$($nuget.Version)" Info Bootstrap -LogToEvent
|
||||||
|
} catch {
|
||||||
|
Write-LogHybrid "NuGet install failed: $($_.Exception.Message)" Error Bootstrap -LogToEvent
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-LogHybrid "NuGet provider already present (v$($nuget.Version))" Info Bootstrap -LogToEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Final import check ───
|
||||||
|
try {
|
||||||
|
Import-PackageProvider -Name NuGet -Force -ErrorAction Stop | Out-Null
|
||||||
|
} catch {
|
||||||
|
Write-LogHybrid "NuGet provider import failed: $($_.Exception.Message)" Error Bootstrap -LogToEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion — guarantee NuGet provider is present without prompting
|
||||||
|
}
|
||||||
185
samy.html
Normal file
185
samy.html
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<title>Script Automation Monkey</title>
|
||||||
|
<link rel="icon" href="{{SamyFaviconUrl}}">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
{{CssContent}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="logo-container">
|
||||||
|
<!-- SVS Logo (left) -->
|
||||||
|
<div class="logo-left">
|
||||||
|
<img src="{{SamyTopLogoUrl}}" alt="SVS Logo">
|
||||||
|
{{moduleVersion}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Centered rotating tagline -->
|
||||||
|
<div id="tagline" class="tagline">
|
||||||
|
Script Automation Monkey (Yeah!)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Big red line under tagline -->
|
||||||
|
<div id="samyHint" class="samy-hint">{{SamyHintText}}</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="sidebar">
|
||||||
|
<button class="tab-button" data-tab="onboardTab">On-Boarding</button>
|
||||||
|
<button class="tab-button" data-tab="offboardTab">Off-Boarding</button>
|
||||||
|
<button class="tab-button" data-tab="devicesTab">Devices</button>
|
||||||
|
|
||||||
|
<div id="status-box" style="margin-top: 1em; font-family: monospace;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div id="onboardTab" class="tab-content">
|
||||||
|
<h2>On-Boarding</h2>
|
||||||
|
<h3 class="subtitle">This new deployment method ensures everything is successfully deployed with greater ease!</h3>
|
||||||
|
|
||||||
|
<div class="columns-container">
|
||||||
|
<div class="checkbox-group column">
|
||||||
|
<h3>SVSMSP Stack</h3>
|
||||||
|
<label><input type="checkbox" id="selectAllLeftCheckbox" onclick="toggleColumn('left')"> Select All</label>
|
||||||
|
{{onboardLeftColumn}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="checkbox-group column">
|
||||||
|
<h3>Optional</h3>
|
||||||
|
<label><input type="checkbox" id="selectAllRightCheckbox" onclick="toggleColumn('right')"> Select All</label>
|
||||||
|
{{onboardRightColumn}}
|
||||||
|
|
||||||
|
<div id="renameComputerBlock" style="display:none; margin-left: 24px; margin-top: 6px;">
|
||||||
|
<label for="txtNewComputerName">New computer name:</label>
|
||||||
|
<input type="text" id="txtNewComputerName" placeholder="e.g. CORP-LAP-123" />
|
||||||
|
<small style="display:block; margin-top:4px;">
|
||||||
|
(Max 15 chars; letters, numbers, and hyphens only.)
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> <!-- end columns-container -->
|
||||||
|
|
||||||
|
|
||||||
|
<div id="PasswordContainer" style="display:none; margin-bottom:1em;">
|
||||||
|
<label for="Password">Enter Password:</label>
|
||||||
|
<div style="display:flex; gap:5px;">
|
||||||
|
<input type="password" id="Password" placeholder="Enter Password" style="flex:1;" />
|
||||||
|
<button onclick="fetchSites()" class="go-button">GO!</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="dattoRmmContainer" style="display:none; margin-bottom:1em;">
|
||||||
|
<label for="dattoDropdown">Select a Datto RMM site:</label>
|
||||||
|
<select id="dattoDropdown" style="width:100%;">
|
||||||
|
<option disabled selected>Fetching sites...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div> <!-- end onboardTab -->
|
||||||
|
|
||||||
|
<div id="offboardTab" class="tab-content">
|
||||||
|
<h2>Off-Boarding</h2>
|
||||||
|
<div class="columns-container">
|
||||||
|
<div class="checkbox-group column">
|
||||||
|
<h3>Remove Stack</h3>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="offboardSelectAll" onclick="toggleOffboardAll()">
|
||||||
|
Select All
|
||||||
|
</label>
|
||||||
|
{{offboardCheckboxes}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tweaksTab" class="tab-content">
|
||||||
|
<h2>Tweaks</h2>
|
||||||
|
<div class="columns-container">
|
||||||
|
<div class="checkbox-group column">
|
||||||
|
<h3>Tweaks</h3>
|
||||||
|
{{tweaksCheckboxes}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="SVSAppsTab" class="tab-content">
|
||||||
|
<h2>SVS APPs</h2>
|
||||||
|
<div class="columns-container">
|
||||||
|
<div class="checkbox-group column">
|
||||||
|
<h3>Applications</h3>
|
||||||
|
{{appsCheckboxes}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- NEW: Devices tab for printers and future stuff -->
|
||||||
|
<div id="devicesTab" class="tab-content">
|
||||||
|
<h2>Devices</h2>
|
||||||
|
<h3 class="subtitle">Manage printers and other client devices.</h3>
|
||||||
|
|
||||||
|
<!-- Printer password + fetch -->
|
||||||
|
<div id="printerPasswordContainer" style="margin-bottom:1em;">
|
||||||
|
<label for="PrinterPassword">Enter Printer Password:</label>
|
||||||
|
<div style="display:flex; gap:5px;">
|
||||||
|
<input type="password"
|
||||||
|
id="PrinterPassword"
|
||||||
|
placeholder="Enter printer password"
|
||||||
|
style="flex:1;" />
|
||||||
|
<button onclick="fetchPrinters()" class="go-button">Get Printers</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Client code dropdown -->
|
||||||
|
<div id="printerClientContainer" style="display:none; margin-bottom:1em;">
|
||||||
|
<label for="printerClientDropdown">Select Client:</label>
|
||||||
|
<select id="printerClientDropdown" style="width:100%;">
|
||||||
|
<option disabled selected>Fetch printers first...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Printer checkbox list -->
|
||||||
|
<div id="printerListContainer" style="display:none; margin-bottom:1em;">
|
||||||
|
<label>Printers for selected client:</label>
|
||||||
|
<small style="display:block; margin-bottom:4px; opacity:0.8;">
|
||||||
|
Check the printers to install, and mark one as "Make default" (optional).
|
||||||
|
</small>
|
||||||
|
|
||||||
|
<div id="printerCheckboxContainer"
|
||||||
|
style="max-height:200px; overflow-y:auto; border:1px solid #444; padding:6px; border-radius:4px;">
|
||||||
|
<!-- Populated by JS -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="installPrintersButton"
|
||||||
|
class="btn btn-success"
|
||||||
|
style="margin-top:8px;"
|
||||||
|
onclick="installSelectedPrinters()">
|
||||||
|
Install Selected Printers
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tiny inline bridge: pass dynamic data, then inline JS from Gitea -->
|
||||||
|
<script>
|
||||||
|
window.SAMY_TASKS = {{tasksJsAll}};
|
||||||
|
window.SAMY_DEFAULT_PAGE = "{{defaultPage}}";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
{{JsContent}}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Floating button group -->
|
||||||
|
<div class="fixed-buttons">
|
||||||
|
<button class="exit-button" onclick="endSession()">Exit</button>
|
||||||
|
<button class="run-button" onclick="triggerInstall()">Run Selected</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
691
samy.js
Normal file
691
samy.js
Normal file
@@ -0,0 +1,691 @@
|
|||||||
|
// Use globals provided by the PowerShell-generated HTML bridge
|
||||||
|
const tasks = (window.SAMY_TASKS || []);
|
||||||
|
const defaultPage = (window.SAMY_DEFAULT_PAGE || "onboard");
|
||||||
|
|
||||||
|
let completedTasks = 0;
|
||||||
|
let totalTasks = 0;
|
||||||
|
|
||||||
|
// Progress / title handling
|
||||||
|
function setTotalTaskCount(count) {
|
||||||
|
totalTasks = count;
|
||||||
|
completedTasks = 0;
|
||||||
|
updateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
function logProgress(label, isSuccess) {
|
||||||
|
const statusBox = document.getElementById("status-box");
|
||||||
|
completedTasks++;
|
||||||
|
updateTitle();
|
||||||
|
|
||||||
|
const msg = isSuccess
|
||||||
|
? ` ${completedTasks}/${totalTasks} done: ${label}`
|
||||||
|
: ` ${completedTasks}/${totalTasks} failed: ${label}`;
|
||||||
|
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.style.color = isSuccess ? "lime" : "red";
|
||||||
|
div.textContent = msg;
|
||||||
|
statusBox?.appendChild(div);
|
||||||
|
|
||||||
|
if (completedTasks === totalTasks) {
|
||||||
|
const finalMsg = document.createElement("div");
|
||||||
|
finalMsg.style.marginTop = "10px";
|
||||||
|
finalMsg.innerHTML = `<strong> All tasks completed (${completedTasks}/${totalTasks})</strong>`;
|
||||||
|
statusBox?.appendChild(finalMsg);
|
||||||
|
|
||||||
|
document.title = ` ScriptMonkey - Complete (${completedTasks}/${totalTasks})`;
|
||||||
|
|
||||||
|
const sound = new Audio(
|
||||||
|
"data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAESsAACJWAAACABAAZGF0YQAAAAA="
|
||||||
|
);
|
||||||
|
sound.play().catch(() => {});
|
||||||
|
flashTitle(document.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTitle() {
|
||||||
|
document.title = `ScriptMonkey - ${completedTasks}/${totalTasks} Done`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function flashTitle(finalTitle) {
|
||||||
|
let flashes = 0;
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
document.title = document.title === "" ? finalTitle : "";
|
||||||
|
flashes++;
|
||||||
|
if (flashes >= 10) {
|
||||||
|
clearInterval(interval);
|
||||||
|
document.title = finalTitle;
|
||||||
|
}
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// Tab Navigation
|
||||||
|
// =======================================================================
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const tabButtons = document.querySelectorAll(".tab-button");
|
||||||
|
const tabContents = document.querySelectorAll(".tab-content");
|
||||||
|
|
||||||
|
if (!tabButtons?.length || !tabContents?.length) {
|
||||||
|
console.error("ScriptMonkey: no tab buttons or tab contents found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tabButtons.forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => {
|
||||||
|
tabButtons.forEach((b) => b.classList.remove("active"));
|
||||||
|
tabContents.forEach((c) => c.classList.remove("active"));
|
||||||
|
|
||||||
|
btn.classList.add("active");
|
||||||
|
const targetId = btn.dataset.tab;
|
||||||
|
const target = document.getElementById(targetId);
|
||||||
|
if (target) target.classList.add("active");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Default tab from PS (onboard/offboard/tweaks/SVSApps)
|
||||||
|
const defaultTabId = `${defaultPage}Tab`;
|
||||||
|
const defaultBtn = document.querySelector(`.tab-button[data-tab='${defaultTabId}']`);
|
||||||
|
const defaultTab = document.getElementById(defaultTabId);
|
||||||
|
if (defaultBtn) defaultBtn.classList.add("active");
|
||||||
|
if (defaultTab) defaultTab.classList.add("active");
|
||||||
|
});
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// Onboarding: Select-all left/right columns
|
||||||
|
// =======================================================================
|
||||||
|
function toggleColumn(col) {
|
||||||
|
const master = document.getElementById(
|
||||||
|
`selectAll${col[0].toUpperCase() + col.slice(1)}Checkbox`
|
||||||
|
);
|
||||||
|
const children = document.querySelectorAll(
|
||||||
|
`#onboardTab input[type=checkbox][data-column="${col}"]`
|
||||||
|
);
|
||||||
|
|
||||||
|
children.forEach((cb) => {
|
||||||
|
cb.checked = master.checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
children.forEach((cb) => {
|
||||||
|
cb.dispatchEvent(new Event("change"));
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelectAll(col) {
|
||||||
|
const master = document.getElementById(
|
||||||
|
`selectAll${col[0].toUpperCase() + col.slice(1)}Checkbox`
|
||||||
|
);
|
||||||
|
const children = document.querySelectorAll(
|
||||||
|
`#onboardTab input[type=checkbox][data-column=${col}]`
|
||||||
|
);
|
||||||
|
|
||||||
|
master.checked = Array.from(children).every((cb) => cb.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
["left", "right"].forEach((col) => {
|
||||||
|
document
|
||||||
|
.querySelectorAll(`#onboardTab input[type=checkbox][data-column=${col}]`)
|
||||||
|
.forEach((cb) =>
|
||||||
|
cb.addEventListener("change", () => updateSelectAll(col))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// Off-boarding Select All
|
||||||
|
// =======================================================================
|
||||||
|
function toggleOffboardAll() {
|
||||||
|
const master = document.getElementById("offboardSelectAll");
|
||||||
|
const children = document.querySelectorAll(
|
||||||
|
"#offboardTab input[type=checkbox]:not(#offboardSelectAll)"
|
||||||
|
);
|
||||||
|
|
||||||
|
children.forEach((cb) => {
|
||||||
|
cb.checked = master.checked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOffboardSelectAll() {
|
||||||
|
const master = document.getElementById("offboardSelectAll");
|
||||||
|
if (!master) return;
|
||||||
|
|
||||||
|
const children = document.querySelectorAll(
|
||||||
|
"#offboardTab input[type=checkbox]:not(#offboardSelectAll)"
|
||||||
|
);
|
||||||
|
if (!children.length) {
|
||||||
|
master.checked = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
master.checked = Array.from(children).every((cb) => cb.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const offChildren = document.querySelectorAll(
|
||||||
|
"#offboardTab input[type=checkbox]:not(#offboardSelectAll)"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!offChildren?.length) return;
|
||||||
|
|
||||||
|
offChildren.forEach((cb) =>
|
||||||
|
cb.addEventListener("change", updateOffboardSelectAll)
|
||||||
|
);
|
||||||
|
updateOffboardSelectAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// DattoRMM options + Enter key handling
|
||||||
|
// =======================================================================
|
||||||
|
function toggleDattoRMMOptions() {
|
||||||
|
const master = document.getElementById("installDattoRMM");
|
||||||
|
const container = document.getElementById("installDattoRMMOptionsContainer");
|
||||||
|
if (!container) return;
|
||||||
|
const checked = master?.checked;
|
||||||
|
container.style.display = checked ? "block" : "none";
|
||||||
|
container
|
||||||
|
.querySelectorAll('input[type="checkbox"]')
|
||||||
|
.forEach((cb) => (cb.checked = checked));
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const master = document.getElementById("installDattoRMM");
|
||||||
|
if (master) {
|
||||||
|
master.addEventListener("change", toggleDattoRMMOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordField = document.getElementById("Password");
|
||||||
|
const goButton = document.querySelector("button[onclick='fetchSites()']");
|
||||||
|
|
||||||
|
if (passwordField && goButton) {
|
||||||
|
passwordField.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
goButton.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const siteDropdown = document.getElementById("dattoDropdown");
|
||||||
|
const runButton = document.querySelector(".run-button");
|
||||||
|
|
||||||
|
if (siteDropdown && runButton) {
|
||||||
|
siteDropdown.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter" && siteDropdown.value) {
|
||||||
|
runButton.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// Fetch Sites handler (calls /getpw)
|
||||||
|
// =======================================================================
|
||||||
|
async function fetchSites() {
|
||||||
|
const pwdInput = document.getElementById("Password");
|
||||||
|
const pwd = (pwdInput?.value ?? "").trim(); // allow blank, normalize whitespace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const dropdown = document.getElementById("dattoDropdown");
|
||||||
|
dropdown.innerHTML = '<option disabled selected>Loading sites...</option>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch("/getpw", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ password: pwd }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!resp.ok) throw "HTTP " + resp.status;
|
||||||
|
|
||||||
|
const sites = await resp.json();
|
||||||
|
|
||||||
|
if (!Array.isArray(sites) || sites.length === 0) {
|
||||||
|
dropdown.innerHTML =
|
||||||
|
'<option disabled selected>No sites returned</option>';
|
||||||
|
alert("No Datto sites returned. Verify credentials/allowlist, or try again in a moment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdown.innerHTML = "";
|
||||||
|
|
||||||
|
sites.forEach((site) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = site.UID;
|
||||||
|
option.textContent = site.Name;
|
||||||
|
dropdown.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("dattoRmmContainer").style.display = "block";
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
dropdown.innerHTML =
|
||||||
|
'<option disabled selected>Error loading sites</option>';
|
||||||
|
alert("Failed to fetch sites. Check password or confirm your public IP is allowlisted.");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// Printer management (Devices tab)
|
||||||
|
// =======================================================================
|
||||||
|
let allPrinters = [];
|
||||||
|
|
||||||
|
// POST /getprinters with password from Devices tab
|
||||||
|
async function fetchPrinters() {
|
||||||
|
const pwdInput = document.getElementById("PrinterPassword");
|
||||||
|
|
||||||
|
const pwd = (pwdInput?.value ?? ""); // allow blank
|
||||||
|
|
||||||
|
const clientContainer = document.getElementById("printerClientContainer");
|
||||||
|
const listContainer = document.getElementById("printerListContainer");
|
||||||
|
const dropdown = document.getElementById("printerClientDropdown");
|
||||||
|
const checkboxContainer = document.getElementById("printerCheckboxContainer");
|
||||||
|
|
||||||
|
if (dropdown) {
|
||||||
|
dropdown.innerHTML = '<option disabled selected>Loading clients...</option>';
|
||||||
|
}
|
||||||
|
if (checkboxContainer) {
|
||||||
|
checkboxContainer.innerHTML = "";
|
||||||
|
}
|
||||||
|
if (clientContainer) clientContainer.style.display = "none";
|
||||||
|
if (listContainer) listContainer.style.display = "none";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch("/getprinters", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ password: pwd }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!resp.ok) throw new Error("HTTP " + resp.status);
|
||||||
|
|
||||||
|
const data = await resp.json();
|
||||||
|
allPrinters = Array.isArray(data) ? data : [];
|
||||||
|
|
||||||
|
if (!allPrinters.length) {
|
||||||
|
alert("No printers returned. Verify credentials/allowlist, or try again in a moment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build unique sorted ClientCode list
|
||||||
|
const codes = [...new Set(allPrinters.map((p) => p.ClientCode))].sort();
|
||||||
|
|
||||||
|
dropdown.innerHTML = "";
|
||||||
|
const defaultOpt = new Option("Select a client...", "", true, true);
|
||||||
|
defaultOpt.disabled = true;
|
||||||
|
dropdown.appendChild(defaultOpt);
|
||||||
|
|
||||||
|
codes.forEach((code) => {
|
||||||
|
dropdown.appendChild(new Option(code, code));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (clientContainer) clientContainer.style.display = "block";
|
||||||
|
} catch (e) {
|
||||||
|
console.error("fetchPrinters error:", e);
|
||||||
|
if (dropdown) {
|
||||||
|
dropdown.innerHTML =
|
||||||
|
'<option disabled selected>Error loading clients</option>';
|
||||||
|
}
|
||||||
|
alert("Failed to fetch printers. Check password or confirm your public IP is allowlisted.");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPrintersForClient(clientCode) {
|
||||||
|
const container = document.getElementById("printerCheckboxContainer");
|
||||||
|
const listContainer = document.getElementById("printerListContainer");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
|
const printers = allPrinters.filter((p) => p.ClientCode === clientCode);
|
||||||
|
|
||||||
|
if (!printers.length) {
|
||||||
|
container.textContent = "No printers found for this client.";
|
||||||
|
if (listContainer) listContainer.style.display = "block";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printers.forEach((p, idx) => {
|
||||||
|
const id = `printer_${clientCode}_${idx}`;
|
||||||
|
const label = document.createElement("label");
|
||||||
|
label.style.display = "block";
|
||||||
|
label.style.marginBottom = "4px";
|
||||||
|
|
||||||
|
//Install-Checkbox
|
||||||
|
const cb = document.createElement("input");
|
||||||
|
cb.type = "checkbox";
|
||||||
|
cb.id = id;
|
||||||
|
|
||||||
|
// stash all fields we might need later
|
||||||
|
cb.dataset.clientCode = p.ClientCode;
|
||||||
|
cb.dataset.profileName = p.ProfileName;
|
||||||
|
cb.dataset.displayName = p.DisplayName;
|
||||||
|
cb.dataset.location = p.Location;
|
||||||
|
cb.dataset.address = p.Address;
|
||||||
|
cb.dataset.printServer = p.PrintServer;
|
||||||
|
cb.dataset.shareName = p.ShareName;
|
||||||
|
cb.dataset.driverName = p.DriverName;
|
||||||
|
cb.dataset.driverInfPath = p.DriverInfPath;
|
||||||
|
|
||||||
|
const nameText = p.DisplayName || p.ProfileName || "Unnamed printer";
|
||||||
|
const locText = p.Location || "Unknown location";
|
||||||
|
|
||||||
|
// Line 1: install checkbox + printer label
|
||||||
|
label.appendChild(cb);
|
||||||
|
label.appendChild(document.createTextNode(" "));
|
||||||
|
label.appendChild(
|
||||||
|
document.createTextNode(`${nameText} (${locText})`)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Line 2: radio for "Make default"
|
||||||
|
const defaultWrapper = document.createElement("div");
|
||||||
|
defaultWrapper.style.marginLeft = "24px";
|
||||||
|
defaultWrapper.style.fontSize = "0.85em";
|
||||||
|
defaultWrapper.style.opacity = "0.9";
|
||||||
|
|
||||||
|
const radio = document.createElement("input");
|
||||||
|
radio.type = "radio";
|
||||||
|
radio.name = "defaultPrinter";
|
||||||
|
radio.value = id; // associate default choice with this checkbox/printer
|
||||||
|
|
||||||
|
const radioLabel = document.createElement("span");
|
||||||
|
radioLabel.textContent = " Make default";
|
||||||
|
|
||||||
|
defaultWrapper.appendChild(radio);
|
||||||
|
defaultWrapper.appendChild(radioLabel);
|
||||||
|
|
||||||
|
label.appendChild(document.createElement("br"));
|
||||||
|
label.appendChild(defaultWrapper);
|
||||||
|
|
||||||
|
container.appendChild(label);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (listContainer) listContainer.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installSelectedPrinters() {
|
||||||
|
const container = document.getElementById("printerCheckboxContainer");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const checked = container.querySelectorAll("input[type=checkbox]:checked");
|
||||||
|
if (!checked.length) {
|
||||||
|
alert("Please select at least one printer.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See which radio is checked for "Make default"
|
||||||
|
const defaultRadio = container.querySelector(
|
||||||
|
'input[type=radio][name="defaultPrinter"]:checked'
|
||||||
|
);
|
||||||
|
const defaultId = defaultRadio ? defaultRadio.value : null;
|
||||||
|
|
||||||
|
const selected = Array.from(checked).map((cb) => ({
|
||||||
|
ClientCode: cb.dataset.clientCode,
|
||||||
|
ProfileName: cb.dataset.profileName,
|
||||||
|
DisplayName: cb.dataset.displayName,
|
||||||
|
Location: cb.dataset.location,
|
||||||
|
Address: cb.dataset.address,
|
||||||
|
PrintServer: cb.dataset.printServer,
|
||||||
|
ShareName: cb.dataset.shareName,
|
||||||
|
DriverName: cb.dataset.driverName,
|
||||||
|
DriverInfPath: cb.dataset.driverInfPath,
|
||||||
|
// Only the printer whose checkbox id matches the selected radio gets SetAsDefault=true
|
||||||
|
SetAsDefault: defaultId !== null && cb.id === defaultId,
|
||||||
|
}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch("/installprinters", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ printers: selected }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!resp.ok) throw new Error("HTTP " + resp.status);
|
||||||
|
|
||||||
|
const result = await resp.json().catch(() => null);
|
||||||
|
|
||||||
|
console.log("Printer install result:", result);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error("installSelectedPrinters error:", e);
|
||||||
|
alert("Failed to trigger printer install.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// Run Selected (main trigger)
|
||||||
|
// =======================================================================
|
||||||
|
async function triggerInstall() {
|
||||||
|
const runBtn = document.querySelector(".run-button");
|
||||||
|
if (!runBtn) return;
|
||||||
|
|
||||||
|
runBtn.disabled = true;
|
||||||
|
|
||||||
|
const statusBox = document.getElementById("status-box");
|
||||||
|
if (statusBox) statusBox.innerHTML = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Figure out which standard tasks are checked
|
||||||
|
const checkedTasks = tasks.filter((t) => {
|
||||||
|
if (["installDattoRMM", "installSVSMSPModule", "renameComputer"].includes(t.id)) return false;
|
||||||
|
const cb = document.getElementById(t.id);
|
||||||
|
return cb && cb.checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Rename checkbox / textbox
|
||||||
|
const renameCB = document.getElementById("renameComputer");
|
||||||
|
const newNameInput = document.getElementById("txtNewComputerName");
|
||||||
|
|
||||||
|
// Count how many "extra" tasks (rename) we're doing
|
||||||
|
let extraTasks = 0;
|
||||||
|
if (renameCB && renameCB.checked) {
|
||||||
|
extraTasks = 1; // treat rename as one task in the progress counter
|
||||||
|
}
|
||||||
|
|
||||||
|
setTotalTaskCount(checkedTasks.length + extraTasks);
|
||||||
|
|
||||||
|
// 1. DattoRMM first
|
||||||
|
const dattoCB = document.getElementById("installDattoRMM");
|
||||||
|
if (dattoCB && dattoCB.checked) {
|
||||||
|
const sub = Array.from(
|
||||||
|
document.querySelectorAll(".sub-option-installDattoRMM:checked")
|
||||||
|
).map((x) => x.value);
|
||||||
|
const dropdown = document.getElementById("dattoDropdown");
|
||||||
|
const uid = dropdown?.value;
|
||||||
|
const name = dropdown?.selectedOptions?.[0]?.text || "Datto";
|
||||||
|
|
||||||
|
if (!uid) {
|
||||||
|
alert("Please select a Datto RMM site before running.");
|
||||||
|
logProgress("Install DattoRMM (no site selected)", false);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await fetch("/installDattoRMM", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ checkedValues: sub, UID: uid, Name: name }),
|
||||||
|
});
|
||||||
|
logProgress("Install DattoRMM", true);
|
||||||
|
} catch (e) {
|
||||||
|
logProgress("Install DattoRMM", false);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. SVSMSP module second
|
||||||
|
const svsCB = document.getElementById("installSVSMSPModule");
|
||||||
|
if (svsCB && svsCB.checked) {
|
||||||
|
try {
|
||||||
|
await fetch("/installSVSMSPModule", { method: "GET" });
|
||||||
|
logProgress("Install SVSMSP Module", true);
|
||||||
|
} catch (e) {
|
||||||
|
logProgress("Install SVSMSP Module", false);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Remaining tasks
|
||||||
|
for (const t of tasks) {
|
||||||
|
if (["installDattoRMM", "installSVSMSPModule", "renameComputer"].includes(t.id)) continue;
|
||||||
|
|
||||||
|
const cb = document.getElementById(t.id);
|
||||||
|
if (!cb || !cb.checked) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch(t.handler, { method: "GET" });
|
||||||
|
logProgress(t.label || t.id, true);
|
||||||
|
} catch (e) {
|
||||||
|
logProgress(t.label || t.id, false);
|
||||||
|
console.error(`Error running ${t.id}:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Rename computer (LAST)
|
||||||
|
if (renameCB && renameCB.checked && newNameInput) {
|
||||||
|
const newName = newNameInput.value.trim();
|
||||||
|
|
||||||
|
// Same basic rules you'll enforce server-side
|
||||||
|
const nameIsValid =
|
||||||
|
newName.length > 0 &&
|
||||||
|
newName.length <= 15 &&
|
||||||
|
/^[A-Za-z0-9-]+$/.test(newName);
|
||||||
|
|
||||||
|
if (!nameIsValid) {
|
||||||
|
alert(
|
||||||
|
"Invalid computer name. Must be 1-15 characters and only letters, numbers, and hyphens."
|
||||||
|
);
|
||||||
|
// still mark it as a failed task so progress reaches 100%
|
||||||
|
logProgress("Rename computer", false);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await fetch("/renameComputer", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ newName }),
|
||||||
|
});
|
||||||
|
logProgress("Rename computer", true);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error calling /renameComputer:", e);
|
||||||
|
logProgress("Rename computer", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("triggerInstall fatal error:", e);
|
||||||
|
} finally {
|
||||||
|
runBtn.disabled = false;
|
||||||
|
if (totalTasks > 0) {
|
||||||
|
console.info(
|
||||||
|
`[Info] All tasks completed (${completedTasks}/${totalTasks})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Best-effort notification to the server
|
||||||
|
try {
|
||||||
|
await fetch("/tasksCompleted", { method: "POST" });
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Could not notify server about completion:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// Shutdown handler (Exit button & window close)
|
||||||
|
// =======================================================================
|
||||||
|
function endSession() {
|
||||||
|
fetch("/quit", { method: "GET" }).finally(() => window.close());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub-options auto-toggle, tagline rotation, and beforeunload hook
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// Sub-option containers
|
||||||
|
const tasksWithSubOptions = document.querySelectorAll(
|
||||||
|
'[id$="OptionsContainer"]'
|
||||||
|
);
|
||||||
|
|
||||||
|
tasksWithSubOptions.forEach((container) => {
|
||||||
|
const taskId = container.id.replace("OptionsContainer", "");
|
||||||
|
const masterCheckbox = document.getElementById(taskId);
|
||||||
|
if (!masterCheckbox) return;
|
||||||
|
|
||||||
|
function updateVisibility() {
|
||||||
|
const checked = masterCheckbox.checked;
|
||||||
|
container.style.display = checked ? "block" : "none";
|
||||||
|
container
|
||||||
|
.querySelectorAll('input[type="checkbox"]')
|
||||||
|
.forEach((cb) => (cb.checked = checked));
|
||||||
|
|
||||||
|
if (taskId === "installDattoRMM") {
|
||||||
|
const pwdBox = document.getElementById("PasswordContainer");
|
||||||
|
const rmmBox = document.getElementById("dattoRmmContainer");
|
||||||
|
if (pwdBox) pwdBox.style.display = checked ? "block" : "none";
|
||||||
|
if (rmmBox) rmmBox.style.display = checked ? "block" : "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
masterCheckbox.addEventListener("change", updateVisibility);
|
||||||
|
updateVisibility();
|
||||||
|
});
|
||||||
|
|
||||||
|
// NEW: Rename computer checkbox -> show/hide text box
|
||||||
|
const renameCheckbox = document.getElementById("renameComputer");
|
||||||
|
const renameBlock = document.getElementById("renameComputerBlock");
|
||||||
|
|
||||||
|
if (renameCheckbox && renameBlock) {
|
||||||
|
function updateRenameVisibility() {
|
||||||
|
renameBlock.style.display = renameCheckbox.checked ? "block" : "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
renameCheckbox.addEventListener("change", updateRenameVisibility);
|
||||||
|
updateRenameVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tagline rotation
|
||||||
|
const taglines = [
|
||||||
|
"Fast deployments, no monkey business.",
|
||||||
|
"Bananas for better builds.",
|
||||||
|
"Deploy without flinging code.",
|
||||||
|
"Tame your stack. Unleash the monkey.",
|
||||||
|
"Monkey see, monkey deploy.",
|
||||||
|
"Deploy smarter -- with a monkey on your team.",
|
||||||
|
"Don't pass the monkey -- let it deploy.",
|
||||||
|
"No more monkeying around. Stack handled.",
|
||||||
|
"Own your stack. But let the monkey do the work.",
|
||||||
|
"Why throw code when the monkey's got it?",
|
||||||
|
"Deployments so easy, a monkey could do it. Ours does.",
|
||||||
|
"Monkey in the stack, not on your back.",
|
||||||
|
];
|
||||||
|
|
||||||
|
const el = document.getElementById("tagline");
|
||||||
|
if (el) {
|
||||||
|
let idx = Math.floor(Math.random() * taglines.length);
|
||||||
|
el.textContent = taglines[idx];
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
idx = (idx + 1) % taglines.length;
|
||||||
|
el.textContent = taglines[idx];
|
||||||
|
}, 10_000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// printer dropdown
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const clientDropdown = document.getElementById("printerClientDropdown");
|
||||||
|
if (clientDropdown) {
|
||||||
|
clientDropdown.addEventListener("change", (e) => {
|
||||||
|
const code = e.target.value;
|
||||||
|
if (code) renderPrintersForClient(code);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// notify server on window close
|
||||||
|
window.addEventListener("beforeunload", () => {
|
||||||
|
fetch("/quit", { method: "GET", keepalive: true });
|
||||||
|
});
|
||||||
155
samy.tasks.json
Normal file
155
samy.tasks.json
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"Id": "setSVSPowerplan",
|
||||||
|
"Name": "setSVSPowerplan",
|
||||||
|
"Label": "Set SVS Powerplan",
|
||||||
|
"HandlerFn": "Invoke-SetSVSPowerPlan",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "left",
|
||||||
|
"Tooltip": "Applies the SVS power configuration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "installSVSMSPModule",
|
||||||
|
"Name": "installSVSMSPModule",
|
||||||
|
"Label": "Install SVSMSP Module",
|
||||||
|
"HandlerFn": "Invoke-InstallSVSMSP",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "left",
|
||||||
|
"Tooltip": "Installs or updates the SVSMSP toolkit module"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "installCyberQP",
|
||||||
|
"Name": "installCyberQP",
|
||||||
|
"Label": "Install CyberQP",
|
||||||
|
"HandlerFn": "Invoke-InstallCyberQP",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "installHelpDesk",
|
||||||
|
"Name": "installHelpDesk",
|
||||||
|
"Label": "Install HelpDesk",
|
||||||
|
"HandlerFn": "Invoke-InstallHelpDesk",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "installThreatLocker",
|
||||||
|
"Name": "installThreatLocker",
|
||||||
|
"Label": "Install ThreatLocker",
|
||||||
|
"HandlerFn": "Invoke-InstallThreatLocker",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "installRocketCyber",
|
||||||
|
"Name": "installRocketCyber",
|
||||||
|
"Label": "Install RocketCyber",
|
||||||
|
"HandlerFn": "Invoke-InstallRocketCyber",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "installDattoRMM",
|
||||||
|
"Name": "installDattoRMM",
|
||||||
|
"Label": "Install DattoRMM",
|
||||||
|
"HandlerFn": "Invoke-InstallDattoRMM",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "left",
|
||||||
|
"Tooltip": "Fetches sites then installs/configures Datto RMM",
|
||||||
|
"SubOptions": [
|
||||||
|
{ "Value": "inputVar", "Label": "Copy Site Variables" },
|
||||||
|
{ "Value": "rmm", "Label": "Install RMM Agent" },
|
||||||
|
{ "Value": "exe", "Label": "Download Executable" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Id": "enableBitLocker",
|
||||||
|
"Name": "EnableBitLocker",
|
||||||
|
"Label": "Enable BitLocker",
|
||||||
|
"HandlerFn": "Set-SVSBitLocker",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "right",
|
||||||
|
"Tooltip": "Enables BitLocker drive encryption"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "setEdgeDefaultSearch",
|
||||||
|
"Name": "setedgedefaultsearch",
|
||||||
|
"Label": "Set Edge Default Search",
|
||||||
|
"HandlerFn": "Invoke-SetEdgeDefaultSearchEngine",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "right",
|
||||||
|
"Tooltip": "Will configure Edge to use Google as default search provider"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "renameComputer",
|
||||||
|
"Name": "renameComputer",
|
||||||
|
"Label": "Rename Computer",
|
||||||
|
"HandlerFn": "Invoke-RenameComputer",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "right",
|
||||||
|
"Tooltip": "Renames the device (reboot required)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "disableAnimations",
|
||||||
|
"Name": "disableAnimations",
|
||||||
|
"Label": "Disable Animations",
|
||||||
|
"HandlerFn": "Disable-Animations",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "right",
|
||||||
|
"Tooltip": "Disables Windows UI animations for performance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "wingetChrome",
|
||||||
|
"Name": "wingetChrome",
|
||||||
|
"Label": "Google Chrome",
|
||||||
|
"HandlerFn": "Invoke-InstallChrome",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "right"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "wingetAcrobat",
|
||||||
|
"Name": "wingetAcrobat",
|
||||||
|
"Label": "Adobe Acrobat Reader (64-bit)",
|
||||||
|
"HandlerFn": "Invoke-InstallAcrobat",
|
||||||
|
"Page": "onboard",
|
||||||
|
"Column": "right"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Id": "offUninstallCyberQP",
|
||||||
|
"Name": "offUninstallCyberQP",
|
||||||
|
"Label": "Uninstall CyberQP",
|
||||||
|
"HandlerFn": "Invoke-UninstallCyberQP",
|
||||||
|
"Page": "offboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "offUninstallHelpDesk",
|
||||||
|
"Name": "offUninstallHelpDesk",
|
||||||
|
"Label": "Uninstall HelpDesk",
|
||||||
|
"HandlerFn": "Invoke-UninstallHelpDesk",
|
||||||
|
"Page": "offboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "offUninstallThreatLocker",
|
||||||
|
"Name": "offUninstallThreatLocker",
|
||||||
|
"Label": "Uninstall ThreatLocker",
|
||||||
|
"HandlerFn": "Invoke-UninstallThreatLocker",
|
||||||
|
"Page": "offboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "offUninstallRocketCyber",
|
||||||
|
"Name": "offUninstallRocketCyber",
|
||||||
|
"Label": "Uninstall RocketCyber",
|
||||||
|
"HandlerFn": "Invoke-UninstallRocketCyber",
|
||||||
|
"Page": "offboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "offCleanupSVSMSPModule",
|
||||||
|
"Name": "offCleanupSVSMSPModule",
|
||||||
|
"Label": "Cleanup SVSMSP Toolkit",
|
||||||
|
"HandlerFn": "Invoke-CleanupSVSMSP",
|
||||||
|
"Page": "offboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user