diff --git a/Scriptmonkey_Beta.ps1 b/Scriptmonkey_Beta.ps1 deleted file mode 100644 index 8b99a3a..0000000 --- a/Scriptmonkey_Beta.ps1 +++ /dev/null @@ -1,2549 +0,0 @@ -#region changes to be done - -#endregion changes to be done - -## Last changes made should fix the issues we had wen running thi in Windows 11 25H2 - -<# -.SYNOPSIS - ScriptMonkey - MSP client onboarding/offboarding toolkit with a user interface, - and optional silent install of the SVSMSP toolkit and headless DattoRMM deployment. - -.DESCRIPTION - Install-DattoRMM is a single, unified toolkit for Datto RMM operations. It can be used - interactively or via HTTP endpoints, and includes built-in validation and error trapping. - - Key features: - - Credential retrieval - securely fetches ApiUrl, ApiKey, and ApiSecretKey from a webhook. - - OAuth management - automatically acquires and refreshes bearer tokens over TLS. - - Site list fetching - returns the list of RMM sites; validates OutputFile to .csv or .json. - - Site list saving - writes fetched site list to the user's Desktop as CSV or JSON. - - Registry variable push - writes site-specific variables under HKLM:\Software\SVS\Deployment. - - Agent download & install - downloads the Datto RMM agent installer and launches it. - - Installer archiving - saves a copy of the downloaded installer to C:\Temp. - - HTTP endpoints - exposes /getpw and /installDattoRMM handlers, each wrapped in try/catch - to log errors and return proper HTTP 500 responses on failure. - - Idempotent & WhatIf support - uses ShouldProcess for safe, testable agent installs. - - Throughout, secrets are never written to logs or console, and all operations produce - clear success/failure messages via Write-LogHybrid. - - -.PARAMETER UseWebhook - Switch that forces credential retrieval from the webhook at WebhookUrl using WebhookPassword. - When omitted, you must supply ApiUrl, ApiKey, and ApiSecretKey directly. - -.PARAMETER WebhookPassword - Password to authenticate to the credentials-fetch webhook. Mandatory when -UseWebhook is set. - -.PARAMETER WebhookUrl - URL of the credentials webhook endpoint. Defaults to $Global:DattoWebhookUrl. - -.PARAMETER ApiUrl - Direct Datto RMM API base URL (used if not fetching from webhook). - -.PARAMETER ApiKey - Direct Datto RMM API key (used if not fetching from webhook). - -.PARAMETER ApiSecretKey - Direct Datto RMM secret (used if not fetching from webhook). - -.PARAMETER FetchSites - Switch to fetch the list of RMM sites and skip all install or variable-push actions. - -.PARAMETER SaveSitesList - Switch to save the fetched site list to the desktop as a file named by OutputFile. - Must be used together 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 PushSiteVars - Switch to fetch site-specific variables and write them under HKLM:\Software\SVS\Deployment. - -.PARAMETER InstallRMM - Switch to download and launch the Datto RMM agent installer for the specified site. - -.PARAMETER SaveCopy - Switch to save a copy of the downloaded Datto RMM installer into C:\Temp. - -.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. - -.EXAMPLE - - & ([ScriptBlock]::Create( (iwr 'https://sm.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://sm.svstools.com' -UseBasicParsing).Content )) ` - -ApiUrl 'https://api.example.com' ` - -ApiKey 'YourApiKey' ` - -ApiSecretKey 'YourSecretKey' ` - -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://sm.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 'https://sm.svstools.com' -UseBasicParsing).Content )) ` - -ApiUrl 'https://api.example.com' ` - -ApiKey 'YourApiKey' ` - -ApiSecretKey 'YourSecretKey' ` - -SiteUID 'site-123' ` - -SiteName 'Acme Corp' ` - -SaveCopy - - # Downloads the RMM installer for “Acme Corp” and saves a copy under C:\Temp without running it. - -.EXAMPLE - & ([ScriptBlock]::Create( (iwr 'https://sm.svstools.com' -UseBasicParsing).Content )) ` - -ApiUrl 'https://api.example.com' ` - -ApiKey 'YourApiKey' ` - -ApiSecretKey 'YourSecretKey' ` - -SiteUID 'site-123' ` - -SiteName 'Acme Corp' ` - -InstallRMM ` - -WhatIf - - # Shows what would happen when installing the RMM agent, without making any changes. - -.EXAMPLE - & ([ScriptBlock]::Create((iwr 'sm.svstools.ca').Content )) -SilentInstall - -.EXAMPLE - & ([ScriptBlock]::Create((iwr 'sm.svstools.com').Content)) -Cleanup - -#> -#region Safely bypass Restricted Execution Policy -# ─── 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://sm.svstools.com' -UseBasicParsing | iex }" - } - - exit -} - -# ─── TLS and silent install defaults ─── -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$ProgressPreference = 'SilentlyContinue' -$ConfirmPreference = 'None' -#endregion Safely bypass Restricted Execution Policy - - function Invoke-ScriptMonkey { - - # ───────────────────────────────────────────────────────────────────────── - # PARAMETERS + GLOBAL VARIABLES - # ───────────────────────────────────────────────────────────────────────── - - [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, - - # ───────────────────────────────────────────────────────── - # Datto headless mode - - # ─── DattoFetch & DattoInstall share the webhook creds ───────────── - [Parameter(Mandatory,ParameterSetName='DattoFetch')] - [Parameter(Mandatory,ParameterSetName='DattoInstall')] - [switch]$UseWebhook, - - [Parameter(Mandatory,ParameterSetName='DattoFetch')] - [Parameter(Mandatory,ParameterSetName='DattoInstall')] - [string]$WebhookPassword, - - [string]$WebhookUrl = $Global:DattoWebhookUrl, - - # ─── only DattoFetch uses these ──────────────────────────────────── - [Parameter(ParameterSetName='DattoFetch')][switch]$FetchSites, - [Parameter(ParameterSetName='DattoFetch')][switch] $SaveSitesList, - [Parameter(ParameterSetName='DattoFetch')][ValidatePattern('\.csv$|\.json$')][string] $OutputFile = 'datto_sites.csv', - - # ─── only DattoInstall uses these ───────────────────────────────── - [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 - ) - - #region global variables - - # Listening port for HTTP UI - $Port = 8082 - - # Configurable endpoints - $Global:DattoWebhookUrl = 'https://automate.svstools.ca/webhook/svsmspkit' - - # Initialize a global in-memory log cache - - if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) { - $Global:LogCache = [System.Collections.ArrayList]::new() - } - - #endregion global variables - - #region SVS Module - - 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 - } - - - function Install-SVSMSP { - param ( - [switch] $Cleanup, - [switch] $InstallToolkit, - [Parameter(Mandatory = $false)][array] $AllModules = @(@{ ModuleName = "SVS_Toolkit" }, @{ ModuleName = "SVSMSP" }), - [Parameter(Mandatory = $false)][array] $AllRepositories = @(@{ RepoName = "SVS_Repo" }, @{ RepoName = "SVS_Toolkit" }), - [Parameter(Mandatory = $false)][string] $NewModuleName = "SVSMSP", - [Parameter(Mandatory = $false)][string] $NewRepositoryName = "SVS_Repo", - [Parameter(Mandatory = $false)][string] $NewRepositoryURL = "http://proget.svstools.ca:8083/nuget/SVS_Repo/" - ) - - function Perform-Cleanup { - Write-LogHybrid "Cleanup mode enabled. Starting cleanup..." "Info" "SVSModule" - - # Attempt to uninstall all versions of SVSMSP - try { - Uninstall-Module -Name SVSMSP -AllVersions -Force -ErrorAction Stop - Write-LogHybrid "SVSMSP module uninstalled from system." "Success" "SVSModule" -LogToEvent - } - catch { - # If no module was found, just warn and continue - if ($_.Exception.Message -match 'No match was found') { - Write-LogHybrid "No existing SVSMSP module found to uninstall." "Warning" "SVSModule" -LogToEvent - } - else { - Write-LogHybrid "Failed to uninstall SVSMSP: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - - # Remove the custom repository if registered - if (Get-PSRepository -Name SVS_Repo -ErrorAction SilentlyContinue) { - try { - Unregister-PSRepository -Name SVS_Repo -ErrorAction Stop - Write-LogHybrid "SVS_Repo repository unregistered." "Success" "SVSModule" -LogToEvent - } - catch { - Write-LogHybrid "Failed to unregister SVS_Repo: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - - # Finally, remove it from the current session if loaded - if (Get-Module -Name SVSMSP) { - try { - Remove-Module SVSMSP -Force -ErrorAction Stop - Write-LogHybrid "SVSMSP module removed from current session." "Success" "SVSModule" -LogToEvent - } - catch { - Write-LogHybrid "Failed to remove SVSMSP from session: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - } - - function Remove-SVSDeploymentRegKey { - $regKey = 'HKLM:\Software\SVS' - - try { - if (Test-Path $regKey) { - Remove-Item -Path $regKey -Recurse -Force - Write-LogHybrid "Registry key '$regKey' deleted successfully." "Success" "SVSModule" -LogToEvent - } - else { - Write-LogHybrid "Registry key '$regKey' not found; nothing to delete." "Info" "SVSModule" -LogToEvent - } - } - catch { - Write-LogHybrid "Failed to delete registry key '$regKey': $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - - - - function Perform-ToolkitInstallation { - Initialize-NuGetProvider - Perform-Cleanup - Write-LogHybrid "Registering repo $NewRepositoryName…" "Info" "SVSModule" -LogToEvent - if (-not (Get-PSRepository -Name $NewRepositoryName -ErrorAction SilentlyContinue)) { - Register-PSRepository -Name $NewRepositoryName -SourceLocation $NewRepositoryURL -InstallationPolicy Trusted - } - Write-LogHybrid "Installing module $NewModuleName…" "Info" "SVSModule" -LogToEvent - Install-Module -Name $NewModuleName -Repository $NewRepositoryName -Scope AllUsers -Force - Write-LogHybrid "Toolkit installation complete." "Success" "SVSModule" -LogToEvent - } - - Write-LogHybrid "Install-SVSMSP called" "Info" "SVSModule" -LogToEvent - if ($Cleanup) { - - Perform-Cleanup - Remove-SVSDeploymentRegKey - return - - } - if ($InstallToolkit) { - Perform-ToolkitInstallation; return - } - # default if no switch passed: - Perform-ToolkitInstallation - } - - #endregion SVS Module - - - - #region Write-Log - - # Fallback logger used when the SVSMSP module (and its Write-Log) is not available. - # Mirrors the behaviour of the toolkit Write-Log (v1.5), including: - # - Default EventLog: "SVSMSP Events" (out of Application log) - # - Default EventSource: "SVSMSP_Module" - # - Level-based Event IDs and console colors - # - Global in-memory log cache - # - One-time Event Log/source initialization with optional auto-elevation - function Write-LogHelper { - <# - .SYNOPSIS - Standardized logging utility with console/file output and Windows Event Log support, - including one-time event source initialization and optional auto-elevated creation - of a custom log/source. (Fallback implementation for ScriptMonkey.) - - .DESCRIPTION - Mirrors the SVSMSP toolkit Write-Log so that Write-LogHybrid can safely fall back - when the module isn't loaded. - - .NOTES - Default EventLog : SVSMSP Events - Default Source : SVSMSP_Module - #> - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [string]$Message, - - [ValidateSet("Info", "Warning", "Error", "Success", "General")] - [string]$Level = "Info", - - [string]$TaskCategory = "GeneralTask", - - [switch]$LogToEvent = $false, - - [string]$EventSource = "SVSMSP_Module", - - # Custom log name so you get your own node under "Applications and Services Logs" - [string]$EventLog = "SVSMSP Events", - - [int]$CustomEventID, - - [string]$LogFile, - - [switch]$PassThru - ) - - # ---------- Event ID / console color ---------- - $EventID = if ($CustomEventID) { $CustomEventID } else { - switch ($Level) { - "Info" { 1000 } - "Warning" { 2000 } - "Error" { 3000 } - "Success" { 4000 } - default { 1000 } - } - } - - $Color = switch ($Level) { - "Info" { "Cyan" } - "Warning" { "Yellow" } - "Error" { "Red" } - "Success" { "Green" } - default { "White" } - } - - $FormattedMessage = "[$Level] [$TaskCategory] $Message (Event ID: $EventID)" - Write-Host $FormattedMessage -ForegroundColor $Color - - # ---------- In-memory cache ---------- - if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) { - $Global:LogCache = [System.Collections.ArrayList]::new() - } - - $logEntry = [PSCustomObject]@{ - Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") - Level = $Level - Message = $FormattedMessage - } - [void]$Global:LogCache.Add($logEntry) - - # ---------- Optional file output ---------- - if ($LogFile) { - try { - "$($logEntry.Timestamp) $FormattedMessage" | - Out-File -FilePath $LogFile -Append -Encoding UTF8 - } - catch { - Write-Host "[Warning] Failed to write to log file: $($_.Exception.Message)" -ForegroundColor Yellow - } - } - - # ---------- Windows Event Log handling with one-time init + optional auto-elevate ---------- - if ($LogToEvent) { - - # Per-run cache for (LogName|Source) init state - if (-not $Global:EventSourceInitState) { - $Global:EventSourceInitState = @{} - } - - $EntryType = switch ($Level) { - "Info" { "Information" } - "Warning" { "Warning" } - "Error" { "Error" } - "Success" { "Information" } # treat success as info in Event Log - default { "Information" } - } - - $sourceKey = "$EventLog|$EventSource" - - if (-not $Global:EventSourceInitState.ContainsKey($sourceKey) -or - -not $Global:EventSourceInitState[$sourceKey]) { - - try { - # Only bother if the source doesn't already exist - if (-not [System.Diagnostics.EventLog]::SourceExists($EventSource)) { - - # Check if current token is admin - $isAdmin = $false - try { - $current = [Security.Principal.WindowsIdentity]::GetCurrent() - $principal = New-Object Security.Principal.WindowsPrincipal($current) - $isAdmin = $principal.IsInRole( - [Security.Principal.WindowsBuiltInRole]::Administrator - ) - } - catch { - $isAdmin = $false - } - - if ($isAdmin) { - # Elevated already: create log/source directly - New-EventLog -LogName $EventLog -Source $EventSource -ErrorAction Stop - } - else { - # Not elevated: run a one-off helper as admin to create log/source - $helperScript = @" -if (-not [System.Diagnostics.EventLog]::SourceExists('$EventSource')) { - New-EventLog -LogName '$EventLog' -Source '$EventSource' -} -"@ - - $tempPath = [System.IO.Path]::Combine( - $env:TEMP, - "Init_${EventLog}_$EventSource.ps1".Replace(' ', '_') - ) - - $helperScript | Set-Content -Path $tempPath -Encoding UTF8 - - try { - # This will trigger UAC prompt in interactive sessions - $null = Start-Process -FilePath "powershell.exe" ` - -ArgumentList "-ExecutionPolicy Bypass -File `"$tempPath`"" ` - -Verb RunAs -Wait -PassThru - } - catch { - Write-Host "[Warning] Auto-elevation to create Event Log '$EventLog' / source '$EventSource' failed: $($_.Exception.Message)" -ForegroundColor Yellow - } - finally { - Remove-Item -Path $tempPath -ErrorAction SilentlyContinue - } - } - } - - # Re-check after creation attempt - if ([System.Diagnostics.EventLog]::SourceExists($EventSource)) { - $Global:EventSourceInitState[$sourceKey] = $true - } - else { - $Global:EventSourceInitState[$sourceKey] = $false - Write-Host "[Warning] Event source '$EventSource' does not exist and could not be created. Skipping Event Log write." -ForegroundColor Yellow - } - } - catch { - Write-Host "[Warning] Failed to initialize Event Log '$EventLog' / source '$EventSource': $($_.Exception.Message)" -ForegroundColor Yellow - $Global:EventSourceInitState[$sourceKey] = $false - } - } - - # Only write if initialization succeeded - if ($Global:EventSourceInitState[$sourceKey]) { - try { - $EventMessage = "TaskCategory: $TaskCategory | Message: $Message" - Write-EventLog -LogName $EventLog -Source $EventSource -EntryType $EntryType -EventId $EventID -Message $EventMessage - } - catch { - Write-Host "[Warning] Failed to write to Event Log: $($_.Exception.Message)" -ForegroundColor Yellow - } - } - } - # ------------------------------------------------------------------------------------------ - - if ($PassThru) { - return $logEntry - } - } - - # ───────────────────────────────────────────────────────────────────────── - # WRITE-LOG HYBRID - # Uses module Write-Log if present; otherwise falls back to Write-LogHelper. - # Defaults aligned with toolkit: - # EventSource = "SVSMSP_Module" - # EventLog = "SVSMSP Events" - # ───────────────────────────────────────────────────────────────────────── - function Write-LogHybrid { - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)] - [string]$Message, - - [ValidateSet("Info", "Warning", "Error", "Success", "General")] - [string]$Level = "Info", - - [string]$TaskCategory = "GeneralTask", - - [switch]$LogToEvent, - - [string]$EventSource = "SVSMSP_Module", - - [string]$EventLog = "SVSMSP Events", - - [int]$CustomEventID, - - [string]$LogFile, - - [switch]$PassThru, - - [ValidateSet("Black","DarkGray","Gray","White","Red","Green","Blue","Yellow","Magenta","Cyan")] - [string]$ForegroundColorOverride - ) - - $formatted = "[$Level] [$TaskCategory] $Message" - - # Build the common parameter set for forwarding into Write-Log / Write-LogHelper - $invokeParams = @{ - Message = $Message - Level = $Level - TaskCategory = $TaskCategory - LogToEvent = $LogToEvent - EventSource = $EventSource - EventLog = $EventLog - } - - if ($PSBoundParameters.ContainsKey('CustomEventID')) { - $invokeParams.CustomEventID = $CustomEventID - } - if ($PSBoundParameters.ContainsKey('LogFile')) { - $invokeParams.LogFile = $LogFile - } - if ($PassThru) { - $invokeParams.PassThru = $true - } - - if ($PSBoundParameters.ContainsKey('ForegroundColorOverride')) { - # 1) print to console with the override color - Write-Host $formatted -ForegroundColor $ForegroundColorOverride - - # 2) then forward the call (sans the override) to Write-Log or Write-LogHelper - if (Get-Command Write-Log -ErrorAction SilentlyContinue) { - Write-Log @invokeParams - } - else { - Write-LogHelper @invokeParams - } - } - else { - # No override: let Write-Log / Write-LogHelper handle everything (including console color) - if (Get-Command Write-Log -ErrorAction SilentlyContinue) { - Write-Log @invokeParams - } - else { - Write-LogHelper @invokeParams - } - } - } - - #endregion Write-Log - - - # This function is used as a fallback if the SVSMSP module is not installed - # Should change this "[string]$EventLog = "Application", => [string]$EventLog = "SVS Scripting", " - function Write-LogHelper { - [CmdletBinding()] - param( - [Parameter(Mandatory)][string]$Message, - [ValidateSet("Info","Warning","Error","Success","General")] - [string]$Level = "Info", - [string]$TaskCategory = "GeneralTask", - [switch]$LogToEvent, - [string]$EventSource = "Script Automation Monkey", - [string]$EventLog = "Application", - [int] $CustomEventID, - [string]$LogFile, - [switch]$PassThru - ) - - # ─── IDs & Colors ──────────────────────────────────────────────── - $idMap = @{ Info=1000; Warning=2000; Error=3000; Success=4000; General=1000 } - $colMap = @{ Info="Cyan"; Warning="Yellow"; Error="Red"; Success="Green"; General="White" } - $EventID = if ($PSBoundParameters.CustomEventID) { $CustomEventID } else { $idMap[$Level] } - $color = $colMap[$Level] - $fmt = "[$Level] [$TaskCategory] $Message (Event ID: $EventID)" - - # ─── Console Output ───────────────────────────────────────────── - Write-Host $fmt -ForegroundColor $color - - # ─── In-Memory Cache ───────────────────────────────────────────── - - # ─── In-Memory Cache ───────────────────────────────────────────── - if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) { - $Global:LogCache = [System.Collections.ArrayList]::new() - } - $Global:LogCache.Add([pscustomobject]@{ - Timestamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') - Level = $Level - Message = $fmt - }) | Out-Null - - - # ─── File Logging ──────────────────────────────────────────────── - if ($PSBoundParameters.LogFile) { - try { - "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')) $fmt" | - Out-File -FilePath $LogFile -Append -Encoding UTF8 - } - catch { - Write-Host "[Warning] File log failed: $_" -ForegroundColor Yellow - } - } - - # ─── Event Log ────────────────────────────────────────────────── - if ($LogToEvent) { - try { - # 1) Ensure your custom source/log exist - if (-not [System.Diagnostics.EventLog]::SourceExists($EventSource)) { - New-EventLog -LogName $EventLog -Source $EventSource -ErrorAction Stop - } - } catch { - Write-Host "[Warning] Could not create event log '$EventLog' or source '$EventSource': $($_.Exception.Message)" -ForegroundColor Yellow - return - } - - # 2) Map level to entry type - $entryType = if ($Level -in 'Warning','Error') { $Level } else { 'Information' } - - # 3) Write to the Windows event log - try { - Write-EventLog ` - -LogName $EventLog ` - -Source $EventSource ` - -EntryType $entryType ` - -EventID $EventID ` - -Message $fmt - } - catch { - Write-Host "[Warning] EventLog failed: $($_.Exception.Message)" -ForegroundColor Yellow - } - } - - if ($PassThru) { return $Global:LogCache[-1] } - } - - # ───────────────────────────────────────────────────────────────────────── - # WRITE-LOG HYBRID (single definition, chooses at runtime if we use the - # Write-Log from the module or the built-in Write-LogHelper funtions ) - # Should chanfge this "[string]$EventLog = "Application"," => "[string]$EventLog = "SVS Scripting"," - # ───────────────────────────────────────────────────────────────────────── - - function Write-LogHybrid { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)][string]$Message, - [ValidateSet("Info","Warning","Error","Success","General")] - [string]$Level = "Info", - [string]$TaskCategory = "GeneralTask", - [switch]$LogToEvent, - [string]$EventSource = "Script Automation Monkey", - [string]$EventLog = "Application", - [ValidateSet("Black","DarkGray","Gray","White","Red","Green","Blue","Yellow","Magenta","Cyan")] - [string]$ForegroundColorOverride - ) - - $formatted = "[$Level] [$TaskCategory] $Message" - - if ($PSBoundParameters.ContainsKey('ForegroundColorOverride')) { - # 1) print to console with the override color - Write-Host $formatted -ForegroundColor $ForegroundColorOverride - - # 2) then forward the call (sans the override) to Write-Log or Write-LogHelper - $invokeParams = @{ - Message = $Message - Level = $Level - TaskCategory = $TaskCategory - LogToEvent = $LogToEvent - EventSource = $EventSource - EventLog = $EventLog - } - - if (Get-Command Write-Log -ErrorAction SilentlyContinue) { - Write-Log @invokeParams - } - else { - Write-LogHelper @invokeParams - } - } - else { - # No override: let Write-Log / Write-LogHelper handle everything (including console color) - if (Get-Command Write-Log -ErrorAction SilentlyContinue) { - Write-Log ` - -Message $Message ` - -Level $Level ` - -TaskCategory $TaskCategory ` - -LogToEvent:$LogToEvent ` - -EventSource $EventSource ` - -EventLog $EventLog - } - else { - Write-LogHelper ` - -Message $Message ` - -Level $Level ` - -TaskCategory $TaskCategory ` - -LogToEvent:$LogToEvent ` - -EventSource $EventSource ` - -EventLog $EventLog - } - } - } - - - #endregion Write-Log - - #region building the Menus - - # 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 - - $Global:SamyTasks = @( - # On-Boarding, left column - @{ Id='setSVSPowerplan'; Name='setSVSPowerplan'; Label='Set SVS Powerplan'; HandlerFn='Handle-setSVSPowerPlan'; Page='onboard'; Column='left' }, - @{ Id='installSVSMSPModule'; Name='installSVSMSPModule'; Label='Install SVSMSP Module'; HandlerFn='Handle-InstallSVSMSP'; Page='onboard'; Column='left' }, - @{ Id='installCyberQP'; Name='installCyberQP'; Label='Install CyberQP'; HandlerFn='Handle-InstallCyberQP'; Page='onboard'; Column='left' }, - @{ Id='installSVSHelpDesk'; Name='installSVSHelpDesk'; Label='Install SVS HelpDesk'; HandlerFn='Handle-InstallSVSHelpDesk'; Page='onboard'; Column='left' }, - @{ Id='installThreatLocker'; Name='installThreatLocker'; Label='Install ThreatLocker'; HandlerFn='Handle-InstallThreatLocker'; Page='onboard'; Column='left' }, - @{ Id='installRocketCyber'; Name='installRocketCyber'; Label='Install RocketCyber'; HandlerFn='Handle-InstallRocketCyber'; Page='onboard'; Column='left' }, - @{ Id='installDattoRMM'; Name='installDattoRMM'; Label='Install DattoRMM'; HandlerFn='Handle-InstallDattoRMM'; Page='onboard'; Column='left'; - SubOptions= @( - @{ Value='inputVar'; Label='Copy Site Variables' }, - @{ Value='rmm'; Label='Install RMM Agent' }, - @{ Value='exe'; Label='Download Executable' } - ) - }, - - - # On-Boarding, right column (optional bits) - @{ Id='enableBitLocker'; Name='EnableBitLocker'; Label='Enable BitLocker'; HandlerFn='Set-SVSBitLocker'; Page='onboard'; Column='right' }, - @{ Id='setEdgeDefaultSearch';Name='setedgedefaultsearch';Label='Set Edge Default Search'; Tooltip='Will configure Edge to use Google as default search provider'; HandlerFn='set-EdgeDefaultSearchProvider';Page='onboard'; Column='right' }, - - # Off-Boarding - @{ Id='offUninstallCyberQP'; Name='offUninstallCyberQP'; Label='Uninstall CyberQP'; HandlerFn='Handle-UninstallCyberQP'; Page='offboard' }, - @{ Id='offUninstallSVSHelpDesk'; Name='offUninstallSVSHelpDesk'; Label='Uninstall SVS HelpDesk'; HandlerFn='Handle-UninstallSVSHelpDesk'; Page='offboard' }, - @{ Id='offUninstallThreatLocker'; Name='offUninstallThreatLocker'; Label='Uninstall ThreatLocker'; HandlerFn='Handle-UninstallThreatLocker'; Page='offboard' }, - @{ Id='offUninstallRocketCyber'; Name='offUninstallRocketCyber'; Label='Uninstall RocketCyber'; HandlerFn='Handle-UninstallRocketCyber'; Page='offboard' }, - @{ Id='offCleanupSVSMSPModule'; Name='offCleanupSVSMSPModule'; Label='Cleanup SVSMSP Toolkit'; HandlerFn='Handle-CleanupSVSMSP'; Page='offboard' }, - - # Tweaks - @{ Id='disableAnimations'; Name='disableAnimations'; Label='Disable Animations'; HandlerFn='Disable-Animations'; Page='tweaks' }, - - # SVS Apps - @{ Id='wingetLastpass'; Name='wingetLastpass'; Label='LastPass Desktop App'; HandlerFn='Install-WingetLastPass'; Page='SVSApps' }, - @{ Id='wingetChrome'; Name='wingetChrome'; Label='Google Chrome'; HandlerFn='Handle-InstallChrome'; Page='SVSApps' }, - @{ Id='wingetAcrobat'; Name='wingetAcrobat'; Label='Adobe Acrobat Reader (64-bit)'; HandlerFn='Handle-InstallAcrobat'; Page='SVSApps' } - - ) -Write-LogHybrid "Tasks by page: onboard=$( - ($Global:SamyTasks | Where-Object Page -eq 'onboard').Count -) offboard=$( - ($Global:SamyTasks | Where-Object Page -eq 'offboard').Count -) tweaks=$( - ($Global:SamyTasks | Where-Object Page -eq 'tweaks').Count -) apps=$( - ($Global:SamyTasks | Where-Object Page -eq 'SVSApps').Count -)" Info UI -LogToEvent - - #endregion building the Menus - - #region Build-Checkboxes - function Build-Checkboxes { - param( - [Parameter(Mandatory)][string]$Page, - [string]$Column - ) - - # Start with all tasks on the given page - $tasks = $Global:SamyTasks | Where-Object Page -EQ $Page - - # Only filter by Column when it actually matters (onboard left/right) - if (-not [string]::IsNullOrEmpty($Column)) { - $tasks = $tasks | Where-Object Column -EQ $Column - } - - ( - $tasks | - ForEach-Object { - $taskId = $_.Id - $tooltip = if ($_.PSObject.Properties.Name -contains 'Tooltip' -and $_.Tooltip) { - " title='$($_.Tooltip)'" - } else { '' } - - $html = "" - - if ($_.SubOptions) { - $subHtml = ( - $_.SubOptions | - ForEach-Object { - "" - } - ) -join "`n" - - $html += @" -
-"@ - } - - $html - } - ) -join "`n" - } # end function Build-checkboxes - - - #endregion Build-Checkboxes - - #region Get-ModuleVersionHtml - - ### Get SVSMSP module version to display in the UI - function Get-ModuleVersionHtml { - $mod = Get-Module -ListAvailable -Name SVSMSP | Sort-Object Version -Descending | Select-Object -First 1 - if ($mod) { - return "