Update src/samy.functions.ps1
This commit is contained in:
@@ -1,16 +1,157 @@
|
|||||||
|
#region Web: Remote Fetch Helpers
|
||||||
|
|
||||||
|
function Get-RemoteText {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Url
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$resp = Invoke-WebRequest -Uri $Url -UseBasicParsing -ErrorAction Stop
|
||||||
|
return $resp.Content
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-LogHybrid "Get-RemoteText failed for ${Url}: $($_.Exception.Message)" Warning UI -LogToEvent
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Web: Remote Fetch Helpers
|
||||||
|
|
||||||
|
#region Web: Router / Request Dispatch
|
||||||
|
|
||||||
|
function Invoke-TasksCompleted {
|
||||||
|
param($Context)
|
||||||
|
Write-LogHybrid "All UI-selected tasks processed" Info UI -LogToEvent
|
||||||
|
Send-Text $Context "Tasks completion acknowledged."
|
||||||
|
}
|
||||||
|
|
||||||
|
function Dispatch-Request {
|
||||||
|
param($Context)
|
||||||
|
|
||||||
|
if ($null -eq $Context -or $null -eq $Context.Request -or $null -eq $Context.Request.Url) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$path = $Context.Request.Url.AbsolutePath.TrimStart('/')
|
||||||
|
|
||||||
|
switch -Regex ($path) {
|
||||||
|
|
||||||
|
'^$' {
|
||||||
|
$html = Get-UIHtml -Page 'onboard'
|
||||||
|
Send-HTML $Context $html
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
'^samy\.js$' {
|
||||||
|
Send-RemoteAsset -Context $Context -Url $Script:SamyJsUrl -ContentType 'application/javascript; charset=utf-8'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
'^samy\.css$' {
|
||||||
|
Send-RemoteAsset -Context $Context -Url $Script:SamyCssUrl -ContentType 'text/css; charset=utf-8'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
'^SVS_logo\.svg$' {
|
||||||
|
Send-RemoteAsset -Context $Context -Url $Script:SamyTopLogoUrl -ContentType 'image/svg+xml'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
'^SAMY\.png$' {
|
||||||
|
Send-RemoteAsset -Context $Context -Url $Script:SamyBgLogoUrl -ContentType 'image/png'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
'^SVS_Favicon\.ico$' {
|
||||||
|
Send-RemoteAsset -Context $Context -Url $Script:SamyFaviconUrl -ContentType 'image/x-icon'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($path -eq 'quit') {
|
||||||
|
Write-LogHybrid "Shutdown requested" "Info" "Server" -LogToEvent
|
||||||
|
Send-Text $Context "Server shutting down."
|
||||||
|
$Global:Listener.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'tasksCompleted') { Invoke-TasksCompleted $Context; return }
|
||||||
|
if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'getpw') { Invoke-FetchSites $Context; return }
|
||||||
|
if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'renameComputer') { Invoke-RenameComputer $Context; return }
|
||||||
|
if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'getprinters') { Invoke-GetPrinters $Context; return }
|
||||||
|
if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'installprinters'){ Invoke-InstallPrinters $Context; return }
|
||||||
|
|
||||||
|
if ($path -in @('onboard', 'offboard', 'devices')) {
|
||||||
|
$html = Get-UIHtml -Page $path
|
||||||
|
Send-HTML $Context $html
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$task = $Global:SamyTasks | Where-Object Name -EQ $path | Select-Object -First 1
|
||||||
|
if ($task) {
|
||||||
|
$fn = Get-TaskHandlerName -Task $task
|
||||||
|
if ([string]::IsNullOrWhiteSpace($fn)) {
|
||||||
|
$Context.Response.StatusCode = 500
|
||||||
|
Send-Text $Context "Task '$($task.Label)' is missing a handler (HandlerFn/Handler/Fn/Name)."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = Get-Command -Name $fn -ErrorAction SilentlyContinue
|
||||||
|
if (-not $cmd) {
|
||||||
|
$Context.Response.StatusCode = 500
|
||||||
|
Send-Text $Context "Handler not found: $fn"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cmd.Parameters.ContainsKey('Context')) { & $fn -Context $Context }
|
||||||
|
else { & $fn }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$Context.Response.StatusCode = 404
|
||||||
|
Send-Text $Context '404 - Not Found'
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Always return something so clients don't hang
|
||||||
|
try {
|
||||||
|
$Context.Response.StatusCode = 500
|
||||||
|
$pos = if ($_.InvocationInfo) { $_.InvocationInfo.PositionMessage } else { "" }
|
||||||
|
$stk = if ($_.ScriptStackTrace) { $_.ScriptStackTrace } else { "" }
|
||||||
|
$msg = "Dispatch error: $($_.Exception.Message)`n$pos`n$stk"
|
||||||
|
$bytes = [Text.Encoding]::UTF8.GetBytes($msg)
|
||||||
|
$Context.Response.ContentType = 'text/plain; charset=utf-8'
|
||||||
|
$Context.Response.ContentLength64 = $bytes.Length
|
||||||
|
$Context.Response.OutputStream.Write($bytes, 0, $bytes.Length)
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try { $Context.Response.OutputStream.Close() } catch { }
|
||||||
|
try { $Context.Response.Close() } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Web: Router / Request Dispatch
|
||||||
|
|
||||||
|
#region Bootstrap: NuGet + PowerShellGet
|
||||||
|
|
||||||
function Initialize-NuGetProvider {
|
function Initialize-NuGetProvider {
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param()
|
param()
|
||||||
|
|
||||||
# Silent defaults
|
#region Bootstrap: Defaults / Non-interactive behavior
|
||||||
|
|
||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
$ProgressPreference = 'SilentlyContinue'
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
$ConfirmPreference = 'None'
|
$ConfirmPreference = 'None'
|
||||||
|
|
||||||
# Extra guardrails for "ShouldContinue" style prompts
|
|
||||||
$PSDefaultParameterValues['*:Confirm'] = $false
|
$PSDefaultParameterValues['*:Confirm'] = $false
|
||||||
|
|
||||||
# Ensure provider folders exist (CurrentUser and AllUsers locations)
|
#endregion Bootstrap: Defaults / Non-interactive behavior
|
||||||
|
|
||||||
|
#region Bootstrap: Ensure provider folders exist
|
||||||
|
|
||||||
$userProvPath = Join-Path $env:LOCALAPPDATA 'PackageManagement\ProviderAssemblies'
|
$userProvPath = Join-Path $env:LOCALAPPDATA 'PackageManagement\ProviderAssemblies'
|
||||||
$allProvPath = Join-Path ${env:ProgramFiles} 'PackageManagement\ProviderAssemblies'
|
$allProvPath = Join-Path ${env:ProgramFiles} 'PackageManagement\ProviderAssemblies'
|
||||||
|
|
||||||
@@ -20,34 +161,37 @@ function Initialize-NuGetProvider {
|
|||||||
New-Item -Path $p -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
New-Item -Path $p -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||||
Write-LogHybrid "Ensured provider folder exists: $p" Info Bootstrap -LogToEvent
|
Write-LogHybrid "Ensured provider folder exists: $p" Info Bootstrap -LogToEvent
|
||||||
}
|
}
|
||||||
} catch {
|
}
|
||||||
|
catch {
|
||||||
# AllUsers path can fail without admin. That's OK.
|
# AllUsers path can fail without admin. That's OK.
|
||||||
Write-LogHybrid "Could not create provider folder: $p ($($_.Exception.Message))" Warning Bootstrap -LogToEvent
|
Write-LogHybrid "Could not create provider folder: $p ($($_.Exception.Message))" Warning Bootstrap -LogToEvent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# 1) Install NuGet provider FIRST, silently, so Install-Module never prompts
|
#endregion Bootstrap: Ensure provider folders exist
|
||||||
|
|
||||||
|
#region Bootstrap: NuGet provider install/import
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$existing = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue |
|
$existing = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue |
|
||||||
Sort-Object Version -Descending | Select-Object -First 1
|
Sort-Object Version -Descending | Select-Object -First 1
|
||||||
|
|
||||||
if (-not $existing -or $existing.Version -lt [Version]'2.8.5.201') {
|
if (-not $existing -or $existing.Version -lt [Version]'2.8.5.201') {
|
||||||
|
|
||||||
Write-LogHybrid "Installing NuGet provider (min 2.8.5.201)..." Info Bootstrap -LogToEvent
|
Write-LogHybrid "Installing NuGet provider (min 2.8.5.201)..." Info Bootstrap -LogToEvent
|
||||||
|
|
||||||
# ForceBootstrap helps avoid interactive bootstrap prompts
|
|
||||||
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 `
|
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 `
|
||||||
-Force -ForceBootstrap -Confirm:$false `
|
-Force -ForceBootstrap -Confirm:$false `
|
||||||
-Scope CurrentUser -ErrorAction Stop | Out-Null
|
-Scope CurrentUser -ErrorAction Stop | Out-Null
|
||||||
|
|
||||||
$existing = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue |
|
$existing = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue |
|
||||||
Sort-Object Version -Descending | Select-Object -First 1
|
Sort-Object Version -Descending | Select-Object -First 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($existing) {
|
if ($existing) {
|
||||||
Import-PackageProvider -Name NuGet -Force -ErrorAction Stop | Out-Null
|
Import-PackageProvider -Name NuGet -Force -ErrorAction Stop | Out-Null
|
||||||
Write-LogHybrid "NuGet provider ready (v$($existing.Version))" Success Bootstrap -LogToEvent
|
Write-LogHybrid "NuGet provider ready (v$($existing.Version))" Success Bootstrap -LogToEvent
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
throw "NuGet provider still not available after install attempt."
|
throw "NuGet provider still not available after install attempt."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,12 +200,17 @@ function Initialize-NuGetProvider {
|
|||||||
throw
|
throw
|
||||||
}
|
}
|
||||||
|
|
||||||
# 2) Now it is safe to update / install modules without NuGet prompts
|
#endregion Bootstrap: NuGet provider install/import
|
||||||
|
|
||||||
|
#region Bootstrap: PackageManagement / PowerShellGet / PSGallery policy
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Import-Module PackageManagement -Force -ErrorAction SilentlyContinue | Out-Null
|
Import-Module PackageManagement -Force -ErrorAction SilentlyContinue | Out-Null
|
||||||
Import-Module PowerShellGet -Force -ErrorAction SilentlyContinue | Out-Null
|
Import-Module PowerShellGet -Force -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
|
||||||
$pkgMgmtVersion = (Get-Module PackageManagement -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Version
|
$pkgMgmtVersion = (Get-Module PackageManagement -ListAvailable |
|
||||||
|
Sort-Object Version -Descending | Select-Object -First 1).Version
|
||||||
|
|
||||||
if ($pkgMgmtVersion -and $pkgMgmtVersion -lt [Version]'1.3.1') {
|
if ($pkgMgmtVersion -and $pkgMgmtVersion -lt [Version]'1.3.1') {
|
||||||
Install-Module PackageManagement -Force -AllowClobber -Confirm:$false -ErrorAction Stop
|
Install-Module PackageManagement -Force -AllowClobber -Confirm:$false -ErrorAction Stop
|
||||||
Write-LogHybrid "Updated PackageManagement to latest version" Info Bootstrap -LogToEvent
|
Write-LogHybrid "Updated PackageManagement to latest version" Info Bootstrap -LogToEvent
|
||||||
@@ -72,7 +221,6 @@ function Initialize-NuGetProvider {
|
|||||||
Write-LogHybrid "Installed PowerShellGet module" Info Bootstrap -LogToEvent
|
Write-LogHybrid "Installed PowerShellGet module" Info Bootstrap -LogToEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
# Trust PSGallery (optional, but common)
|
|
||||||
$gallery = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue
|
$gallery = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue
|
||||||
if ($gallery -and $gallery.InstallationPolicy -ne 'Trusted') {
|
if ($gallery -and $gallery.InstallationPolicy -ne 'Trusted') {
|
||||||
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop
|
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop
|
||||||
@@ -81,48 +229,64 @@ function Initialize-NuGetProvider {
|
|||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-LogHybrid "PackageManagement/PowerShellGet setup failed: $($_.Exception.Message)" Warning Bootstrap -LogToEvent
|
Write-LogHybrid "PackageManagement/PowerShellGet setup failed: $($_.Exception.Message)" Warning Bootstrap -LogToEvent
|
||||||
# You can choose to throw here if you want hard-fail behavior
|
# Decide if you want to throw here or allow soft-fail.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion Bootstrap: PackageManagement / PowerShellGet / PSGallery policy
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Re-usable functions
|
#endregion Bootstrap: NuGet + PowerShellGet
|
||||||
|
|
||||||
|
#region Registry: Low-level helpers
|
||||||
|
|
||||||
function Set-RegistryValueInHkuRoot {
|
function Set-RegistryValueInHkuRoot {
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)] [string] $HkuRoot, # e.g. "Registry::HKEY_USERS\S-1-5-21-..."
|
[Parameter(Mandatory)] [string] $HkuRoot,
|
||||||
[Parameter(Mandatory)] [string] $RelativeKeyPath, # e.g. "Software\...\Explorer\Advanced"
|
[Parameter(Mandatory)] [string] $RelativeKeyPath,
|
||||||
[Parameter(Mandatory)] [string] $Name,
|
[Parameter(Mandatory)] [string] $Name,
|
||||||
[Parameter(Mandatory)] [ValidateSet('String','ExpandString','DWord','QWord','Binary','MultiString')] [string] $Type,
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateSet('String','ExpandString','DWord','QWord','Binary','MultiString')]
|
||||||
|
[string] $Type,
|
||||||
[Parameter(Mandatory)] $Value
|
[Parameter(Mandatory)] $Value
|
||||||
)
|
)
|
||||||
|
|
||||||
$k = Join-Path $HkuRoot $RelativeKeyPath
|
$k = Join-Path $HkuRoot $RelativeKeyPath
|
||||||
if (-not (Test-Path $k)) { New-Item -Path $k -Force | Out-Null }
|
if (-not (Test-Path $k)) { New-Item -Path $k -Force | Out-Null }
|
||||||
|
|
||||||
New-ItemProperty -Path $k -Name $Name -PropertyType $Type -Value $Value -Force -err
|
New-ItemProperty -Path $k -Name $Name -PropertyType $Type -Value $Value -Force -ErrorAction Stop | Out-Null
|
||||||
Stop | Out-Null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion Registry: Low-level helpers
|
||||||
|
|
||||||
|
#region Registry: Apply settings to Current User + Default User + Existing Profiles
|
||||||
|
|
||||||
function Set-RegistryValueForCurrentAndAllUsers {
|
function Set-RegistryValueForCurrentAndAllUsers {
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)] [string] $RelativeKeyPath,
|
[Parameter(Mandatory)] [string] $RelativeKeyPath,
|
||||||
[Parameter(Mandatory)] [string] $Name,
|
[Parameter(Mandatory)] [string] $Name,
|
||||||
[Parameter(Mandatory)] [ValidateSet('String','ExpandString','DWord','QWord','Binary','MultiString')] [string] $Type,
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateSet('String','ExpandString','DWord','QWord','Binary','MultiString')]
|
||||||
|
[string] $Type,
|
||||||
[Parameter(Mandatory)] $Value
|
[Parameter(Mandatory)] $Value
|
||||||
)
|
)
|
||||||
|
|
||||||
# 1) Current user (HKCU) when meaningful
|
#region HKCU: current user (best-effort)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$hkcuKey = "HKCU:\$RelativeKeyPath"
|
$hkcuKey = "HKCU:\$RelativeKeyPath"
|
||||||
if (-not (Test-Path $hkcuKey)) { New-Item -Path $hkcuKey -Force | Out-Null }
|
if (-not (Test-Path $hkcuKey)) { New-Item -Path $hkcuKey -Force | Out-Null }
|
||||||
New-ItemProperty -Path $hkcuKey -Name $Name -PropertyType $Type -Value $Value -Force | Out-Null
|
New-ItemProperty -Path $hkcuKey -Name $Name -PropertyType $Type -Value $Value -Force | Out-Null
|
||||||
} catch {
|
}
|
||||||
|
catch {
|
||||||
# Common during SYSTEM runs; ignore
|
# Common during SYSTEM runs; ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
# 2) Default User (future users)
|
#endregion HKCU: current user (best-effort)
|
||||||
|
|
||||||
|
#region Default User: future profiles
|
||||||
|
|
||||||
$defaultDat = "C:\Users\Default\NTUSER.DAT"
|
$defaultDat = "C:\Users\Default\NTUSER.DAT"
|
||||||
$mountName = "SVS_DefaultUser"
|
$mountName = "SVS_DefaultUser"
|
||||||
$mount = "HKU\$mountName"
|
$mount = "HKU\$mountName"
|
||||||
@@ -135,10 +299,12 @@ function Set-RegistryValueForCurrentAndAllUsers {
|
|||||||
if ($LASTEXITCODE -eq 0) {
|
if ($LASTEXITCODE -eq 0) {
|
||||||
$didLoad = $true
|
$didLoad = $true
|
||||||
Write-LogHybrid "Loaded Default User hive ($defaultDat) to HKEY_USERS\$mountName" Info Tweaks -LogToEvent
|
Write-LogHybrid "Loaded Default User hive ($defaultDat) to HKEY_USERS\$mountName" Info Tweaks -LogToEvent
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Write-LogHybrid "Failed to load Default User hive ($defaultDat) to HKEY_USERS\$mountName. reg.exe said: $loadOut" Warning Tweaks -LogToEvent
|
Write-LogHybrid "Failed to load Default User hive ($defaultDat) to HKEY_USERS\$mountName. reg.exe said: $loadOut" Warning Tweaks -LogToEvent
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Write-LogHybrid "Default User hive already loaded at HKEY_USERS\$mountName (skipping reg load)" Info Tweaks -LogToEvent
|
Write-LogHybrid "Default User hive already loaded at HKEY_USERS\$mountName (skipping reg load)" Info Tweaks -LogToEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,28 +321,32 @@ function Set-RegistryValueForCurrentAndAllUsers {
|
|||||||
$unloadOut = & reg.exe unload $mount 2>&1
|
$unloadOut = & reg.exe unload $mount 2>&1
|
||||||
if ($LASTEXITCODE -eq 0) {
|
if ($LASTEXITCODE -eq 0) {
|
||||||
Write-LogHybrid "Unloaded Default User hive from HKEY_USERS\$mountName" Info Tweaks -LogToEvent
|
Write-LogHybrid "Unloaded Default User hive from HKEY_USERS\$mountName" Info Tweaks -LogToEvent
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Write-LogHybrid "Failed to unload Default User hive from HKEY_USERS\$mountName. reg.exe said: $unloadOut" Warning Tweaks -LogToEvent
|
Write-LogHybrid "Failed to unload Default User hive from HKEY_USERS\$mountName. reg.exe said: $unloadOut" Warning Tweaks -LogToEvent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Write-LogHybrid "Default User hive not found at $defaultDat (skipping future-user tweak)" Warning Tweaks -LogToEvent
|
Write-LogHybrid "Default User hive not found at $defaultDat (skipping future-user tweak)" Warning Tweaks -LogToEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion Default User: future profiles
|
||||||
|
|
||||||
|
#region Existing Profiles: iterate ProfileList, mount NTUSER.DAT as needed
|
||||||
|
|
||||||
# 3) Existing profiles
|
|
||||||
$profileList = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
|
$profileList = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
|
||||||
Get-ChildItem $profileList -ErrorAction SilentlyContinue | ForEach-Object {
|
Get-ChildItem $profileList -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
$sid = $_.PSChildName
|
$sid = $_.PSChildName
|
||||||
if ($sid -notmatch '^S-1-5-21-\d+-\d+-\d+-\d+$') { return }
|
if ($sid -notmatch '^S-1-5-21-\d+-\d+-\d+-\d+$') { return }
|
||||||
|
|
||||||
# If already loaded, write directly
|
|
||||||
if (Test-Path "Registry::HKEY_USERS\$sid") {
|
if (Test-Path "Registry::HKEY_USERS\$sid") {
|
||||||
try {
|
try {
|
||||||
Set-RegistryValueInHkuRoot -HkuRoot "Registry::HKEY_USERS\$sid" `
|
Set-RegistryValueInHkuRoot -HkuRoot "Registry::HKEY_USERS\$sid" `
|
||||||
-RelativeKeyPath $RelativeKeyPath -Name $Name -Type $Type -Value $Value
|
-RelativeKeyPath $RelativeKeyPath -Name $Name -Type $Type -Value $Value
|
||||||
} catch {}
|
}
|
||||||
|
catch { }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,168 +361,171 @@ function Set-RegistryValueForCurrentAndAllUsers {
|
|||||||
try {
|
try {
|
||||||
Set-RegistryValueInHkuRoot -HkuRoot "Registry::HKEY_USERS\SVS_$sid" `
|
Set-RegistryValueInHkuRoot -HkuRoot "Registry::HKEY_USERS\SVS_$sid" `
|
||||||
-RelativeKeyPath $RelativeKeyPath -Name $Name -Type $Type -Value $Value
|
-RelativeKeyPath $RelativeKeyPath -Name $Name -Type $Type -Value $Value
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
& reg.exe unload $tempMount 2>$null | Out-Null
|
& reg.exe unload $tempMount 2>$null | Out-Null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion Existing Profiles: iterate ProfileList, mount NTUSER.DAT as needed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion Registry: Apply settings to Current User + Default User + Existing Profiles
|
||||||
|
|
||||||
|
#region Shell: Restart helpers
|
||||||
|
|
||||||
function Restart-ExplorerIfInteractive {
|
function Restart-ExplorerIfInteractive {
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param()
|
param()
|
||||||
|
|
||||||
# Avoid during SYSTEM/unboxing contexts
|
|
||||||
if ($env:USERNAME -ne 'SYSTEM') {
|
if ($env:USERNAME -ne 'SYSTEM') {
|
||||||
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
||||||
Start-Process explorer.exe
|
Start-Process explorer.exe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Re-usable functions
|
#endregion Shell: Restart helpers
|
||||||
|
|
||||||
#region App handlers
|
#region UI Handlers: Apps
|
||||||
|
|
||||||
function Invoke-Install1Password {
|
function Invoke-Install1Password {
|
||||||
param($Context)
|
param($Context)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
# Default if called without suboptions
|
$selected = @('desktop')
|
||||||
$selected = @('desktop')
|
|
||||||
|
|
||||||
# If JS POSTs { checkedValues: [...] }, use that
|
if ($Context -and $Context.Request -and $Context.Request.HttpMethod -eq 'POST') {
|
||||||
if ($Context -and $Context.Request -and $Context.Request.HttpMethod -eq 'POST') {
|
$raw = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd()
|
||||||
$raw = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd()
|
if (-not [string]::IsNullOrWhiteSpace($raw)) {
|
||||||
if (-not [string]::IsNullOrWhiteSpace($raw)) {
|
$data = $raw | ConvertFrom-Json
|
||||||
$data = $raw | ConvertFrom-Json
|
if ($data.checkedValues) { $selected = @($data.checkedValues) }
|
||||||
if ($data.checkedValues) { $selected = @($data.checkedValues) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Extension IDs from official store listings
|
$chromeExtId = 'aeblfdkhhhdcdjpifhhbdiojplfjncoa'
|
||||||
$chromeExtId = 'aeblfdkhhhdcdjpifhhbdiojplfjncoa' # Chrome Web Store :contentReference[oaicite:1]{index=1}
|
$edgeExtId = 'dppgmdbiimibapkepcbdbmkaabgiofem'
|
||||||
$edgeExtId = 'dppgmdbiimibapkepcbdbmkaabgiofem' # Edge Add-ons :contentReference[oaicite:2]{index=2}
|
|
||||||
|
|
||||||
if ($selected -contains 'desktop') {
|
if ($selected -contains 'desktop') {
|
||||||
winget install -e --id AgileBits.1Password --silent --accept-package-agreements --accept-source-agreements
|
winget install -e --id AgileBits.1Password --silent --accept-package-agreements --accept-source-agreements
|
||||||
Write-LogHybrid "Installed 1Password desktop app via winget" Success SVSApps -LogToEvent
|
Write-LogHybrid "Installed 1Password desktop app via winget" Success SVSApps -LogToEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($selected -contains 'chromeExt') {
|
if ($selected -contains 'chromeExt') {
|
||||||
$chromeKey = "HKLM:\SOFTWARE\Policies\Google\Chrome\ExtensionInstallForcelist"
|
$chromeKey = "HKLM:\SOFTWARE\Policies\Google\Chrome\ExtensionInstallForcelist"
|
||||||
New-Item -Path $chromeKey -Force | Out-Null
|
New-Item -Path $chromeKey -Force | Out-Null
|
||||||
New-ItemProperty -Path $chromeKey -Name "1" -PropertyType String -Force `
|
New-ItemProperty -Path $chromeKey -Name "1" -PropertyType String -Force `
|
||||||
-Value "$chromeExtId;https://clients2.google.com/service/update2/crx" | Out-Null
|
-Value "$chromeExtId;https://clients2.google.com/service/update2/crx" | Out-Null
|
||||||
Write-LogHybrid "Forced 1Password extension install for Chrome" Success SVSApps -LogToEvent
|
Write-LogHybrid "Forced 1Password extension install for Chrome" Success SVSApps -LogToEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($selected -contains 'edgeExt') {
|
if ($selected -contains 'edgeExt') {
|
||||||
$edgeKey = "HKLM:\SOFTWARE\Policies\Microsoft\Edge\ExtensionInstallForcelist"
|
$edgeKey = "HKLM:\SOFTWARE\Policies\Microsoft\Edge\ExtensionInstallForcelist"
|
||||||
New-Item -Path $edgeKey -Force | Out-Null
|
New-Item -Path $edgeKey -Force | Out-Null
|
||||||
New-ItemProperty -Path $edgeKey -Name "1" -PropertyType String -Force `
|
New-ItemProperty -Path $edgeKey -Name "1" -PropertyType String -Force `
|
||||||
-Value "$edgeExtId;https://edge.microsoft.com/extensionwebstorebase/v1/crx" | Out-Null
|
-Value "$edgeExtId;https://edge.microsoft.com/extensionwebstorebase/v1/crx" | Out-Null
|
||||||
Write-LogHybrid "Forced 1Password extension install for Edge" Success SVSApps -LogToEvent
|
Write-LogHybrid "Forced 1Password extension install for Edge" Success SVSApps -LogToEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
Send-Text $Context "1Password processed: $($selected -join ', ')"
|
Send-Text $Context "1Password processed: $($selected -join ', ')"
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-LogHybrid "1Password install failed: $($_.Exception.Message)" Error SVSApps -LogToEvent
|
|
||||||
Send-Text $Context "ERROR: $($_.Exception.Message)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch {
|
||||||
function Invoke-DisableAnimations {
|
Write-LogHybrid "1Password install failed: $($_.Exception.Message)" Error SVSApps -LogToEvent
|
||||||
param($Context)
|
Send-Text $Context "ERROR: $($_.Exception.Message)"
|
||||||
|
|
||||||
try {
|
|
||||||
$selected = @()
|
|
||||||
|
|
||||||
if ($Context -and $Context.Request -and $Context.Request.HttpMethod -eq 'POST') {
|
|
||||||
$raw = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd()
|
|
||||||
if (-not [string]::IsNullOrWhiteSpace($raw)) {
|
|
||||||
$data = $raw | ConvertFrom-Json
|
|
||||||
if ($data.checkedValues) { $selected = @($data.checkedValues) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($selected.Count -eq 0) {
|
|
||||||
$selected = @('window','taskbar','menus')
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($selected -contains 'window') {
|
|
||||||
Set-RegistryValueForCurrentAndAllUsers `
|
|
||||||
-RelativeKeyPath "Control Panel\Desktop\WindowMetrics" `
|
|
||||||
-Name "MinAnimate" -Type String -Value "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($selected -contains 'taskbar') {
|
|
||||||
Set-RegistryValueForCurrentAndAllUsers `
|
|
||||||
-RelativeKeyPath "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" `
|
|
||||||
-Name "TaskbarAnimations" -Type DWord -Value 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($selected -contains 'menus') {
|
|
||||||
Set-RegistryValueForCurrentAndAllUsers `
|
|
||||||
-RelativeKeyPath "Control Panel\Desktop" `
|
|
||||||
-Name "MenuShowDelay" -Type String -Value "50"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($selected -contains 'taskbar') {
|
|
||||||
Restart-ExplorerIfInteractive
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-LogHybrid "Disable Animations applied (Current + All Existing + Default User). Selected: $($selected -join ', ')" Success Tweaks -LogToEvent
|
|
||||||
Send-Text $Context "Disable Animations applied: $($selected -join ', ')"
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-LogHybrid "Disable Animations failed: $($_.Exception.Message)" Error Tweaks -LogToEvent
|
|
||||||
Send-Text $Context "ERROR: $($_.Exception.Message)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion UI Handlers: Apps
|
||||||
|
|
||||||
function Invoke-EnableNumLock {
|
#region UI Handlers: Tweaks
|
||||||
param($Context)
|
|
||||||
|
|
||||||
try {
|
function Invoke-DisableAnimations {
|
||||||
# .DEFAULT affects the logon screen + default profile behavior
|
param($Context)
|
||||||
$path = "Registry::HKEY_USERS\.DEFAULT\Control Panel\Keyboard"
|
|
||||||
New-Item -Path $path -Force | Out-Null
|
|
||||||
|
|
||||||
# Common value: "2" = NumLock on at startup (Windows uses string here)
|
try {
|
||||||
Set-ItemProperty -Path $path -Name "InitialKeyboardIndicators" -Value "2" -Type String
|
$selected = @()
|
||||||
|
|
||||||
Write-LogHybrid "NumLock default enabled (HKEY_USERS\.DEFAULT)" Success Tweaks -LogToEvent
|
if ($Context -and $Context.Request -and $Context.Request.HttpMethod -eq 'POST') {
|
||||||
Send-Text $Context "NumLock default enabled."
|
$raw = (New-Object IO.StreamReader $Context.Request.InputStream).ReadToEnd()
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($raw)) {
|
||||||
|
$data = $raw | ConvertFrom-Json
|
||||||
|
if ($data.checkedValues) { $selected = @($data.checkedValues) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch {
|
|
||||||
Write-LogHybrid "Enable NumLock failed: $($_.Exception.Message)" Error Tweaks -LogToEvent
|
if ($selected.Count -eq 0) {
|
||||||
Send-Text $Context "ERROR: $($_.Exception.Message)"
|
$selected = @('window','taskbar','menus')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($selected -contains 'window') {
|
||||||
|
Set-RegistryValueForCurrentAndAllUsers `
|
||||||
|
-RelativeKeyPath "Control Panel\Desktop\WindowMetrics" `
|
||||||
|
-Name "MinAnimate" -Type String -Value "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($selected -contains 'taskbar') {
|
||||||
|
Set-RegistryValueForCurrentAndAllUsers `
|
||||||
|
-RelativeKeyPath "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" `
|
||||||
|
-Name "TaskbarAnimations" -Type DWord -Value 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($selected -contains 'menus') {
|
||||||
|
Set-RegistryValueForCurrentAndAllUsers `
|
||||||
|
-RelativeKeyPath "Control Panel\Desktop" `
|
||||||
|
-Name "MenuShowDelay" -Type String -Value "50"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($selected -contains 'taskbar') {
|
||||||
|
Restart-ExplorerIfInteractive
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-LogHybrid "Disable Animations applied (Current + All Existing + Default User). Selected: $($selected -join ', ')" Success Tweaks -LogToEvent
|
||||||
|
Send-Text $Context "Disable Animations applied: $($selected -join ', ')"
|
||||||
}
|
}
|
||||||
|
catch {
|
||||||
function Invoke-ClassicContextMenu {
|
Write-LogHybrid "Disable Animations failed: $($_.Exception.Message)" Error Tweaks -LogToEvent
|
||||||
param($Context)
|
Send-Text $Context "ERROR: $($_.Exception.Message)"
|
||||||
|
|
||||||
try {
|
|
||||||
$key = "HKCU:\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32"
|
|
||||||
New-Item -Path $key -Force | Out-Null
|
|
||||||
|
|
||||||
# Default value must be blank
|
|
||||||
Set-ItemProperty -Path $key -Name "(default)" -Value "" -Force
|
|
||||||
|
|
||||||
# Restart Explorer so it takes effect
|
|
||||||
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
|
||||||
Start-Process explorer.exe
|
|
||||||
|
|
||||||
Write-LogHybrid "Classic context menu enabled (Win11)" Success Tweaks -LogToEvent
|
|
||||||
Send-Text $Context "Classic context menu enabled (Explorer restarted)."
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-LogHybrid "Classic context menu tweak failed: $($_.Exception.Message)" Error Tweaks -LogToEvent
|
|
||||||
Send-Text $Context "ERROR: $($_.Exception.Message)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-EnableNumLock {
|
||||||
|
param($Context)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$path = "Registry::HKEY_USERS\.DEFAULT\Control Panel\Keyboard"
|
||||||
|
New-Item -Path $path -Force | Out-Null
|
||||||
|
|
||||||
|
# Ensure it's a string value (Windows uses string here)
|
||||||
|
New-ItemProperty -Path $path -Name "InitialKeyboardIndicators" -PropertyType String -Value "2" -Force | Out-Null
|
||||||
|
|
||||||
|
Write-LogHybrid "NumLock default enabled (HKEY_USERS\.DEFAULT)" Success Tweaks -LogToEvent
|
||||||
|
Send-Text $Context "NumLock default enabled."
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-LogHybrid "Enable NumLock failed: $($_.Exception.Message)" Error Tweaks -LogToEvent
|
||||||
|
Send-Text $Context "ERROR: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-ClassicContextMenu {
|
||||||
|
param($Context)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$key = "HKCU:\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32"
|
||||||
|
New-Item -Path $key -Force | Out-Null
|
||||||
|
|
||||||
|
Set-ItemProperty -Path $key -Name "(default)" -Value "" -Force
|
||||||
|
|
||||||
|
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
||||||
|
Start-Process explorer.exe
|
||||||
|
|
||||||
|
Write-LogHybrid "Classic context menu enabled (Win11)" Success Tweaks -LogToEvent
|
||||||
|
Send-Text $Context "Classic context menu enabled (Explorer restarted)."
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-LogHybrid "Classic context menu tweak failed: $($_.Exception.Message)" Error Tweaks -LogToEvent
|
||||||
|
Send-Text $Context "ERROR: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion UI Handlers: Tweaks
|
||||||
|
|
||||||
#endregion App handlers
|
|
||||||
|
|||||||
Reference in New Issue
Block a user