# Watchroom para Windows. Para instalar ou atualizar, cole no PowerShell: # irm https://srv1822509.hstgr.cloud/install.ps1 | iex # O app também usa este script para se atualizar: passa -Package com o pacote que ele já baixou e conferiu a assinatura. param([string]$Package, [switch]$Quiet) & { # Bloco próprio: com iex o script roda na sessão de quem colou o comando; nada aqui deve vazar para ela. $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' # a barra nativa deixa o Invoke-RestMethod lento no PS 5.1 e também esconderia Write-Progress [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 # O app abre este script de dentro da pasta instalada; o Windows e o PowerShell não deixam mover a pasta atual. Push-Location $env:TEMP [IO.Directory]::SetCurrentDirectory($env:TEMP) $site = 'https://srv1822509.hstgr.cloud' $dir = Join-Path $env:LOCALAPPDATA 'Programs\Watchroom' $exe = Join-Path $dir 'watchroom.exe' $startMenu = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs\Watchroom.lnk' $uninstallKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\Watchroom' # Instalação antiga feita pelo instalador Inno, quando o app se chamava Salas. $legacyKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\{6E0B5C2A-4F1D-4B8E-9C3A-5A1D2F7E8B90}_is1' function Say([string]$text) { if (!$Quiet) { Write-Host $text } } # Progresso numa linha só, reescrita com `r: aparece em qualquer console, ao contrário do Write-Progress. function Progress([string]$text) { if (!$Quiet) { Write-Host -NoNewline "`r $text " } } if (![Environment]::Is64BitOperatingSystem) { throw 'O Watchroom precisa do Windows 10 ou 11 de 64 bits.' } $work = Join-Path $env:TEMP 'watchroom-install' New-Item -ItemType Directory -Force $work | Out-Null try { if (!$Package) { Say 'Procurando a versão mais recente do Watchroom...' $manifest = Invoke-RestMethod -UseBasicParsing "$site/downloads/windows.json" if (!"$($manifest.url)".StartsWith("$site/downloads/") -or "$($manifest.sha256)" -notmatch '^[0-9a-f]{64}$' -or "$($manifest.version)" -notmatch '^\d+\.\d+\.\d+$') { throw 'O site respondeu com dados de versão inválidos. Tente novamente mais tarde.' } $Package = Join-Path $work "Watchroom-$($manifest.version).zip" Say "Baixando o Watchroom $($manifest.version)..." # Download com progresso customizado (evita a lentidão do Invoke-WebRequest no PS 5.1) $request = [Net.WebRequest]::Create($manifest.url) $response = $request.GetResponse() $totalBytes = $response.ContentLength $stream = $response.GetResponseStream() $fileStream = [IO.File]::Create($Package) $buffer = New-Object byte[] 65536 $downloaded = 0 $timer = [Diagnostics.Stopwatch]::StartNew() try { do { $read = $stream.Read($buffer, 0, $buffer.Length) if ($read -gt 0) { $fileStream.Write($buffer, 0, $read) $downloaded += $read # Atualiza a barra a cada 250ms if ($timer.ElapsedMilliseconds -gt 250) { if ($totalBytes -gt 0) { $percent = [math]::Round(($downloaded / $totalBytes) * 100, 1) $mb = [math]::Round($downloaded / 1MB, 1) $totalMb = [math]::Round($totalBytes / 1MB, 1) Progress "$percent% ($mb MB de $totalMb MB)" } else { $mb = [math]::Round($downloaded / 1MB, 1) Progress "$mb MB baixados" } $timer.Restart() } } } while ($read -gt 0) } finally { Progress "$([math]::Round($downloaded / 1MB, 1)) MB baixados"; Say '' $fileStream.Close() $stream.Close() $response.Close() } Say 'Verificando integridade...' if ((Get-FileHash -Algorithm SHA256 $Package).Hash.ToLower() -ne $manifest.sha256) { Remove-Item -LiteralPath $Package -Force throw 'O download veio corrompido. Rode o comando de novo.' } } elseif (!(Test-Path -LiteralPath $Package)) { throw "Pacote não encontrado: $Package" } Say 'Extraindo...' $staging = "$dir.new" if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging -Recurse -Force } Add-Type -AssemblyName System.IO.Compression.FileSystem # Extração com barra de progresso customizada $zip = [IO.Compression.ZipFile]::OpenRead($Package) $entries = $zip.Entries $totalEntries = $entries.Count $extracted = 0 $timer = [Diagnostics.Stopwatch]::StartNew() $root = [IO.Path]::GetFullPath($staging) + [IO.Path]::DirectorySeparatorChar try { foreach ($entry in $entries) { $destPath = [IO.Path]::GetFullPath([IO.Path]::Combine($staging, $entry.FullName)) # Proteção contra ZipSlip if (-not $destPath.StartsWith($root, [StringComparison]::OrdinalIgnoreCase)) { continue } if ($entry.FullName.EndsWith('/') -or $entry.FullName.EndsWith('\')) { [IO.Directory]::CreateDirectory($destPath) | Out-Null } else { $dirName = [IO.Path]::GetDirectoryName($destPath) if (-not [IO.Directory]::Exists($dirName)) { [IO.Directory]::CreateDirectory($dirName) | Out-Null } $entryStream = $entry.Open() $outFile = [IO.File]::Create($destPath) $entryStream.CopyTo($outFile) $outFile.Close() $entryStream.Close() } $extracted++ if ($timer.ElapsedMilliseconds -gt 250) { $percent = [math]::Round(($extracted / $totalEntries) * 100) Progress "$percent%" $timer.Restart() } } } finally { Progress "$extracted de $totalEntries arquivos"; Say '' $zip.Dispose() } if (!(Test-Path -LiteralPath (Join-Path $staging 'watchroom.exe'))) { throw 'Pacote incompleto: watchroom.exe ausente.' } $version = (Get-Item (Join-Path $staging 'watchroom.exe')).VersionInfo.ProductVersion # O app fecha sozinho ao se atualizar; espera um pouco antes de forçar. $running = @(Get-Process -Name watchroom -ErrorAction SilentlyContinue | Where-Object { $_.Path -and $_.Path.StartsWith($dir, [StringComparison]::OrdinalIgnoreCase) }) if ($running) { Say 'Fechando o Watchroom...' $running | Wait-Process -Timeout 15 -ErrorAction SilentlyContinue $running | Where-Object { !$_.HasExited } | Stop-Process -Force Start-Sleep -Milliseconds 500 } $legacy = Get-ItemProperty -LiteralPath $legacyKey -ErrorAction SilentlyContinue # Só remove a instalação antiga que está na pasta por usuário esperada. if ($legacy -and "$($legacy.InstallLocation)".StartsWith($env:LOCALAPPDATA, [StringComparison]::OrdinalIgnoreCase)) { Say 'Removendo a versão antiga (Salas)...' Get-Process -Name salas-client -ErrorAction SilentlyContinue | Stop-Process -Force $uninstaller = "$($legacy.UninstallString)".Trim('"') if (Test-Path -LiteralPath $uninstaller) { Start-Process -FilePath $uninstaller -ArgumentList '/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART' -Wait } } # Troca as pastas inteiras: uma atualização nunca deixa arquivos de versões diferentes misturados. $old = "$dir.old" if (Test-Path -LiteralPath $old) { Remove-Item -LiteralPath $old -Recurse -Force } if (Test-Path -LiteralPath $dir) { Move-Item -LiteralPath $dir -Destination $old } try { New-Item -ItemType Directory -Force (Split-Path $dir) | Out-Null Move-Item -LiteralPath $staging -Destination $dir } catch { if (Test-Path -LiteralPath $old) { Move-Item -LiteralPath $old -Destination $dir } throw } if (Test-Path -LiteralPath $old) { Remove-Item -LiteralPath $old -Recurse -Force -ErrorAction SilentlyContinue } $firstInstall = !(Test-Path -LiteralPath $startMenu) $shell = New-Object -ComObject WScript.Shell $shortcuts = @($startMenu) if ($firstInstall) { $shortcuts += Join-Path ([Environment]::GetFolderPath('Desktop')) 'Watchroom.lnk' } foreach ($path in $shortcuts) { New-Item -ItemType Directory -Force (Split-Path $path) | Out-Null $link = $shell.CreateShortcut($path) $link.TargetPath = $exe $link.WorkingDirectory = $dir $link.IconLocation = "$exe,0" $link.Save() } # Links de convite: o site abre watchroom://; salas:// é de antes da troca de nome. foreach ($scheme in 'watchroom', 'salas') { $key = "HKCU:\Software\Classes\$scheme" New-Item -Force "$key\shell\open\command" | Out-Null New-Item -Force "$key\DefaultIcon" | Out-Null Set-ItemProperty -LiteralPath $key -Name '(default)' -Value 'URL:Watchroom' Set-ItemProperty -LiteralPath $key -Name 'URL Protocol' -Value '' Set-ItemProperty -LiteralPath "$key\DefaultIcon" -Name '(default)' -Value "`"$exe`",0" Set-ItemProperty -LiteralPath "$key\shell\open\command" -Name '(default)' -Value "`"$exe`" `"%1`"" } $uninstall = @' # Remove o Watchroom. As configurações em %APPDATA%\Watchroom ficam guardadas. $dir = Split-Path -Parent $MyInvocation.MyCommand.Path Get-Process -Name watchroom -ErrorAction SilentlyContinue | Where-Object { $_.Path -and $_.Path.StartsWith($dir, [StringComparison]::OrdinalIgnoreCase) } | Stop-Process -Force Start-Sleep -Milliseconds 500 Remove-Item -LiteralPath (Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs\Watchroom.lnk'), (Join-Path ([Environment]::GetFolderPath('Desktop')) 'Watchroom.lnk') -Force -ErrorAction SilentlyContinue foreach ($scheme in 'watchroom', 'salas') { $command = (Get-ItemProperty -LiteralPath "HKCU:\Software\Classes\$scheme\shell\open\command" -ErrorAction SilentlyContinue).'(default)' if ("$command".StartsWith("`"$dir", [StringComparison]::OrdinalIgnoreCase)) { Remove-Item -LiteralPath "HKCU:\Software\Classes\$scheme" -Recurse -Force } } Remove-Item -LiteralPath 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\Watchroom' -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $dir -Recurse -Force '@ Set-Content -LiteralPath (Join-Path $dir 'uninstall.ps1') -Value $uninstall -Encoding UTF8 New-Item -Force $uninstallKey | Out-Null $size = [int]((Get-ChildItem -LiteralPath $dir -Recurse -File | Measure-Object -Sum Length).Sum / 1KB) $values = @{ DisplayName = 'Watchroom'; DisplayVersion = "$version"; Publisher = 'Watchroom'; DisplayIcon = "$exe,0" InstallLocation = $dir; UninstallString = "powershell.exe -NoProfile -ExecutionPolicy Bypass -File `"$dir\uninstall.ps1`"" } foreach ($name in $values.Keys) { Set-ItemProperty -LiteralPath $uninstallKey -Name $name -Value $values[$name] } foreach ($name in 'NoModify', 'NoRepair') { Set-ItemProperty -LiteralPath $uninstallKey -Name $name -Value 1 -Type DWord } Set-ItemProperty -LiteralPath $uninstallKey -Name EstimatedSize -Value $size -Type DWord Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue Say "Watchroom $version instalado. Abrindo..." Start-Process -FilePath $exe -WorkingDirectory $dir } catch { # Na atualização o app já fechou: reabre a versão que ficou instalada e guarda o motivo. if ($Quiet) { Set-Content -LiteralPath (Join-Path $work 'erro.txt') -Value "$_" -Encoding UTF8 -ErrorAction SilentlyContinue if (Test-Path -LiteralPath $exe) { Start-Process -FilePath $exe -WorkingDirectory $dir } } throw } finally { Pop-Location -ErrorAction SilentlyContinue } }