mirror of
https://github.com/GavinBS/win-setup-menu.git
synced 2026-08-17 16:56:17 +05:30
2262 lines
58 KiB
PowerShell
2262 lines
58 KiB
PowerShell
# install-menu.ps1
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$SoftwareGroups = [ordered]@{
|
|
"System Tools" = @(
|
|
@{ Type = "Winget"; Name = "7-Zip"; Id = "7zip.7zip" }
|
|
@{ Type = "Winget"; Name = "Git"; Id = "Git.Git" }
|
|
@{ Type = "Winget"; Name = "PowerShell 7"; Id = "Microsoft.PowerShell" }
|
|
@{ Type = "Winget"; Name = "Windows Terminal"; Id = "Microsoft.WindowsTerminal" }
|
|
@{ Type = "Winget"; Name = "PowerToys"; Id = "Microsoft.PowerToys" }
|
|
@{ Type = "Winget"; Name = "Everything"; Id = "voidtools.Everything" }
|
|
@{ Type = "Winget"; Name = "VC++ Runtime"; Id = "Microsoft.VCRedist.2015+.x64" }
|
|
@{ Type = "Winget"; Name = "CrystalDiskInfo Aoi Edition"; Id = "CrystalDewWorld.CrystalDiskInfo.AoiEdition" }
|
|
@{ Type = "Winget"; Name = "CPUID CPU-Z"; Id = "CPUID.CPU-Z" }
|
|
@{ Type = "Winget"; Name = "HWiNFO"; Id = "REALiX.HWiNFO" }
|
|
)
|
|
|
|
"Apps" = @(
|
|
@{ Type = "Winget"; Name = "Google Chrome"; Id = "Google.Chrome" }
|
|
@{ Type = "Winget"; Name = "WeChat"; Id = "Tencent.WeChat.Universal" }
|
|
@{ Type = "Winget"; Name = "DingTalk"; Id = "Alibaba.DingTalk.Mainland" }
|
|
@{ Type = "Winget"; Name = "QQ"; Id = "Tencent.QQ" }
|
|
@{ Type = "Winget"; Name = "Telegram"; Id = "Telegram.TelegramDesktop" }
|
|
@{ Type = "Winget"; Name = "VLC"; Id = "VideoLAN.VLC" }
|
|
@{ Type = "Winget"; Name = "Obsidian"; Id = "Obsidian.Obsidian" }
|
|
@{ Type = "Winget"; Name = "Office Tool Plus"; Id = "Yerong.OfficeToolPlus" }
|
|
@{ Type = "Winget"; Name = "Neovide"; Id = "Neovide.Neovide" }
|
|
@{ Type = "Winget"; Name = "Syncthing"; Id = "Syncthing.Syncthing"; ProcessName = "syncthing"; AutostartName = "Syncthing" }
|
|
@{ Type = "Winget"; Name = "Steam"; Id = "Valve.Steam" }
|
|
@{ Type = "Winget"; Name = "LocalSend"; Id = "LocalSend.LocalSend" }
|
|
@{ Type = "Winget"; Name = "v2rayN"; Id = "2dust.v2rayN" }
|
|
)
|
|
|
|
"Command Line Tools" = @(
|
|
@{ Type = "Winget"; Name = "Yazi"; Id = "sxyazi.yazi" }
|
|
@{ Type = "Winget"; Name = "Neovim"; Id = "Neovim.Neovim" }
|
|
@{ Type = "Winget"; Name = "eza"; Id = "eza-community.eza" }
|
|
@{ Type = "Winget"; Name = "zoxide"; Id = "ajeetdsouza.zoxide" }
|
|
@{ Type = "Winget"; Name = "fzf"; Id = "junegunn.fzf" }
|
|
@{ Type = "Winget"; Name = "ripgrep"; Id = "BurntSushi.ripgrep.MSVC" }
|
|
@{ Type = "Winget"; Name = "fd"; Id = "sharkdp.fd" }
|
|
@{ Type = "Winget"; Name = "bat"; Id = "sharkdp.bat" }
|
|
@{ Type = "Winget"; Name = "jq"; Id = "jqlang.jq" }
|
|
@{ Type = "Winget"; Name = "curl"; Id = "cURL.cURL" }
|
|
@{ Type = "Winget"; Name = "wget"; Id = "GNU.Wget2" }
|
|
@{ Type = "Winget"; Name = "starship"; Id = "Starship.Starship" }
|
|
@{ Type = "Winget"; Name = "fastfetch"; Id = "Fastfetch-cli.Fastfetch" }
|
|
@{ Type = "Winget"; Name = "lazygit"; Id = "JesseDuffield.lazygit" }
|
|
@{ Type = "Winget"; Name = "git-delta"; Id = "dandavison.delta" }
|
|
@{ Type = "Winget"; Name = "bottom"; Id = "Clement.bottom" }
|
|
@{ Type = "Winget"; Name = "FFmpeg"; Id = "Gyan.FFmpeg" }
|
|
@{ Type = "Winget"; Name = "ImageMagick"; Id = "ImageMagick.ImageMagick" }
|
|
@{ Type = "Winget"; Name = "Poppler"; Id = "oschwartz10612.Poppler" }
|
|
)
|
|
|
|
"Dev Tools" = @(
|
|
@{ Type = "Winget"; Name = "Visual Studio Code"; Id = "Microsoft.VisualStudioCode" }
|
|
@{ Type = "Winget"; Name = "GitHub CLI"; Id = "GitHub.cli" }
|
|
@{ Type = "Winget"; Name = "Python 3.13"; Id = "Python.Python.3.13" }
|
|
@{ Type = "Winget"; Name = "Node.js LTS"; Id = "OpenJS.NodeJS.LTS" }
|
|
@{ Type = "Winget"; Name = "uv"; Id = "astral-sh.uv" }
|
|
@{ Type = "Winget"; Name = "Docker Desktop"; Id = "Docker.DockerDesktop" }
|
|
)
|
|
|
|
"Fonts" = @(
|
|
@{ Type = "Winget"; Name = "JetBrains Mono Nerd Font"; Id = "DEVCOM.JetBrainsMonoNerdFont" }
|
|
@{ Type = "Winget"; Name = "Cascadia Code Nerd Font"; Id = "DEVCOM.CascadiaCodeNerdFont" }
|
|
@{ Type = "Winget"; Name = "Cascadia Mono Nerd Font"; Id = "DEVCOM.CascadiaMonoNerdFont" }
|
|
@{ Type = "Winget"; Name = "FiraCode Nerd Font"; Id = "DEVCOM.FiraCodeNerdFont" }
|
|
@{ Type = "Winget"; Name = "Hack Nerd Font"; Id = "DEVCOM.HackNerdFont" }
|
|
)
|
|
|
|
"WSL / Linux" = @(
|
|
@{ Type = "WslBase"; Name = "WSL base only"; Exe = "wsl"; Args = @("--install", "--no-distribution") }
|
|
@{ Type = "WslDistro"; Name = "Ubuntu"; Exe = "wsl"; Args = @("--install", "-d", "Ubuntu") }
|
|
@{ Type = "WslDistro"; Name = "Debian"; Exe = "wsl"; Args = @("--install", "-d", "Debian") }
|
|
@{ Type = "WslDistro"; Name = "Kali Linux"; Exe = "wsl"; Args = @("--install", "-d", "kali-linux") }
|
|
@{ Type = "WslDistro"; Name = "Arch Linux"; Exe = "wsl"; Args = @("--install", "-d", "archlinux") }
|
|
)
|
|
|
|
"Autostart" = @(
|
|
@{ Type = "Autostart"; Name = "Syncthing"; Command = "syncthing.exe"; Args = @("--no-console", "--no-browser"); ShortcutName = "Syncthing.lnk"; Url = "http://localhost:8384" }
|
|
)
|
|
|
|
"Package Downloads" = @(
|
|
# Put direct download links here. Files are downloaded to Downloads\packages by default.
|
|
# Example:
|
|
# @{ Type = "Download"; Name = "Example ZIP Package"; Url = "https://example.com/package.zip"; FileName = "package.zip" }
|
|
@{ Type = "Download"; Name = "HEU_KMS_Activator"; Url = "https://github.com/zbezj/HEU_KMS_Activator/releases/download/63.4.0/HEU_KMS_Activator_v63.4.0.rar"; FileName = "HEU_KMS_Activator_v63.4.0.zip" }
|
|
)
|
|
|
|
"Maintenance" = @(
|
|
@{ Type = "Command"; Name = "Update winget source"; Exe = "winget"; Args = @("source", "update") }
|
|
@{ Type = "Command"; Name = "Upgrade all winget packages"; Exe = "winget"; Args = @("upgrade", "--all", "--accept-source-agreements", "--accept-package-agreements") }
|
|
@{ Type = "Command"; Name = "List installed packages"; Exe = "winget"; Args = @("list") }
|
|
@{ Type = "Command"; Name = "Export installed packages"; Exe = "winget"; Args = @("export", "-o", "winget-export.json", "--accept-source-agreements") }
|
|
@{ Type = "Command"; Name = "WSL status"; Exe = "wsl"; Args = @("--status") }
|
|
@{ Type = "Command"; Name = "List online WSL distros"; Exe = "wsl"; Args = @("--list", "--online") }
|
|
)
|
|
}
|
|
|
|
function Write-Line {
|
|
Write-Host "----------------------------------------" -ForegroundColor DarkGray
|
|
}
|
|
|
|
function Write-Title {
|
|
param (
|
|
[string]$Text
|
|
)
|
|
|
|
Write-Line
|
|
Write-Host " $Text" -ForegroundColor Cyan
|
|
Write-Line
|
|
}
|
|
|
|
function Write-MenuItem {
|
|
param (
|
|
[int]$Number,
|
|
[string]$Name,
|
|
[string]$Extra = ""
|
|
)
|
|
|
|
Write-Host ("{0,2}. " -f $Number) -NoNewline -ForegroundColor Yellow
|
|
Write-Host $Name -NoNewline -ForegroundColor White
|
|
|
|
if ($Extra -ne "") {
|
|
Write-Host " $Extra" -ForegroundColor DarkGray
|
|
}
|
|
else {
|
|
Write-Host ""
|
|
}
|
|
}
|
|
|
|
function Write-Option {
|
|
param (
|
|
[string]$Key,
|
|
[string]$Text
|
|
)
|
|
|
|
Write-Host " $Key. " -NoNewline -ForegroundColor Yellow
|
|
Write-Host $Text -ForegroundColor White
|
|
}
|
|
|
|
function Write-Hint {
|
|
param (
|
|
[string]$Text
|
|
)
|
|
|
|
Write-Host $Text -ForegroundColor DarkGray
|
|
}
|
|
|
|
function Write-Info {
|
|
param (
|
|
[string]$Text
|
|
)
|
|
|
|
Write-Host $Text -ForegroundColor Cyan
|
|
}
|
|
|
|
function Write-Success {
|
|
param (
|
|
[string]$Text
|
|
)
|
|
|
|
Write-Host $Text -ForegroundColor Green
|
|
}
|
|
|
|
function Write-Warn {
|
|
param (
|
|
[string]$Text
|
|
)
|
|
|
|
Write-Host $Text -ForegroundColor Yellow
|
|
}
|
|
|
|
function Write-ErrorText {
|
|
param (
|
|
[string]$Text
|
|
)
|
|
|
|
Write-Host $Text -ForegroundColor Red
|
|
}
|
|
|
|
function Write-Kv {
|
|
param (
|
|
[string]$Key,
|
|
[string]$Value
|
|
)
|
|
|
|
Write-Host ("{0,-8}: " -f $Key) -NoNewline -ForegroundColor DarkGray
|
|
Write-Host $Value -ForegroundColor White
|
|
}
|
|
|
|
function Wait-User {
|
|
Write-Host ""
|
|
Read-Host "Press Enter to continue"
|
|
}
|
|
|
|
function Test-Winget {
|
|
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
|
|
Write-ErrorText "winget not found. Please install App Installer first."
|
|
return $false
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
function Test-WslCommand {
|
|
if (-not (Get-Command wsl -ErrorAction SilentlyContinue)) {
|
|
Write-ErrorText "wsl command not found."
|
|
Write-Warn "Please make sure your Windows version supports WSL."
|
|
return $false
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
function Test-CommandExists {
|
|
param (
|
|
[string]$CommandName
|
|
)
|
|
|
|
if (-not (Get-Command $CommandName -ErrorAction SilentlyContinue)) {
|
|
Write-ErrorText "$CommandName not found."
|
|
return $false
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
function Test-WingetInstalled {
|
|
param (
|
|
[string]$Id
|
|
)
|
|
|
|
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
|
|
return $false
|
|
}
|
|
|
|
$output = winget list --id $Id -e --accept-source-agreements 2>$null | Out-String
|
|
|
|
if ($output -match [regex]::Escape($Id)) {
|
|
return $true
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
function Test-WslReady {
|
|
if (-not (Get-Command wsl -ErrorAction SilentlyContinue)) {
|
|
return $false
|
|
}
|
|
|
|
wsl --status *> $null
|
|
|
|
if ($LASTEXITCODE -eq 0) {
|
|
return $true
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
function Get-WslDistroName {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
if (-not $App.Args) {
|
|
return ""
|
|
}
|
|
|
|
$argsText = @($App.Args)
|
|
|
|
for ($i = 0; $i -lt $argsText.Count; $i++) {
|
|
if ($argsText[$i] -eq "-d" -and ($i + 1) -lt $argsText.Count) {
|
|
return [string]$argsText[$i + 1]
|
|
}
|
|
}
|
|
|
|
if ($argsText.Count -ge 2 -and $argsText[0] -eq "--install") {
|
|
return [string]$argsText[1]
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
function Test-WslDistroInstalled {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
if (-not (Test-WslReady)) {
|
|
return $false
|
|
}
|
|
|
|
$distroName = Get-WslDistroName -App $App
|
|
|
|
if ($distroName -eq "") {
|
|
return $false
|
|
}
|
|
|
|
$distros = wsl --list --quiet 2>$null
|
|
|
|
foreach ($distro in $distros) {
|
|
if ($distro.Trim() -ieq $distroName) {
|
|
return $true
|
|
}
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
function Test-IsAdmin {
|
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
$principal = [Security.Principal.WindowsPrincipal]$identity
|
|
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
}
|
|
|
|
function Restart-ScriptAsAdmin {
|
|
$scriptPath = $PSCommandPath
|
|
|
|
if (-not $scriptPath) {
|
|
Write-Warn "Cannot find current script path."
|
|
Write-Warn "Please run PowerShell as Administrator manually."
|
|
Wait-User
|
|
return
|
|
}
|
|
|
|
Write-Warn "Administrator permission is required."
|
|
Write-Hint "A new Administrator PowerShell window will be opened."
|
|
Write-Host ""
|
|
|
|
$confirm = Read-Host "Restart this script as Administrator? Y/N"
|
|
|
|
if ($confirm -notmatch "^[Yy]$") {
|
|
Write-Warn "Cancelled."
|
|
Wait-User
|
|
return
|
|
}
|
|
|
|
$argumentList = "-ExecutionPolicy Bypass -File `"$scriptPath`""
|
|
|
|
Start-Process powershell -Verb RunAs -ArgumentList $argumentList
|
|
|
|
exit
|
|
}
|
|
|
|
|
|
function Get-StartupFolder {
|
|
return [Environment]::GetFolderPath("Startup")
|
|
}
|
|
|
|
function Get-AutostartShortcutPath {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
$startupFolder = Get-StartupFolder
|
|
$shortcutName = [string]$App.ShortcutName
|
|
|
|
if ([string]::IsNullOrWhiteSpace($shortcutName)) {
|
|
$shortcutName = "$($App.Name).lnk"
|
|
}
|
|
|
|
return (Join-Path $startupFolder $shortcutName)
|
|
}
|
|
|
|
function Get-AutostartArgumentsText {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
if (-not $App.Args) {
|
|
return ""
|
|
}
|
|
|
|
return (@($App.Args) -join " ")
|
|
}
|
|
|
|
function Get-AutostartLauncherFolder {
|
|
$folder = Join-Path $env:LOCALAPPDATA "InstallMenu\Autostart"
|
|
return $folder
|
|
}
|
|
|
|
function Get-AutostartLauncherPath {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
$name = [string]$App.Name
|
|
|
|
if ([string]::IsNullOrWhiteSpace($name)) {
|
|
$name = "AutostartApp"
|
|
}
|
|
|
|
$safeName = ($name -replace '[^a-zA-Z0-9._-]', '_')
|
|
return (Join-Path (Get-AutostartLauncherFolder) "$safeName-hidden.vbs")
|
|
}
|
|
|
|
function Get-AutostartCommandLine {
|
|
param (
|
|
[string]$ExePath,
|
|
[string]$ArgumentsText
|
|
)
|
|
|
|
$commandLine = "`"$ExePath`""
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($ArgumentsText)) {
|
|
$commandLine = "$commandLine $ArgumentsText"
|
|
}
|
|
|
|
return $commandLine
|
|
}
|
|
|
|
function New-AutostartHiddenLauncher {
|
|
param (
|
|
[hashtable]$App,
|
|
[string]$ExePath,
|
|
[string]$ArgumentsText
|
|
)
|
|
|
|
$launcherFolder = Get-AutostartLauncherFolder
|
|
$launcherPath = Get-AutostartLauncherPath -App $App
|
|
|
|
if (-not (Test-Path $launcherFolder)) {
|
|
New-Item -ItemType Directory -Path $launcherFolder -Force | Out-Null
|
|
}
|
|
|
|
$commandLine = Get-AutostartCommandLine -ExePath $ExePath -ArgumentsText $ArgumentsText
|
|
$vbsCommandLine = $commandLine.Replace('"', '""')
|
|
|
|
$content = @"
|
|
Set WshShell = CreateObject("WScript.Shell")
|
|
WshShell.Run "$vbsCommandLine", 0, False
|
|
"@
|
|
|
|
Set-Content -Path $launcherPath -Value $content -Encoding Unicode
|
|
return $launcherPath
|
|
}
|
|
|
|
function Start-HiddenProgram {
|
|
param (
|
|
[string]$ExePath,
|
|
[string]$ArgumentsText
|
|
)
|
|
|
|
$commandLine = Get-AutostartCommandLine -ExePath $ExePath -ArgumentsText $ArgumentsText
|
|
$wsh = New-Object -ComObject WScript.Shell
|
|
$wsh.Run($commandLine, 0, $false) | Out-Null
|
|
}
|
|
|
|
function Get-AutostartCommandInfo {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
$commandName = [string]$App.Command
|
|
|
|
if ([string]::IsNullOrWhiteSpace($commandName)) {
|
|
return $null
|
|
}
|
|
|
|
return (Get-Command $commandName -ErrorAction SilentlyContinue)
|
|
}
|
|
|
|
function Get-AutostartExePath {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
$commandInfo = Get-AutostartCommandInfo -App $App
|
|
|
|
if ($null -eq $commandInfo) {
|
|
return ""
|
|
}
|
|
|
|
if ($commandInfo.Source) {
|
|
return [string]$commandInfo.Source
|
|
}
|
|
|
|
if ($commandInfo.Path) {
|
|
return [string]$commandInfo.Path
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
function Get-AutostartProcessName {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
if ($App.ProcessName) {
|
|
return [string]$App.ProcessName
|
|
}
|
|
|
|
$commandName = [string]$App.Command
|
|
|
|
if ([string]::IsNullOrWhiteSpace($commandName)) {
|
|
return ""
|
|
}
|
|
|
|
return [System.IO.Path]::GetFileNameWithoutExtension($commandName)
|
|
}
|
|
|
|
function Test-AutostartEnabled {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
$shortcutPath = Get-AutostartShortcutPath -App $App
|
|
return (Test-Path $shortcutPath)
|
|
}
|
|
|
|
|
|
function Test-AutostartProcessRunning {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
$processName = Get-AutostartProcessName -App $App
|
|
|
|
if ([string]::IsNullOrWhiteSpace($processName)) {
|
|
return $false
|
|
}
|
|
|
|
$process = Get-Process -Name $processName -ErrorAction SilentlyContinue
|
|
return ($null -ne $process)
|
|
}
|
|
|
|
function Stop-AutostartAppNow {
|
|
param (
|
|
[hashtable]$App,
|
|
[switch]$NoPause,
|
|
[switch]$Silent
|
|
)
|
|
|
|
$processName = Get-AutostartProcessName -App $App
|
|
|
|
if ([string]::IsNullOrWhiteSpace($processName)) {
|
|
if (-not $Silent) {
|
|
Write-Warn "No process name configured for $($App.Name)."
|
|
}
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
$processes = Get-Process -Name $processName -ErrorAction SilentlyContinue
|
|
|
|
if ($null -eq $processes) {
|
|
if (-not $Silent) {
|
|
Write-Warn "$($App.Name) is not running."
|
|
}
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
try {
|
|
$processes | Stop-Process -Force
|
|
|
|
if (-not $Silent) {
|
|
Write-Success "Stopped: $($App.Name)"
|
|
}
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return $true
|
|
}
|
|
catch {
|
|
if (-not $Silent) {
|
|
Write-ErrorText "Failed to stop $($App.Name): $($_.Exception.Message)"
|
|
}
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Enable-AutostartShortcut {
|
|
param (
|
|
[hashtable]$App,
|
|
[switch]$NoPause
|
|
)
|
|
|
|
$exePath = Get-AutostartExePath -App $App
|
|
|
|
if ([string]::IsNullOrWhiteSpace($exePath)) {
|
|
Write-ErrorText "$($App.Name) command not found: $($App.Command)"
|
|
Write-Warn "Please install $($App.Name) first, then enable autostart."
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
$startupFolder = Get-StartupFolder
|
|
$shortcutPath = Get-AutostartShortcutPath -App $App
|
|
$argumentsText = Get-AutostartArgumentsText -App $App
|
|
|
|
if (-not (Test-Path $startupFolder)) {
|
|
New-Item -ItemType Directory -Path $startupFolder -Force | Out-Null
|
|
}
|
|
|
|
$launcherPath = New-AutostartHiddenLauncher -App $App -ExePath $exePath -ArgumentsText $argumentsText
|
|
$wscriptPath = Join-Path $env:WINDIR "System32\wscript.exe"
|
|
|
|
$wsh = New-Object -ComObject WScript.Shell
|
|
$shortcut = $wsh.CreateShortcut($shortcutPath)
|
|
$shortcut.TargetPath = $wscriptPath
|
|
$shortcut.Arguments = "//B //Nologo `"$launcherPath`""
|
|
$shortcut.WorkingDirectory = Split-Path $launcherPath
|
|
$shortcut.Description = "Start $($App.Name) hidden at Windows user logon"
|
|
$shortcut.WindowStyle = 7
|
|
$shortcut.Save()
|
|
|
|
Write-Success "Autostart enabled: $($App.Name)"
|
|
Write-Hint "Shortcut: $shortcutPath"
|
|
Write-Hint "Launcher: $launcherPath"
|
|
Write-Hint "Command : $exePath $argumentsText"
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
function Disable-AutostartShortcut {
|
|
param (
|
|
[hashtable]$App,
|
|
[switch]$NoPause
|
|
)
|
|
|
|
$shortcutPath = Get-AutostartShortcutPath -App $App
|
|
|
|
$launcherPath = Get-AutostartLauncherPath -App $App
|
|
$removed = $false
|
|
|
|
if (Test-Path $shortcutPath) {
|
|
Remove-Item $shortcutPath -Force
|
|
Write-Success "Removed shortcut: $shortcutPath"
|
|
$removed = $true
|
|
}
|
|
|
|
if (Test-Path $launcherPath) {
|
|
Remove-Item $launcherPath -Force
|
|
Write-Success "Removed launcher: $launcherPath"
|
|
$removed = $true
|
|
}
|
|
|
|
if ($removed) {
|
|
Write-Success "Autostart disabled: $($App.Name)"
|
|
}
|
|
else {
|
|
Write-Warn "Autostart is not enabled: $($App.Name)"
|
|
Write-Hint "Shortcut not found: $shortcutPath"
|
|
}
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
}
|
|
|
|
function Start-AutostartAppNow {
|
|
param (
|
|
[hashtable]$App,
|
|
[switch]$NoPause
|
|
)
|
|
|
|
$exePath = Get-AutostartExePath -App $App
|
|
|
|
if ([string]::IsNullOrWhiteSpace($exePath)) {
|
|
Write-ErrorText "$($App.Name) command not found: $($App.Command)"
|
|
Write-Warn "Please install $($App.Name) first."
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if (Test-AutostartProcessRunning -App $App) {
|
|
Write-Warn "$($App.Name) is already running."
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
$argumentsText = Get-AutostartArgumentsText -App $App
|
|
|
|
Start-HiddenProgram -ExePath $exePath -ArgumentsText $argumentsText
|
|
|
|
Write-Success "Started hidden: $($App.Name)"
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
}
|
|
|
|
function Open-AutostartAppUrl {
|
|
param (
|
|
[hashtable]$App,
|
|
[switch]$NoPause
|
|
)
|
|
|
|
$url = [string]$App.Url
|
|
|
|
if ([string]::IsNullOrWhiteSpace($url)) {
|
|
Write-Warn "No URL configured for $($App.Name)."
|
|
}
|
|
else {
|
|
Start-Process $url
|
|
Write-Success "Opened: $url"
|
|
}
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
}
|
|
|
|
function Show-AutostartInfo {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
Clear-Host
|
|
Write-Title "Autostart Info"
|
|
|
|
$shortcutPath = Get-AutostartShortcutPath -App $App
|
|
$exePath = Get-AutostartExePath -App $App
|
|
$argumentsText = Get-AutostartArgumentsText -App $App
|
|
$enabledText = if (Test-AutostartEnabled -App $App) { "Enabled" } else { "Disabled" }
|
|
$runningText = if (Test-AutostartProcessRunning -App $App) { "Running" } else { "Not running" }
|
|
|
|
Write-Kv -Key "Name" -Value $App.Name
|
|
Write-Kv -Key "Type" -Value "Startup folder shortcut + hidden launcher"
|
|
Write-Kv -Key "Status" -Value $enabledText
|
|
Write-Kv -Key "Running" -Value $runningText
|
|
Write-Kv -Key "Startup" -Value (Get-StartupFolder)
|
|
Write-Kv -Key "Shortcut" -Value $shortcutPath
|
|
Write-Kv -Key "Launcher" -Value (Get-AutostartLauncherPath -App $App)
|
|
Write-Kv -Key "Command" -Value "$($App.Command) $argumentsText"
|
|
|
|
if ([string]::IsNullOrWhiteSpace($exePath)) {
|
|
Write-Kv -Key "Exe" -Value "Not found"
|
|
}
|
|
else {
|
|
Write-Kv -Key "Exe" -Value $exePath
|
|
}
|
|
|
|
if ($App.Url) {
|
|
Write-Kv -Key "Web UI" -Value ([string]$App.Url)
|
|
}
|
|
|
|
if (Test-Path $shortcutPath) {
|
|
try {
|
|
$wsh = New-Object -ComObject WScript.Shell
|
|
$shortcut = $wsh.CreateShortcut($shortcutPath)
|
|
Write-Host ""
|
|
Write-Info "Current shortcut content:"
|
|
Write-Kv -Key "Target" -Value $shortcut.TargetPath
|
|
Write-Kv -Key "Args" -Value $shortcut.Arguments
|
|
Write-Kv -Key "WorkDir" -Value $shortcut.WorkingDirectory
|
|
}
|
|
catch {
|
|
Write-Warn "Could not read shortcut details."
|
|
}
|
|
}
|
|
|
|
$launcherPath = Get-AutostartLauncherPath -App $App
|
|
if (Test-Path $launcherPath) {
|
|
Write-Host ""
|
|
Write-Info "Hidden launcher exists: $launcherPath"
|
|
}
|
|
|
|
Wait-User
|
|
}
|
|
|
|
function Invoke-AutostartAction {
|
|
param (
|
|
[hashtable]$App,
|
|
[switch]$SkipConfirm,
|
|
[switch]$NoPause
|
|
)
|
|
|
|
if ($SkipConfirm -and (Test-AutostartEnabled -App $App)) {
|
|
Write-Warn "Autostart already enabled, skipped: $($App.Name)"
|
|
return
|
|
}
|
|
|
|
if (-not $SkipConfirm) {
|
|
Clear-Host
|
|
Write-Title "Enable Autostart Confirmation"
|
|
Write-Kv -Key "Name" -Value $App.Name
|
|
Write-Kv -Key "Status" -Value (Get-AppStatusText -App $App)
|
|
Write-Kv -Key "Command" -Value (Get-AppCommandText -App $App)
|
|
Write-Host ""
|
|
|
|
$confirm = Read-Host "Enable or recreate autostart shortcut? Y/N"
|
|
|
|
if ($confirm -notmatch "^[Yy]$") {
|
|
Write-Warn "Cancelled."
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
$enabled = Enable-AutostartShortcut -App $App -NoPause
|
|
|
|
if ($enabled) {
|
|
Write-Host ""
|
|
Start-AutostartAppNow -App $App -NoPause
|
|
|
|
if ($App.Url) {
|
|
Write-Hint "Web UI: $($App.Url)"
|
|
}
|
|
}
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
}
|
|
|
|
function Show-AutostartActionMenu {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
while ($true) {
|
|
Clear-Host
|
|
Write-Title "Autostart: $($App.Name)"
|
|
|
|
Write-Kv -Key "Status" -Value (Get-AppStatusText -App $App)
|
|
Write-Kv -Key "Shortcut" -Value (Get-AutostartShortcutPath -App $App)
|
|
Write-Kv -Key "Command" -Value (Get-AppCommandText -App $App)
|
|
|
|
Write-Host ""
|
|
Write-Option -Key "1" -Text "Enable / recreate autostart + start now (hidden)"
|
|
Write-Option -Key "2" -Text "Disable autostart"
|
|
Write-Option -Key "3" -Text "Start now (hidden)"
|
|
Write-Option -Key "4" -Text "Open Web UI"
|
|
Write-Option -Key "5" -Text "Show details"
|
|
Write-Option -Key "6" -Text "Stop now"
|
|
Write-Option -Key "B" -Text "Back"
|
|
Write-Option -Key "Q" -Text "Quit"
|
|
Write-Host ""
|
|
|
|
$choice = Read-Host "Choose"
|
|
|
|
switch -Regex ($choice) {
|
|
"^1$" {
|
|
Invoke-AutostartAction -App $App
|
|
}
|
|
|
|
"^2$" {
|
|
Disable-AutostartShortcut -App $App
|
|
}
|
|
|
|
"^3$" {
|
|
Start-AutostartAppNow -App $App
|
|
}
|
|
|
|
"^4$" {
|
|
Open-AutostartAppUrl -App $App
|
|
}
|
|
|
|
"^5$" {
|
|
Show-AutostartInfo -App $App
|
|
}
|
|
|
|
"^6$" {
|
|
Stop-AutostartAppNow -App $App
|
|
}
|
|
|
|
"^[Bb]$" {
|
|
return
|
|
}
|
|
|
|
"^[Qq]$" {
|
|
exit
|
|
}
|
|
|
|
default {
|
|
Write-Warn "Invalid input."
|
|
Wait-User
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function Get-PackageDownloadsFolder {
|
|
$downloads = Join-Path $env:USERPROFILE "Downloads"
|
|
$folder = Join-Path $downloads "packages"
|
|
|
|
if (-not (Test-Path -LiteralPath $folder)) {
|
|
New-Item -ItemType Directory -Path $folder -Force | Out-Null
|
|
}
|
|
|
|
return $folder
|
|
}
|
|
|
|
function Get-SafeFileName {
|
|
param (
|
|
[string]$FileName
|
|
)
|
|
|
|
$invalidChars = [System.IO.Path]::GetInvalidFileNameChars()
|
|
$safeName = $FileName
|
|
|
|
foreach ($char in $invalidChars) {
|
|
$safeName = $safeName.Replace([string]$char, "_")
|
|
}
|
|
|
|
return $safeName
|
|
}
|
|
|
|
function Get-DownloadFileName {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
if ($App.FileName -and -not [string]::IsNullOrWhiteSpace([string]$App.FileName)) {
|
|
return (Get-SafeFileName -FileName ([string]$App.FileName))
|
|
}
|
|
|
|
$url = [string]$App.Url
|
|
|
|
try {
|
|
$uri = [System.Uri]$url
|
|
$fileName = [System.IO.Path]::GetFileName($uri.AbsolutePath)
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($fileName)) {
|
|
return (Get-SafeFileName -FileName $fileName)
|
|
}
|
|
}
|
|
catch {
|
|
# Fall through to name-based fallback.
|
|
}
|
|
|
|
return (Get-SafeFileName -FileName "$($App.Name).zip")
|
|
}
|
|
|
|
function Get-DownloadDestinationFolder {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
if ($App.DestinationDir -and -not [string]::IsNullOrWhiteSpace([string]$App.DestinationDir)) {
|
|
$folder = [Environment]::ExpandEnvironmentVariables([string]$App.DestinationDir)
|
|
}
|
|
else {
|
|
$folder = Get-PackageDownloadsFolder
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $folder)) {
|
|
New-Item -ItemType Directory -Path $folder -Force | Out-Null
|
|
}
|
|
|
|
return $folder
|
|
}
|
|
|
|
function Get-DownloadDestinationPath {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
$folder = Get-DownloadDestinationFolder -App $App
|
|
$fileName = Get-DownloadFileName -App $App
|
|
|
|
return (Join-Path $folder $fileName)
|
|
}
|
|
|
|
function Test-DownloadFileExists {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
$destinationPath = Get-DownloadDestinationPath -App $App
|
|
return (Test-Path -LiteralPath $destinationPath -PathType Leaf)
|
|
}
|
|
|
|
function Invoke-DownloadPackage {
|
|
param (
|
|
[hashtable]$App,
|
|
[switch]$SkipConfirm,
|
|
[switch]$NoPause
|
|
)
|
|
|
|
$url = [string]$App.Url
|
|
|
|
if ([string]::IsNullOrWhiteSpace($url)) {
|
|
Write-ErrorText "Download URL is empty: $($App.Name)"
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
$destinationPath = Get-DownloadDestinationPath -App $App
|
|
$destinationFolder = Split-Path -Parent $destinationPath
|
|
$fileExists = Test-Path -LiteralPath $destinationPath -PathType Leaf
|
|
|
|
if ($SkipConfirm -and $fileExists) {
|
|
Write-Warn "Already downloaded, skipped: $($App.Name)"
|
|
Write-Hint "Path: $destinationPath"
|
|
return $true
|
|
}
|
|
|
|
if ((-not $SkipConfirm) -and $fileExists) {
|
|
Write-Warn "File already exists: $destinationPath"
|
|
$confirm = Read-Host "Overwrite it? Y/N"
|
|
|
|
if ($confirm -notmatch "^[Yy]$") {
|
|
Write-Warn "Cancelled."
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return $false
|
|
}
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $destinationFolder)) {
|
|
New-Item -ItemType Directory -Path $destinationFolder -Force | Out-Null
|
|
}
|
|
|
|
$tempPath = "$destinationPath.download"
|
|
|
|
if (Test-Path -LiteralPath $tempPath) {
|
|
Remove-Item -LiteralPath $tempPath -Force
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Info "Downloading: $($App.Name)"
|
|
Write-Hint "URL: $url"
|
|
Write-Hint "Save to: $destinationPath"
|
|
Write-Host ""
|
|
|
|
try {
|
|
Invoke-WebRequest -Uri $url -OutFile $tempPath -UseBasicParsing
|
|
|
|
if (Test-Path -LiteralPath $destinationPath) {
|
|
Remove-Item -LiteralPath $destinationPath -Force
|
|
}
|
|
|
|
Move-Item -LiteralPath $tempPath -Destination $destinationPath -Force
|
|
Write-Success "Downloaded: $destinationPath"
|
|
return $true
|
|
}
|
|
catch {
|
|
Write-ErrorText "Download failed: $($_.Exception.Message)"
|
|
|
|
if (Test-Path -LiteralPath $tempPath) {
|
|
Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
return $false
|
|
}
|
|
finally {
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
}
|
|
}
|
|
|
|
function Remove-DownloadedPackage {
|
|
param (
|
|
[hashtable]$App,
|
|
[switch]$NoPause
|
|
)
|
|
|
|
$destinationPath = Get-DownloadDestinationPath -App $App
|
|
|
|
if (-not (Test-Path -LiteralPath $destinationPath -PathType Leaf)) {
|
|
Write-Warn "Downloaded file not found: $destinationPath"
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
try {
|
|
Remove-Item -LiteralPath $destinationPath -Force
|
|
Write-Success "Deleted downloaded file: $destinationPath"
|
|
return $true
|
|
}
|
|
catch {
|
|
Write-ErrorText "Failed to delete file: $($_.Exception.Message)"
|
|
return $false
|
|
}
|
|
finally {
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-AppTypeText {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
switch ($App.Type) {
|
|
"Winget" { return "Winget" }
|
|
"WslBase" { return "WSL Base" }
|
|
"WslDistro" { return "WSL Linux Distro" }
|
|
"Command" { return "Command" }
|
|
"Autostart" { return "Autostart" }
|
|
"Download" { return "Download" }
|
|
default { return "Unknown" }
|
|
}
|
|
}
|
|
|
|
function Get-AppCommandText {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
switch ($App.Type) {
|
|
"Winget" {
|
|
return "winget install --id $($App.Id) -e"
|
|
}
|
|
|
|
"WslBase" {
|
|
return "$($App.Exe) $($App.Args -join ' ')"
|
|
}
|
|
|
|
"WslDistro" {
|
|
return "$($App.Exe) $($App.Args -join ' ')"
|
|
}
|
|
|
|
"Command" {
|
|
return "$($App.Exe) $($App.Args -join ' ')"
|
|
}
|
|
|
|
"Autostart" {
|
|
return "$($App.Command) $(Get-AutostartArgumentsText -App $App)"
|
|
}
|
|
|
|
"Download" {
|
|
return "Download to $(Get-DownloadDestinationPath -App $App)"
|
|
}
|
|
|
|
default {
|
|
return ""
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-AppStatusText {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
switch ($App.Type) {
|
|
"Winget" {
|
|
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
|
|
return "Unknown, winget not found"
|
|
}
|
|
|
|
if (Test-WingetInstalled -Id ([string]$App.Id)) {
|
|
return "Installed"
|
|
}
|
|
|
|
return "Not installed"
|
|
}
|
|
|
|
"WslBase" {
|
|
if (Test-WslReady) {
|
|
return "Enabled"
|
|
}
|
|
|
|
return "Not enabled"
|
|
}
|
|
|
|
"WslDistro" {
|
|
if (-not (Test-WslReady)) {
|
|
return "WSL not ready"
|
|
}
|
|
|
|
if (Test-WslDistroInstalled -App $App) {
|
|
return "Installed"
|
|
}
|
|
|
|
return "Not installed"
|
|
}
|
|
|
|
"Command" {
|
|
return "Runnable command"
|
|
}
|
|
|
|
"Autostart" {
|
|
$enabledText = if (Test-AutostartEnabled -App $App) { "Enabled" } else { "Disabled" }
|
|
$runningText = if (Test-AutostartProcessRunning -App $App) { "Running" } else { "Not running" }
|
|
$exePath = Get-AutostartExePath -App $App
|
|
|
|
if ([string]::IsNullOrWhiteSpace($exePath)) {
|
|
return "$enabledText, command not found"
|
|
}
|
|
|
|
return "$enabledText, $runningText"
|
|
}
|
|
|
|
"Download" {
|
|
if (Test-DownloadFileExists -App $App) {
|
|
return "Downloaded"
|
|
}
|
|
|
|
return "Not downloaded"
|
|
}
|
|
|
|
default {
|
|
return "Unknown"
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-PrimaryActionText {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
switch ($App.Type) {
|
|
"Winget" {
|
|
if (Test-WingetInstalled -Id ([string]$App.Id)) {
|
|
return "Upgrade / install again"
|
|
}
|
|
|
|
return "Install"
|
|
}
|
|
|
|
"WslBase" {
|
|
if (Test-WslReady) {
|
|
return "Already enabled"
|
|
}
|
|
|
|
return "Install"
|
|
}
|
|
|
|
"WslDistro" {
|
|
if (Test-WslDistroInstalled -App $App) {
|
|
return "Already installed"
|
|
}
|
|
|
|
return "Install"
|
|
}
|
|
|
|
"Command" {
|
|
return "Run"
|
|
}
|
|
|
|
"Autostart" {
|
|
if (Test-AutostartEnabled -App $App) {
|
|
return "Recreate autostart shortcut"
|
|
}
|
|
|
|
return "Enable autostart"
|
|
}
|
|
|
|
"Download" {
|
|
if (Test-DownloadFileExists -App $App) {
|
|
return "Download again / overwrite"
|
|
}
|
|
|
|
return "Download"
|
|
}
|
|
|
|
default {
|
|
return "Run"
|
|
}
|
|
}
|
|
}
|
|
|
|
function Convert-SelectionToNumbers {
|
|
param (
|
|
[string]$InputText,
|
|
[int]$MaxNumber
|
|
)
|
|
|
|
$InputText = $InputText.Trim()
|
|
|
|
if ($InputText -notmatch "^\d+(\s*-\s*\d+)?(\s*,\s*\d+(\s*-\s*\d+)?)*$") {
|
|
return $null
|
|
}
|
|
|
|
$result = @()
|
|
$parts = $InputText -split ","
|
|
|
|
foreach ($part in $parts) {
|
|
$part = $part.Trim()
|
|
|
|
if ($part -match "^(\d+)\s*-\s*(\d+)$") {
|
|
$start = [int]$Matches[1]
|
|
$end = [int]$Matches[2]
|
|
|
|
if ($start -gt $end) {
|
|
Write-Warn "Invalid range: $part"
|
|
return $null
|
|
}
|
|
|
|
for ($i = $start; $i -le $end; $i++) {
|
|
if ($i -lt 1 -or $i -gt $MaxNumber) {
|
|
Write-Warn "Invalid number: $i"
|
|
return $null
|
|
}
|
|
|
|
if ($result -notcontains $i) {
|
|
$result += $i
|
|
}
|
|
}
|
|
}
|
|
elseif ($part -match "^\d+$") {
|
|
$number = [int]$part
|
|
|
|
if ($number -lt 1 -or $number -gt $MaxNumber) {
|
|
Write-Warn "Invalid number: $number"
|
|
return $null
|
|
}
|
|
|
|
if ($result -notcontains $number) {
|
|
$result += $number
|
|
}
|
|
}
|
|
else {
|
|
return $null
|
|
}
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
function Install-WslBase {
|
|
param (
|
|
[switch]$NoPause
|
|
)
|
|
|
|
if (-not (Test-WslCommand)) {
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
return $false
|
|
}
|
|
|
|
if (Test-WslReady) {
|
|
Write-Success "WSL is already installed or enabled."
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
if (-not (Test-IsAdmin)) {
|
|
Restart-ScriptAsAdmin
|
|
return $false
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Info "Installing WSL base..."
|
|
Write-Hint "Command: wsl --install --no-distribution"
|
|
Write-Host ""
|
|
|
|
wsl --install --no-distribution
|
|
|
|
Write-Host ""
|
|
Write-Success "WSL base installation command finished."
|
|
Write-Warn "Please restart Windows, then run this script again to install Ubuntu/Debian/Kali/Arch."
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
function Ensure-WslReady {
|
|
param (
|
|
[switch]$NoPause
|
|
)
|
|
|
|
if (Test-WslReady) {
|
|
return $true
|
|
}
|
|
|
|
Clear-Host
|
|
Write-Title "WSL Required"
|
|
Write-Warn "WSL is not installed or enabled."
|
|
Write-Host ""
|
|
|
|
$confirm = Read-Host "Install WSL first? Y/N"
|
|
|
|
if ($confirm -notmatch "^[Yy]$") {
|
|
Write-Host ""
|
|
Write-Warn "Cancelled."
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
Install-WslBase -NoPause:$NoPause | Out-Null
|
|
return $false
|
|
}
|
|
|
|
|
|
function Test-AppSupportsUninstall {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
switch ($App.Type) {
|
|
"Winget" { return $true }
|
|
"WslDistro" { return $true }
|
|
"Autostart" { return $true }
|
|
"Download" { return $true }
|
|
default { return $false }
|
|
}
|
|
}
|
|
|
|
function Get-AppUninstallCommandText {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
switch ($App.Type) {
|
|
"Winget" {
|
|
return "winget uninstall --id $($App.Id) -e"
|
|
}
|
|
|
|
"WslDistro" {
|
|
$distroName = Get-WslDistroName -App $App
|
|
|
|
if ([string]::IsNullOrWhiteSpace($distroName)) {
|
|
return "wsl --unregister <distro>"
|
|
}
|
|
|
|
return "wsl --unregister $distroName"
|
|
}
|
|
|
|
"Autostart" {
|
|
return "Remove startup shortcut and hidden launcher"
|
|
}
|
|
|
|
"Download" {
|
|
return "Delete $(Get-DownloadDestinationPath -App $App)"
|
|
}
|
|
|
|
default {
|
|
return ""
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-RelatedAutostartApp {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
$name = ""
|
|
|
|
if ($App.AutostartName) {
|
|
$name = [string]$App.AutostartName
|
|
}
|
|
else {
|
|
$name = [string]$App.Name
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($name)) {
|
|
return $null
|
|
}
|
|
|
|
if (-not $SoftwareGroups.Contains("Autostart")) {
|
|
return $null
|
|
}
|
|
|
|
foreach ($autostartApp in $SoftwareGroups["Autostart"]) {
|
|
if ([string]$autostartApp.Name -ieq $name) {
|
|
return $autostartApp
|
|
}
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Stop-AppProcessIfConfigured {
|
|
param (
|
|
[hashtable]$App,
|
|
[switch]$Silent
|
|
)
|
|
|
|
if (-not $App.ProcessName) {
|
|
return $true
|
|
}
|
|
|
|
$processName = [string]$App.ProcessName
|
|
$processes = Get-Process -Name $processName -ErrorAction SilentlyContinue
|
|
|
|
if ($null -eq $processes) {
|
|
if (-not $Silent) {
|
|
Write-Hint "Process is not running: $processName"
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
try {
|
|
$processes | Stop-Process -Force
|
|
|
|
if (-not $Silent) {
|
|
Write-Success "Stopped process: $processName"
|
|
}
|
|
|
|
return $true
|
|
}
|
|
catch {
|
|
if (-not $Silent) {
|
|
Write-Warn "Failed to stop process ${processName}: $($_.Exception.Message)"
|
|
}
|
|
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Invoke-AppUninstall {
|
|
param (
|
|
[hashtable]$App,
|
|
[switch]$SkipConfirm,
|
|
[switch]$NoPause
|
|
)
|
|
|
|
if (-not (Test-AppSupportsUninstall -App $App)) {
|
|
Write-Warn "Uninstall is not supported for this item: $($App.Name)"
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
$statusText = Get-AppStatusText -App $App
|
|
$uninstallCommand = Get-AppUninstallCommandText -App $App
|
|
|
|
if ($SkipConfirm) {
|
|
if ($App.Type -eq "Winget" -and $statusText -ne "Installed") {
|
|
Write-Warn "Not installed, skipped uninstall: $($App.Name)"
|
|
return
|
|
}
|
|
|
|
if ($App.Type -eq "WslDistro") {
|
|
Write-Warn "WSL distro uninstall requires single-item confirmation, skipped: $($App.Name)"
|
|
return
|
|
}
|
|
|
|
if ($App.Type -eq "Autostart" -and -not (Test-AutostartEnabled -App $App)) {
|
|
Write-Warn "Autostart not enabled, skipped: $($App.Name)"
|
|
return
|
|
}
|
|
|
|
if ($App.Type -eq "Download" -and -not (Test-DownloadFileExists -App $App)) {
|
|
Write-Warn "Downloaded file not found, skipped delete: $($App.Name)"
|
|
return
|
|
}
|
|
}
|
|
|
|
if (-not $SkipConfirm) {
|
|
Clear-Host
|
|
Write-Title "Uninstall Confirmation"
|
|
Write-Kv -Key "Name" -Value $App.Name
|
|
Write-Kv -Key "Type" -Value (Get-AppTypeText -App $App)
|
|
Write-Kv -Key "Status" -Value $statusText
|
|
Write-Kv -Key "Command" -Value $uninstallCommand
|
|
Write-Host ""
|
|
|
|
if ($App.Type -eq "Winget" -and $statusText -ne "Installed") {
|
|
Write-Warn "$($App.Name) is not installed."
|
|
Wait-User
|
|
return
|
|
}
|
|
|
|
if ($App.Type -eq "Download" -and -not (Test-DownloadFileExists -App $App)) {
|
|
Write-Warn "Downloaded file not found."
|
|
Wait-User
|
|
return
|
|
}
|
|
|
|
if ($App.Type -eq "WslDistro") {
|
|
Write-Warn "This will unregister the WSL distro and delete its Linux files."
|
|
Write-Warn "Only continue if you do not need data inside this distro."
|
|
Write-Host ""
|
|
}
|
|
|
|
if ($App.Type -eq "Winget" -and $App.ProcessName) {
|
|
Write-Hint "Before uninstalling, the script will stop its running process if needed."
|
|
}
|
|
|
|
if ($App.Type -eq "Winget" -and $App.AutostartName) {
|
|
Write-Hint "Related autostart shortcut will also be removed if it exists."
|
|
}
|
|
|
|
Write-Host ""
|
|
$confirm = Read-Host "Are you sure you want to uninstall $($App.Name)? Y/N"
|
|
|
|
if ($confirm -notmatch "^[Yy]$") {
|
|
Write-Warn "Cancelled."
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
$success = $true
|
|
|
|
if ($App.Type -eq "Winget") {
|
|
if (-not (Test-Winget)) {
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
return
|
|
}
|
|
|
|
$relatedAutostartApp = Get-RelatedAutostartApp -App $App
|
|
|
|
if ($null -ne $relatedAutostartApp) {
|
|
Disable-AutostartShortcut -App $relatedAutostartApp -NoPause
|
|
}
|
|
|
|
Stop-AppProcessIfConfigured -App $App | Out-Null
|
|
|
|
$id = [string]$App.Id
|
|
Write-Host ""
|
|
Write-Info "Uninstalling: $($App.Name)"
|
|
Write-Hint "Winget ID: $id"
|
|
Write-Host ""
|
|
|
|
winget uninstall --id $id -e
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
$success = $false
|
|
}
|
|
}
|
|
elseif ($App.Type -eq "WslDistro") {
|
|
if (-not (Test-WslCommand)) {
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
return
|
|
}
|
|
|
|
$distroName = Get-WslDistroName -App $App
|
|
|
|
if ([string]::IsNullOrWhiteSpace($distroName)) {
|
|
Write-ErrorText "Could not determine WSL distro name."
|
|
$success = $false
|
|
}
|
|
elseif (-not (Test-WslDistroInstalled -App $App)) {
|
|
Write-Warn "WSL distro is not installed: $($App.Name)"
|
|
$success = $true
|
|
}
|
|
else {
|
|
Write-Host ""
|
|
Write-Info "Unregistering WSL distro: $distroName"
|
|
Write-Hint "Command: wsl --unregister $distroName"
|
|
Write-Host ""
|
|
|
|
wsl --unregister $distroName
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
$success = $false
|
|
}
|
|
}
|
|
}
|
|
elseif ($App.Type -eq "Autostart") {
|
|
Disable-AutostartShortcut -App $App -NoPause
|
|
$success = $true
|
|
}
|
|
elseif ($App.Type -eq "Download") {
|
|
$success = Remove-DownloadedPackage -App $App -NoPause
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
if ($success) {
|
|
Write-Success "Uninstall finished: $($App.Name)"
|
|
}
|
|
else {
|
|
Write-Warn "Uninstall finished with errors: $($App.Name)"
|
|
Write-Hint "Exit code: $LASTEXITCODE"
|
|
}
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
}
|
|
|
|
function Show-AppInfo {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
Clear-Host
|
|
Write-Title "Software Info"
|
|
Write-Kv -Key "Name" -Value $App.Name
|
|
Write-Kv -Key "Type" -Value (Get-AppTypeText -App $App)
|
|
Write-Kv -Key "Status" -Value (Get-AppStatusText -App $App)
|
|
Write-Kv -Key "Command" -Value (Get-AppCommandText -App $App)
|
|
|
|
if (Test-AppSupportsUninstall -App $App) {
|
|
Write-Kv -Key "Uninstall" -Value (Get-AppUninstallCommandText -App $App)
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
if ($App.Type -eq "Winget") {
|
|
if (Test-Winget) {
|
|
$id = [string]$App.Id
|
|
winget show --id $id -e
|
|
}
|
|
}
|
|
elseif ($App.Type -eq "WslBase" -or $App.Type -eq "WslDistro") {
|
|
if (Test-WslCommand) {
|
|
Write-Host ""
|
|
Write-Info "WSL status:"
|
|
wsl --status
|
|
|
|
Write-Host ""
|
|
Write-Info "Installed WSL distributions:"
|
|
wsl --list --verbose
|
|
|
|
Write-Host ""
|
|
Write-Info "Available online distributions:"
|
|
wsl --list --online
|
|
}
|
|
}
|
|
elseif ($App.Type -eq "Command") {
|
|
Write-Hint "This item runs a command directly."
|
|
}
|
|
elseif ($App.Type -eq "Autostart") {
|
|
Write-Hint "This item creates or removes a shortcut in the current user's Startup folder."
|
|
Write-Host ""
|
|
Write-Info "Autostart details:"
|
|
Write-Kv -Key "Startup" -Value (Get-StartupFolder)
|
|
Write-Kv -Key "Shortcut" -Value (Get-AutostartShortcutPath -App $App)
|
|
Write-Kv -Key "Running" -Value ($(if (Test-AutostartProcessRunning -App $App) { "Running" } else { "Not running" }))
|
|
if ($App.Url) {
|
|
Write-Kv -Key "Web UI" -Value ([string]$App.Url)
|
|
}
|
|
}
|
|
elseif ($App.Type -eq "Download") {
|
|
Write-Hint "This item downloads a file to Downloads\packages by default."
|
|
Write-Host ""
|
|
Write-Info "Download details:"
|
|
Write-Kv -Key "URL" -Value ([string]$App.Url)
|
|
Write-Kv -Key "File" -Value (Get-DownloadDestinationPath -App $App)
|
|
Write-Kv -Key "Exists" -Value ($(if (Test-DownloadFileExists -App $App) { "Yes" } else { "No" }))
|
|
}
|
|
else {
|
|
Write-Warn "Unknown app type."
|
|
}
|
|
|
|
Wait-User
|
|
}
|
|
|
|
function Invoke-AppAction {
|
|
param (
|
|
[hashtable]$App,
|
|
[switch]$SkipConfirm,
|
|
[switch]$NoPause
|
|
)
|
|
|
|
$actionText = Get-PrimaryActionText -App $App
|
|
$statusText = Get-AppStatusText -App $App
|
|
|
|
if ($SkipConfirm) {
|
|
if ($App.Type -eq "Winget" -and $statusText -eq "Installed") {
|
|
Write-Warn "Already installed, skipped: $($App.Name)"
|
|
return
|
|
}
|
|
|
|
if ($App.Type -eq "WslBase" -and $statusText -eq "Enabled") {
|
|
Write-Warn "WSL already enabled, skipped."
|
|
return
|
|
}
|
|
|
|
if ($App.Type -eq "WslDistro" -and $statusText -eq "Installed") {
|
|
Write-Warn "Already installed, skipped: $($App.Name)"
|
|
return
|
|
}
|
|
|
|
if ($App.Type -eq "Autostart" -and (Test-AutostartEnabled -App $App)) {
|
|
Write-Warn "Autostart already enabled, skipped: $($App.Name)"
|
|
return
|
|
}
|
|
|
|
if ($App.Type -eq "Download" -and (Test-DownloadFileExists -App $App)) {
|
|
Write-Warn "Already downloaded, skipped: $($App.Name)"
|
|
Write-Hint "Path: $(Get-DownloadDestinationPath -App $App)"
|
|
return
|
|
}
|
|
}
|
|
|
|
if (-not $SkipConfirm) {
|
|
Clear-Host
|
|
Write-Title "$actionText Confirmation"
|
|
Write-Kv -Key "Name" -Value $App.Name
|
|
Write-Kv -Key "Type" -Value (Get-AppTypeText -App $App)
|
|
Write-Kv -Key "Status" -Value $statusText
|
|
Write-Kv -Key "Command" -Value (Get-AppCommandText -App $App)
|
|
Write-Host ""
|
|
|
|
if ($App.Type -eq "Winget" -and $statusText -eq "Installed") {
|
|
$confirm = Read-Host "Already installed. Continue and run winget upgrade/install? Y/N"
|
|
}
|
|
elseif ($App.Type -eq "WslBase" -and $statusText -eq "Enabled") {
|
|
Write-Success "WSL is already enabled."
|
|
Wait-User
|
|
return
|
|
}
|
|
elseif ($App.Type -eq "WslDistro" -and $statusText -eq "Installed") {
|
|
Write-Success "$($App.Name) is already installed."
|
|
Wait-User
|
|
return
|
|
}
|
|
elseif ($App.Type -eq "Command") {
|
|
$confirm = Read-Host "Are you sure you want to run $($App.Name)? Y/N"
|
|
}
|
|
elseif ($App.Type -eq "Download") {
|
|
$confirm = Read-Host "Are you sure you want to download $($App.Name)? Y/N"
|
|
}
|
|
else {
|
|
$confirm = Read-Host "Are you sure you want to install $($App.Name)? Y/N"
|
|
}
|
|
|
|
if ($confirm -notmatch "^[Yy]$") {
|
|
Write-Host ""
|
|
Write-Warn "Cancelled."
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
$success = $true
|
|
|
|
if ($App.Type -eq "Winget") {
|
|
if (-not (Test-Winget)) {
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
return
|
|
}
|
|
|
|
$id = [string]$App.Id
|
|
$isInstalled = Test-WingetInstalled -Id $id
|
|
|
|
Write-Host ""
|
|
|
|
if ($isInstalled) {
|
|
Write-Info "Upgrading: $($App.Name)"
|
|
Write-Hint "Winget ID: $id"
|
|
Write-Host ""
|
|
|
|
winget upgrade --id $id -e --accept-source-agreements --accept-package-agreements
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Warn "Upgrade command did not complete successfully. Trying install command..."
|
|
winget install --id $id -e --accept-source-agreements --accept-package-agreements
|
|
}
|
|
}
|
|
else {
|
|
Write-Info "Installing: $($App.Name)"
|
|
Write-Hint "Winget ID: $id"
|
|
Write-Host ""
|
|
|
|
winget install --id $id -e --accept-source-agreements --accept-package-agreements
|
|
}
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
$success = $false
|
|
}
|
|
}
|
|
elseif ($App.Type -eq "WslBase") {
|
|
Install-WslBase -NoPause:$NoPause | Out-Null
|
|
return
|
|
}
|
|
elseif ($App.Type -eq "WslDistro") {
|
|
if (-not (Ensure-WslReady -NoPause:$NoPause)) {
|
|
return
|
|
}
|
|
|
|
if (-not (Test-IsAdmin)) {
|
|
Restart-ScriptAsAdmin
|
|
return
|
|
}
|
|
|
|
$exe = [string]$App.Exe
|
|
$cmdArgs = @($App.Args)
|
|
|
|
Write-Host ""
|
|
Write-Info "Installing: $($App.Name)"
|
|
Write-Hint "Command: $(Get-AppCommandText -App $App)"
|
|
Write-Host ""
|
|
|
|
& $exe @cmdArgs
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
$success = $false
|
|
}
|
|
}
|
|
elseif ($App.Type -eq "Autostart") {
|
|
Invoke-AutostartAction -App $App -SkipConfirm:$SkipConfirm -NoPause:$NoPause
|
|
return
|
|
}
|
|
elseif ($App.Type -eq "Download") {
|
|
$success = Invoke-DownloadPackage -App $App -SkipConfirm:$SkipConfirm -NoPause:$NoPause
|
|
}
|
|
elseif ($App.Type -eq "Command") {
|
|
$exe = [string]$App.Exe
|
|
$cmdArgs = @($App.Args)
|
|
|
|
if (-not (Test-CommandExists -CommandName $exe)) {
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
return
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Info "Running: $($App.Name)"
|
|
Write-Hint "Command: $(Get-AppCommandText -App $App)"
|
|
Write-Host ""
|
|
|
|
& $exe @cmdArgs
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
$success = $false
|
|
}
|
|
}
|
|
else {
|
|
Write-Warn "Unknown app type."
|
|
$success = $false
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
if ($success) {
|
|
Write-Success "Finished: $($App.Name)"
|
|
}
|
|
else {
|
|
Write-Warn "Command finished with errors: $($App.Name)"
|
|
Write-Hint "Exit code: $LASTEXITCODE"
|
|
}
|
|
|
|
if (-not $NoPause) {
|
|
Wait-User
|
|
}
|
|
}
|
|
|
|
function Show-SingleAppActionMenu {
|
|
param (
|
|
[hashtable]$App
|
|
)
|
|
|
|
if ($App.Type -eq "Autostart") {
|
|
Show-AutostartActionMenu -App $App
|
|
return
|
|
}
|
|
|
|
while ($true) {
|
|
Clear-Host
|
|
Write-Title "Selected Software"
|
|
|
|
Write-Kv -Key "Name" -Value $App.Name
|
|
Write-Kv -Key "Type" -Value (Get-AppTypeText -App $App)
|
|
Write-Kv -Key "Status" -Value (Get-AppStatusText -App $App)
|
|
Write-Kv -Key "Command" -Value (Get-AppCommandText -App $App)
|
|
|
|
Write-Host ""
|
|
Write-Option -Key "1" -Text (Get-PrimaryActionText -App $App)
|
|
Write-Option -Key "2" -Text "Show info"
|
|
|
|
if (Test-AppSupportsUninstall -App $App) {
|
|
Write-Option -Key "3" -Text "Uninstall"
|
|
}
|
|
|
|
Write-Option -Key "B" -Text "Back"
|
|
Write-Option -Key "Q" -Text "Quit"
|
|
Write-Host ""
|
|
|
|
$choice = Read-Host "Choose"
|
|
|
|
switch -Regex ($choice) {
|
|
"^1$" {
|
|
Invoke-AppAction -App $App
|
|
}
|
|
|
|
"^2$" {
|
|
Show-AppInfo -App $App
|
|
}
|
|
|
|
"^3$" {
|
|
if (Test-AppSupportsUninstall -App $App) {
|
|
Invoke-AppUninstall -App $App
|
|
}
|
|
else {
|
|
Write-Warn "Uninstall is not supported for this item."
|
|
Wait-User
|
|
}
|
|
}
|
|
|
|
"^[Bb]$" {
|
|
return
|
|
}
|
|
|
|
"^[Qq]$" {
|
|
exit
|
|
}
|
|
|
|
default {
|
|
Write-Warn "Invalid input."
|
|
Wait-User
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function Show-MultiAppActionMenu {
|
|
param (
|
|
[array]$SelectedApps
|
|
)
|
|
|
|
while ($true) {
|
|
Clear-Host
|
|
Write-Title "Selected Software List"
|
|
|
|
for ($i = 0; $i -lt $SelectedApps.Count; $i++) {
|
|
$app = $SelectedApps[$i]
|
|
$typeText = Get-AppTypeText -App $app
|
|
$statusText = Get-AppStatusText -App $app
|
|
|
|
Write-MenuItem -Number ($i + 1) -Name $app.Name -Extra "[$typeText] [$statusText]"
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Option -Key "1" -Text "Install selected, skip installed"
|
|
Write-Option -Key "2" -Text "Show info one by one"
|
|
Write-Option -Key "3" -Text "Uninstall selected, skip unsupported/not installed"
|
|
Write-Option -Key "B" -Text "Back"
|
|
Write-Option -Key "Q" -Text "Quit"
|
|
Write-Host ""
|
|
|
|
Write-Hint "Installed software / WSL distros / enabled autostart items / downloaded files will be skipped."
|
|
Write-Hint "Maintenance commands will still run."
|
|
Write-Hint "Batch uninstall skips WSL distros. Unregister WSL distros from the single-item menu."
|
|
Write-Host ""
|
|
|
|
$choice = Read-Host "Choose"
|
|
|
|
switch -Regex ($choice) {
|
|
"^1$" {
|
|
Write-Host ""
|
|
$confirm = Read-Host "Install selected items and skip installed ones? Y/N"
|
|
|
|
if ($confirm -notmatch "^[Yy]$") {
|
|
Write-Warn "Cancelled."
|
|
Wait-User
|
|
return
|
|
}
|
|
|
|
foreach ($app in $SelectedApps) {
|
|
Invoke-AppAction -App $app -SkipConfirm -NoPause
|
|
}
|
|
|
|
Wait-User
|
|
}
|
|
|
|
"^2$" {
|
|
foreach ($app in $SelectedApps) {
|
|
Show-AppInfo -App $app
|
|
}
|
|
}
|
|
|
|
"^3$" {
|
|
Write-Host ""
|
|
Write-Warn "This will uninstall selected winget packages, remove selected autostart items, and delete selected downloaded files."
|
|
Write-Hint "WSL distros are skipped in batch uninstall to avoid accidental data loss."
|
|
$confirm = Read-Host "Uninstall selected items? Y/N"
|
|
|
|
if ($confirm -notmatch "^[Yy]$") {
|
|
Write-Warn "Cancelled."
|
|
Wait-User
|
|
return
|
|
}
|
|
|
|
foreach ($app in $SelectedApps) {
|
|
Invoke-AppUninstall -App $app -SkipConfirm -NoPause
|
|
}
|
|
|
|
Wait-User
|
|
}
|
|
|
|
"^[Bb]$" {
|
|
return
|
|
}
|
|
|
|
"^[Qq]$" {
|
|
exit
|
|
}
|
|
|
|
default {
|
|
Write-Warn "Invalid input."
|
|
Wait-User
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function Show-GroupMenu {
|
|
param (
|
|
[string]$GroupName,
|
|
[array]$Apps
|
|
)
|
|
|
|
while ($true) {
|
|
Clear-Host
|
|
Write-Title "Category: $GroupName"
|
|
|
|
for ($i = 0; $i -lt $Apps.Count; $i++) {
|
|
$app = $Apps[$i]
|
|
Write-MenuItem -Number ($i + 1) -Name $app.Name -Extra "[$(Get-AppTypeText -App $app)]"
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Option -Key "B" -Text "Back"
|
|
Write-Option -Key "Q" -Text "Quit"
|
|
Write-Host ""
|
|
|
|
Write-Hint "Input examples:"
|
|
Write-Hint " 1 single software"
|
|
Write-Hint " 1-3 range"
|
|
Write-Hint " 2,3,6 multiple software"
|
|
Write-Hint " 1-5,8,10 mixed range and numbers"
|
|
Write-Host ""
|
|
|
|
$choice = Read-Host "Choose software"
|
|
|
|
switch -Regex ($choice) {
|
|
"^[Qq]$" {
|
|
exit
|
|
}
|
|
|
|
"^[Bb]$" {
|
|
return
|
|
}
|
|
|
|
default {
|
|
$numbers = Convert-SelectionToNumbers -InputText $choice -MaxNumber $Apps.Count
|
|
|
|
if ($null -eq $numbers) {
|
|
Write-Warn "Invalid input."
|
|
Wait-User
|
|
continue
|
|
}
|
|
|
|
$selectedApps = @()
|
|
|
|
foreach ($number in $numbers) {
|
|
$selectedApps += $Apps[$number - 1]
|
|
}
|
|
|
|
if ($selectedApps.Count -eq 1) {
|
|
Show-SingleAppActionMenu -App $selectedApps[0]
|
|
}
|
|
else {
|
|
Show-MultiAppActionMenu -SelectedApps $selectedApps
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function Show-MainMenu {
|
|
$groups = @($SoftwareGroups.Keys)
|
|
|
|
while ($true) {
|
|
Clear-Host
|
|
Write-Title "Software Install Menu"
|
|
|
|
for ($i = 0; $i -lt $groups.Count; $i++) {
|
|
Write-MenuItem -Number ($i + 1) -Name $groups[$i]
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Option -Key "Q" -Text "Quit"
|
|
Write-Host ""
|
|
|
|
$choice = Read-Host "Choose category"
|
|
|
|
if ($choice -match "^[Qq]$") {
|
|
exit
|
|
}
|
|
|
|
if ($choice -match "^\d+$") {
|
|
$number = [int]$choice
|
|
|
|
if ($number -ge 1 -and $number -le $groups.Count) {
|
|
$groupName = $groups[$number - 1]
|
|
Show-GroupMenu -GroupName $groupName -Apps $SoftwareGroups[$groupName]
|
|
}
|
|
else {
|
|
Write-Warn "Invalid category number."
|
|
Wait-User
|
|
}
|
|
}
|
|
else {
|
|
Write-Warn "Invalid input."
|
|
Wait-User
|
|
}
|
|
}
|
|
}
|
|
|
|
Show-MainMenu
|