# QIA Research - pemasang lewat PowerShell (versi pratinjau) # # Memasang atau memperbarui QIA Research dari https://demo.qia-research.com: # # irm https://demo.qia-research.com/pasang.ps1 | iex # # Ingin membaca skrip ini dulu sebelum menjalankannya (dianjurkan): # # irm https://demo.qia-research.com/pasang.ps1 -OutFile pasang.ps1 # notepad pasang.ps1 # powershell -ExecutionPolicy Bypass -File .\pasang.ps1 # # Yang dikerjakan, berurutan: # 1. membaca data/unduhan.json (nama berkas, versi, SHA-256) dari situs; # 2. mengunduh paket ke folder sementara; # 3. mencocokkan SHA-256 dan BERHENTI bila berbeda; # 4. MENUTUP QIA Research bila sedang berjalan - hanya proses yang berkas # programnya ada di dalam folder pemasangan, tidak pernah proses lain; # 5. mengekstrak paket (tiga berkas) dengan tar bawaan Windows; # 6. menjalankan "Pasang QIA Research.bat" - pemasang yang sama dengan unduhan # lewat peramban: aplikasi ke %LOCALAPPDATA%\Programs\QIA Research, data Anda # dipertahankan saat memperbarui, pintasan dibuat di desktop; # 7. menghapus folder sementara. # # Menutup aplikasi sendiri lebih dulu (bila ingin memastikan): # Get-Process | Where-Object { $_.Path -like "$env:LOCALAPPDATA\Programs\QIA Research\*" } | Stop-Process # # Batas yang perlu diketahui: pencocokan SHA-256 melindungi dari unduhan yang rusak # atau terpotong, bukan dari server yang diretas (skrip dan hash datang dari situs # yang sama). Paket pratinjau belum ditandatangani secara digital. # # Rancang-bangun dan penerbit: Dr. Adam M. - bangun/pasang.ps1 & { # Continue, bukan Stop: di PowerShell 5.1 keluaran stderr program bawaan (tar, cmd) # berubah menjadi galat yang menghentikan skrip bila Stop. Cmdlet yang harus # berhenti diberi -ErrorAction Stop satu per satu. $ErrorActionPreference = 'Continue' $ProgressPreference = 'SilentlyContinue' # bilah kemajuan membuat unduhan PowerShell 5.1 sangat lambat try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch { } $Situs = 'https://demo.qia-research.com' if ($env:QIA_SITUS) { $Situs = $env:QIA_SITUS.TrimEnd('/') } # Paket hanya diunduh lewat HTTPS; HTTP hanya untuk mesin sendiri (uji). if (-not ($Situs -match '^https://' -or $Situs -match '^http://(127\.0\.0\.1|localhost)(:\d+)?$')) { Write-Host "Alamat situs ditolak (hanya HTTPS): $Situs" -ForegroundColor Red return } Write-Host '' Write-Host ' QIA Research - pemasang lewat PowerShell (versi pratinjau)' -ForegroundColor Cyan Write-Host " Sumber: $Situs" Write-Host '' $tar = Join-Path $env:SystemRoot 'System32\tar.exe' if (-not (Test-Path $tar)) { Write-Host 'tar.exe bawaan Windows tidak ditemukan. Diperlukan Windows 10 versi 1803 atau lebih baru.' -ForegroundColor Red return } try { $info = Invoke-RestMethod -Uri "$Situs/data/unduhan.json" -UseBasicParsing -ErrorAction Stop } catch { Write-Host "Informasi paket tidak dapat dibaca dari $Situs. Periksa sambungan internet, lalu coba lagi." -ForegroundColor Red return } if (-not ($info.berkas -match '^[A-Za-z0-9._-]+\.zip$') -or -not ($info.sha256 -match '^[0-9a-fA-F]{64}$')) { Write-Host 'Informasi paket di situs tidak berbentuk sah. Pemasangan dibatalkan.' -ForegroundColor Red return } $versi = if ($info.versi) { "versi $($info.versi)" } else { 'versi terbaru' } # Pemasang .exe dipakai HANYA bila situs menyatakan ia ditandatangani DAN # tanda tangannya terbukti sah di mesin ini. Exe tanpa tanda tangan # menghadapi SmartScreen ("Windows protected your PC"), yang lebih # menakutkan daripada jalur zip - jadi tanpa tanda tangan, zip yang dipakai. $exe = $info.pemasang_exe $pakaiExe = $false if ($exe -and $exe.ditandatangani -eq $true) { if (($exe.berkas -match '^[A-Za-z0-9._-]+\.exe$') -and ($exe.sha256 -match '^[0-9a-fA-F]{64}$')) { $pakaiExe = $true } } function Unduh-Paket { # Unduhan yang DILANJUTKAN, bukan diulang dari nol, dan kemajuannya terlihat. # # Kejadiannya 16 September 2026: unduhan 110 MB pemilik putus di tengah # jalan - "An existing connection was forcibly closed by the remote # host" - dan Invoke-WebRequest tidak menyisakan apa pun untuk # dilanjutkan; layarnya juga diam sepanjang unduhan, sehingga tidak ada # cara membedakan "sedang berjalan" dari "macet". Sambungan yang putus # pada berkas sebesar ini bukan keadaan luar biasa: unggahan ke server # yang sama pernah putus di 89 MB. param([string]$Alamat, [string]$Tujuan, [int]$PerkiraanMb) $maksCoba = 5 for ($coba = 1; $coba -le $maksCoba; $coba++) { $sudah = 0 if (Test-Path $Tujuan) { $sudah = (Get-Item $Tujuan).Length } $berkas = $null $aliran = $null $tanggapan = $null try { $minta = [Net.HttpWebRequest]::Create($Alamat) $minta.UserAgent = 'QIA-Research-pemasang' $minta.Timeout = 30000 $minta.ReadWriteTimeout = 120000 if ($sudah -gt 0) { $minta.AddRange([int64]$sudah) } $tanggapan = $minta.GetResponse() # Server yang TIDAK mendukung lanjutan menjawab 200 berisi # SELURUH berkas. Menambahkannya ke potongan lama menghasilkan # berkas rusak yang baru ketahuan di SHA-256 - jadi mulai ulang. if ($sudah -gt 0 -and $tanggapan.StatusCode -ne 'PartialContent') { $sudah = 0 } $tahu_total = $tanggapan.ContentLength -gt 0 $total = $tanggapan.ContentLength + $sudah if (-not $tahu_total) { $total = [int64]$PerkiraanMb * 1MB } $aliran = $tanggapan.GetResponseStream() $mode = if ($sudah -gt 0) { [IO.FileMode]::Append } else { [IO.FileMode]::Create } $berkas = New-Object IO.FileStream($Tujuan, $mode, [IO.FileAccess]::Write, [IO.FileShare]::None) $petak = New-Object byte[] 262144 $jam = [Diagnostics.Stopwatch]::StartNew() $lapor = 0 $awal = $sudah while (($dibaca = $aliran.Read($petak, 0, $petak.Length)) -gt 0) { $berkas.Write($petak, 0, $dibaca) $sudah += $dibaca if (($jam.ElapsedMilliseconds - $lapor) -ge 400) { $lapor = $jam.ElapsedMilliseconds $detik = [Math]::Max($jam.Elapsed.TotalSeconds, 0.001) $laju = (($sudah - $awal) / 1MB) / $detik $persen = [Math]::Min(100, [Math]::Round($sudah * 100 / $total)) Write-Host -NoNewline ("`r {0,5:N0}% - {1,6:N1} dari {2:N0} MB - {3,5:N1} MB/detik " -f ` $persen, ($sudah / 1MB), ($total / 1MB), $laju) } } $berkas.Close(); $berkas = $null $aliran.Close(); $aliran = $null $tanggapan.Close(); $tanggapan = $null # Sambungan yang putus TIDAK selalu melempar galat: bila server # menutup aliran di tengah badan jawaban, Read() sekadar # mengembalikan 0 dan perulangan di atas selesai seolah-olah # normal. Tanpa pemeriksaan ini, berkas separuh itu diserahkan ke # pemeriksaan SHA-256, ditolak sebagai "rusak", lalu dibuang - # padahal yang benar adalah MELANJUTKANNYA. if ($tahu_total -and $sudah -lt $total) { throw ("Aliran berhenti pada {0:N1} dari {1:N1} MB." -f ($sudah / 1MB), ($total / 1MB)) } Write-Host ("`r 100% - {0:N1} MB selesai diunduh. " -f ((Get-Item $Tujuan).Length / 1MB)) return $true } catch { if ($berkas) { $berkas.Close() } if ($aliran) { $aliran.Close() } if ($tanggapan) { $tanggapan.Close() } $ada = 0 if (Test-Path $Tujuan) { $ada = (Get-Item $Tujuan).Length } Write-Host '' Write-Host (" Sambungan terputus pada {0:N1} MB: {1}" -f ($ada / 1MB), $_.Exception.Message) -ForegroundColor Yellow if ($coba -lt $maksCoba) { Write-Host " Melanjutkan unduhan (percobaan $($coba + 1) dari $maksCoba)..." Start-Sleep -Seconds 3 } } } Write-Host 'Unduhan tidak dapat diselesaikan setelah beberapa percobaan.' -ForegroundColor Red Write-Host 'Periksa sambungan internet, lalu jalankan perintah ini lagi - unduhan dilanjutkan dari titik terakhir.' return $false } # Folder sementara BERNAMA TETAP untuk satu versi: unduhan yang putus dapat # dilanjutkan oleh perintah berikutnya, bukan dimulai dari nol lagi. $selesai = $false $sementara = Join-Path $env:TEMP ('qia-pasang-' + ($info.versi -replace '[^0-9A-Za-z.]', '')) if (-not (Test-Path $sementara)) { New-Item -ItemType Directory -Path $sementara -ErrorAction Stop | Out-Null } try { if ($pakaiExe) { $berkas_exe = Join-Path $sementara $exe.berkas Write-Host "[a] Mengunduh pemasang QIA Research $versi ($($exe.ukuran_mb) MB)..." if (-not (Unduh-Paket "$Situs/unduh/$($exe.berkas)" $berkas_exe $exe.ukuran_mb)) { return } Write-Host '[b] Memeriksa keutuhan berkas (SHA-256)...' $hash_exe = (Get-FileHash -Path $berkas_exe -Algorithm SHA256 -ErrorAction Stop).Hash if ($hash_exe -ne $exe.sha256.ToUpperInvariant()) { Remove-Item -LiteralPath $berkas_exe -Force -ErrorAction SilentlyContinue Write-Host 'SHA-256 berkas unduhan TIDAK cocok dengan situs. Pemasangan dibatalkan; tidak ada yang diubah.' -ForegroundColor Red Write-Host 'Berkas unduhan dibuang. Jalankan perintah ini lagi untuk mengunduh ulang.' return } Write-Host '[c] Memeriksa tanda tangan digital...' $tanda = Get-AuthenticodeSignature -LiteralPath $berkas_exe if ($tanda.Status -ne 'Valid') { Write-Host "Tanda tangan digital TIDAK sah ($($tanda.Status)). Pemasangan dibatalkan." -ForegroundColor Red Write-Host 'Unduh lewat halaman situs, atau hubungi Dr. Adam M.' return } Write-Host " Penerbit: $($tanda.SignerCertificate.Subject)" Write-Host '[d] Menjalankan pemasang...' $selesai = $true & $berkas_exe if ($LASTEXITCODE -eq 0) { Write-Host '' Write-Host "QIA Research $versi terpasang. Buka dari pintasan 'QIA Research' di desktop." -ForegroundColor Green Write-Host 'Untuk memperbarui kelak, jalankan perintah yang sama.' } else { Write-Host 'Pemasangan tidak selesai; baca pesan pemasang di atas.' -ForegroundColor Yellow } return } $zip = Join-Path $sementara $info.berkas Write-Host "[a] Mengunduh QIA Research $versi ($($info.ukuran_mb) MB)..." Write-Host ' Unduhan yang terputus dilanjutkan sendiri; biarkan jendela ini terbuka.' if (-not (Unduh-Paket "$Situs/unduh/$($info.berkas)" $zip $info.ukuran_mb)) { return } Write-Host '[b] Memeriksa keutuhan berkas (SHA-256)...' $hash = (Get-FileHash -Path $zip -Algorithm SHA256 -ErrorAction Stop).Hash if ($hash -ne $info.sha256.ToUpperInvariant()) { # Berkasnya DIBUANG: folder sementara kini bernama tetap, jadi # potongan rusak yang ditinggalkan akan "dilanjutkan" perintah # berikutnya dan gagal lagi dengan sebab yang sama selamanya. Remove-Item -LiteralPath $zip -Force -ErrorAction SilentlyContinue Write-Host 'SHA-256 berkas unduhan TIDAK cocok dengan situs. Pemasangan dibatalkan; tidak ada yang diubah.' -ForegroundColor Red Write-Host " diharapkan: $($info.sha256)" Write-Host " diperoleh : $hash" Write-Host 'Berkas unduhan dibuang. Jalankan perintah ini lagi untuk mengunduh ulang.' return } # Pemasang menolak berjalan selama aplikasinya masih memegang foldernya, # dan itu pagar yang benar. Yang ditutup di sini HANYA proses yang # berkas programnya berada DI DALAM folder pemasangan - aturan yang sama # dengan peluncur (smartquery/peluncuran.py): yang tidak terbukti milik # kita tidak pernah disentuh, sebab di PC yang sama ada puluhan proses # milik orang lain. $folderApl = Join-Path $env:LOCALAPPDATA 'Programs\QIA Research' $berjalan = @(Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Path -and $_.Path.StartsWith($folderApl, [StringComparison]::OrdinalIgnoreCase) }) if ($berjalan.Count -gt 0) { Write-Host "[c] QIA Research sedang berjalan; menutupnya lebih dulu ($($berjalan.Count) proses)..." $berjalan | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 2 } Write-Host '[d] Mengekstrak paket...' & $tar -xf $zip -C $sementara if ($LASTEXITCODE -ne 0) { Write-Host 'Paket tidak dapat diekstrak. Coba jalankan perintah ini lagi.' -ForegroundColor Red return } $pemasang = Join-Path $sementara 'Pasang QIA Research.bat' if (-not (Test-Path $pemasang)) { Write-Host 'Pemasang tidak ditemukan di dalam paket.' -ForegroundColor Red return } Write-Host '[e] Menjalankan pemasang...' $selesai = $true & $pemasang if ($LASTEXITCODE -eq 0) { Write-Host '' Write-Host "QIA Research $versi terpasang. Buka dari pintasan 'QIA Research' di desktop." -ForegroundColor Green Write-Host 'Untuk memperbarui kelak, jalankan perintah yang sama.' } else { Write-Host 'Pemasangan tidak selesai; baca pesan pemasang di atas.' -ForegroundColor Yellow } } finally { # Dibersihkan HANYA bila pemasangnya sempat dijalankan. Menghapusnya pada # setiap keluar akan membuang unduhan yang sudah 80 persen - persis yang # membuat sambungan putus menjadi mahal. if ($selesai) { Remove-Item -LiteralPath $sementara -Recurse -Force -ErrorAction SilentlyContinue } elseif (Test-Path $sementara) { Write-Host '' Write-Host "Unduhan sementara disimpan di $sementara" Write-Host 'Jalankan perintah yang sama untuk melanjutkannya.' } } }