# ========================= # CONFIG # ========================= if ([Environment]::Is64BitOperatingSystem) { $AppZipUrl = "https://get.classpad.dev/LauncherUI-x64.zip" } else { $AppZipUrl = "https://get.classpad.dev/LauncherUI-x86.zip" } $AppExeName = "LauncherUI.exe" # ========================= # Helpers # ========================= function Is-Admin { $id = [Security.Principal.WindowsIdentity]::GetCurrent() $p = New-Object Security.Principal.WindowsPrincipal($id) $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } # ========================= # Dependency Definitions # ========================= $Dependencies = @( @{ Name = "DotNet48" Test = { $k = "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" (Test-Path $k) -and ((Get-ItemProperty $k).Release -ge 528040) } Url = "https://go.microsoft.com/fwlink/?LinkId=2085155" File = "dotnet48.exe" Args = "/quiet /norestart" }, @{ Name = "VCx86" Test = { if ([Environment]::Is64BitOperatingSystem) { $k = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x86" } else { $k = "HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x86" } (Test-Path $k) -and ((Get-ItemProperty $k).Installed -eq 1) } Url = "https://aka.ms/vc14/vc_redist.x86.exe" File = "vc_x86.exe" Args = "/quiet /norestart" }, @{ Name = "VCx64" Test = { if (![Environment]::Is64BitOperatingSystem) { return $true } $k="HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64" (Test-Path $k) -and ((Get-ItemProperty $k).Installed -eq 1) } Url = "https://aka.ms/vc14/vc_redist.x64.exe" File = "vc_x64.exe" Args = "/quiet /norestart" }, @{ Name = "WebView2" Test = { $guid = "{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" if ([Environment]::Is64BitOperatingSystem) { $paths = @( "HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\$guid", "HKCU:\Software\Microsoft\EdgeUpdate\Clients\$guid" ) } else { $paths = @( "HKLM:\SOFTWARE\Microsoft\EdgeUpdate\Clients\$guid", "HKCU:\Software\Microsoft\EdgeUpdate\Clients\$guid" ) } foreach ($path in $paths) { if (Test-Path $path) { $pv = (Get-ItemProperty $path -ErrorAction SilentlyContinue).pv if (-not [string]::IsNullOrWhiteSpace($pv)) { try { $ver = [version]$pv if ($ver -gt [version]"0.0.0.0") { return $true } } catch { # malformed version string -> ignore } } } } return $false } Url = "https://go.microsoft.com/fwlink/p/?LinkId=2124703" File = "webview2.exe" Args = "/silent /install" } ) # ========================= # Detect Missing # ========================= $Missing = $Dependencies | Where-Object { -not (& $_.Test) } # ========================= # SINGLE INSTALL BLOCK # ========================= $InstallBlock = { param($Deps) try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {} $path = "$env:APPDATA\hollyhock-launcher\deps" if (-not (Test-Path -PathType container $path)) { New-Item -Path $path -ItemType Directory } foreach($d in $Deps){ $filePath = "$path\$($d.File)" if(-not (Test-Path $filePath)){ (New-Object Net.WebClient).DownloadFile($d.Url,$filePath) } Write-Host "Installing $($d.Name)" Start-Process $filePath -ArgumentList $d.Args -Wait } } # ========================= # Execute Install Block # ========================= if ($Missing.Count -gt 0) { if (Is-Admin) { & $InstallBlock $Missing } else{ Write-Host "Elevation required for dependency installation..." $encodedBlock = [Convert]::ToBase64String( [Text.Encoding]::Unicode.GetBytes($InstallBlock.ToString()) ) $payload = [Convert]::ToBase64String( [Text.Encoding]::Unicode.GetBytes( ($Missing | ConvertTo-Json -Depth 5 -Compress) ) ) $script = @" `$block = [ScriptBlock]::Create( [Text.Encoding]::Unicode.GetString( [Convert]::FromBase64String("$encodedBlock") ) ) `$deps = [Text.Encoding]::Unicode.GetString( [Convert]::FromBase64String("$payload") ) | ConvertFrom-Json & `$block `$deps "@ $pipeName = "dep_install_" + [guid]::NewGuid().ToString() $pipe = New-Object System.IO.Pipes.NamedPipeServerStream( $pipeName, [System.IO.Pipes.PipeDirection]::Out ) $bootstrap = @" `$pipe = New-Object System.IO.Pipes.NamedPipeClientStream('.', '$pipeName', [System.IO.Pipes.PipeDirection]::In) `$pipe.Connect() `$reader = New-Object System.IO.StreamReader(`$pipe) `$script = `$reader.ReadToEnd() Invoke-Expression `$script "@ $encodedBootstrap = [Convert]::ToBase64String( [Text.Encoding]::Unicode.GetBytes($bootstrap) ) $proc = Start-Process powershell ` -Verb RunAs ` -ArgumentList "-NoProfile -ExecutionPolicy Bypass -EncodedCommand $encodedBootstrap" ` -PassThru $pipe.WaitForConnection() $writer = New-Object System.IO.StreamWriter($pipe) $writer.Write($script) $writer.Flush() $writer.Dispose() $pipe.Dispose() $proc.WaitForExit() } } # ========================= # App Phase # ========================= Write-Host "Downloading application..." $dirPrefix = "$env:APPDATA\hollyhock-launcher" $appDir = "$dirPrefix\app" if (-not (Test-Path -PathType container $dirPrefix)) { New-Item -Path $dirPrefix -ItemType Directory } else { if (Test-Path -PathType container $appDir) { Remove-Item $appDir -Recurse -Force } } $zipFile = "$appDir.zip" (New-Object Net.WebClient).DownloadFile($AppZipUrl,$zipFile) Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory($zipFile,$appDir) Write-Host "Launching application..." Start-Process "$appDir\$AppExeName"