param([switch]$Consent, [switch]$CheckOnly) $ErrorActionPreference = 'Stop' $expectedVersion = [version]'0.2.22' $installerHash = 'e93a5b830020d78eea5b6942a90bdfbd8c5d014860ddc7c6b52d490a65ff50e0' $runtimeHash = '988da626bf1ef398d6bf3cb44c128a958377238249be5696c7eb27efe056b90f' $exe = Join-Path $env:LOCALAPPDATA 'MA AI X\ma-ai.exe' function File-Sha256([string]$Path) { $stream = [IO.File]::OpenRead($Path) $sha = [Security.Cryptography.SHA256]::Create() try { return ([BitConverter]::ToString($sha.ComputeHash($stream))).Replace('-','').ToLowerInvariant() } finally { $sha.Dispose(); $stream.Dispose() } } function Compatible-Runtime { if (!(Test-Path -LiteralPath $exe -PathType Leaf)) { return $false } $version = [Diagnostics.FileVersionInfo]::GetVersionInfo($exe).ProductVersion return $version -eq $expectedVersion.ToString() -and (File-Sha256 $exe) -eq $runtimeHash } function Activate-Background { # The single-instance runtime handles repeated background requests without UI. Start-Process -FilePath $exe -ArgumentList '--background' -WindowStyle Hidden | Out-Null } if (Compatible-Runtime) { if (!$CheckOnly) { Activate-Background } [ordered]@{state='REUSE';version=$expectedVersion.ToString();launchDesktopUi=$false;path=$exe} | ConvertTo-Json -Compress exit 0 } $decision = if (Test-Path -LiteralPath $exe) { 'UPDATE_WITH_CONSENT' } else { 'INSTALL_WITH_CONSENT' } if ($CheckOnly -or !$Consent) { [ordered]@{state=$decision;performed=$false;consentRequired=$true;launchDesktopUi=$false} | ConvertTo-Json -Compress exit 0 } $tempInstaller = Join-Path ([IO.Path]::GetTempPath()) ('ma-ai-x-' + [guid]::NewGuid().ToString('N') + '.exe') try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $client = New-Object Net.WebClient try { $client.DownloadFile('https://commander.cineworld-studios.it/download/MA-AI-X-Setup.exe', $tempInstaller) } finally { $client.Dispose() } if ((File-Sha256 $tempInstaller) -ne $installerHash) { throw 'INSTALLER_SHA256_MISMATCH' } $running = @(Get-Process -Name 'ma-ai' -ErrorAction SilentlyContinue | Where-Object { $_.Path -eq $exe }) if ($running.Count -gt 0) { $installedHash = File-Sha256 $exe if ($installedHash -notin @($runtimeHash,'ce982bd37b8146d4af8a240337e23f6ad51b949ef122f07b2989aadb46ac7cdd')) { throw 'CLOSE_INCOMPATIBLE_RUNTIME_BEFORE_UPDATE' } Start-Process -FilePath $exe -ArgumentList '--quit-background' -WindowStyle Hidden | Out-Null foreach ($instance in $running) { if (!$instance.WaitForExit(15000)) { throw 'RUNTIME_SHUTDOWN_TIMEOUT' } } } # Wait only for NSIS: -Wait also waits for its long-lived background child. $process = Start-Process -FilePath $tempInstaller -ArgumentList '/S' -WindowStyle Hidden -PassThru if (!$process.WaitForExit(180000)) { throw 'INSTALLER_TIMEOUT' } if ($process.ExitCode -ne 0) { throw "INSTALLER_EXIT_$($process.ExitCode)" } if (!(Compatible-Runtime)) { throw 'INSTALLED_RUNTIME_VERIFICATION_FAILED' } Activate-Background [ordered]@{state=$decision;performed=$true;version=$expectedVersion.ToString();sha256Verified=$true;launchDesktopUi=$false;path=$exe} | ConvertTo-Json -Compress } finally { if (Test-Path -LiteralPath $tempInstaller) { Remove-Item -LiteralPath $tempInstaller -Force } }