param( [switch]$Update, [switch]$Uninstall, [switch]$NoLaunch, [switch]$NoDesktopShortcut, [switch]$NoAutoStart, [switch]$Quiet, [int]$WaitForPid = 0, [switch]$Worker, [string]$StateFile = "", [switch]$IsolatedTest, [string]$InstallRootOverride = "", [string]$StateRootOverride = "" ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $ProductName = "ZionDesk" $ManifestUrl = "https://download.ziondesk.app/desktop/windows/powershell/latest.json" $InstallRoot = if ($InstallRootOverride) { [IO.Path]::GetFullPath($InstallRootOverride) } else { Join-Path $env:LOCALAPPDATA "Programs\ZionDesk" } $StateRoot = if ($StateRootOverride) { [IO.Path]::GetFullPath($StateRootOverride) } else { Join-Path $env:LOCALAPPDATA "ZionDesk" } $MetadataPath = Join-Path $InstallRoot ".ziondesk-install.json" $InstalledScriptPath = Join-Path $StateRoot "install.ps1" $ExecutablePath = Join-Path $InstallRoot "ZionDesk.exe" $StartMenuShortcut = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs\ZionDesk.lnk" $DesktopShortcut = Join-Path ([Environment]::GetFolderPath("Desktop")) "ZionDesk.lnk" $RunKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" $UninstallKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\ZionDeskPowerShell" $LegacyBootstrapperUninstallKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\ZionDeskBootstrapper" $LegacyInstallRoot = Join-Path $env:LOCALAPPDATA "Programs\CommandGPT" $LegacyUninstaller = Join-Path $LegacyInstallRoot "Uninstall CommandGPT.exe" $LegacyProfileRoot = Join-Path $env:APPDATA "@commandgpt\desktop" function Write-Status([string]$Message) { if (-not $Quiet -and $Worker) { Write-Output "[ZionDesk] $Message" } } function Fail([string]$Message) { throw "[ZionDesk] $Message" } function Format-Bytes([long]$Value) { $number = [double]$Value $units = @("B", "KB", "MB", "GB") $unit = 0 while ($number -ge 1024 -and $unit -lt ($units.Length - 1)) { $number /= 1024 $unit++ } if ($unit -ge 2) { return ("{0:0.0} {1}" -f $number, $units[$unit]) } return ("{0:0} {1}" -f $number, $units[$unit]) } function Publish-InstallerState { param( [string]$Stage, [string]$Status = "working", [double]$Progress = 0, [string]$Title = "", [string]$Detail = "", [long]$Downloaded = 0, [long]$Total = 0, [double]$Speed = 0, [string]$Version = "", [string]$ErrorMessage = "" ) if (-not $StateFile) { return } $state = [ordered]@{ stage = $Stage status = $Status progress = [Math]::Max(0, [Math]::Min(100, $Progress)) title = $Title detail = $Detail downloaded = $Downloaded total = $Total speed = $Speed version = $Version error = $ErrorMessage updatedAt = (Get-Date).ToUniversalTime().ToString("o") } $directory = Split-Path -Parent $StateFile if ($directory) { New-Item -ItemType Directory -Force -Path $directory | Out-Null } $tmp = "$StateFile.tmp" $json = $state | ConvertTo-Json -Depth 4 -Compress [IO.File]::WriteAllText($tmp, $json, (New-Object Text.UTF8Encoding($false))) Move-Item -LiteralPath $tmp -Destination $StateFile -Force } function Wait-ForProcessExit([int]$PidToWait) { if ($PidToWait -le 0) { return } Publish-InstallerState -Stage "waiting" -Progress 2 -Title "Preparando atualização" -Detail "Aguardando o ZionDesk encerrar com segurança..." try { $process = Get-Process -Id $PidToWait -ErrorAction Stop $process.WaitForExit() } catch {} } function Stop-ZionDeskProcesses { if ($IsolatedTest) { return } foreach ($name in @("ZionDesk", "CommandGPT")) { Get-Process -Name $name -ErrorAction SilentlyContinue | Where-Object { $_.Id -ne $PID } | Stop-Process -Force -ErrorAction SilentlyContinue } } function Read-InstalledVersion { if (Test-Path -LiteralPath $MetadataPath) { try { $metadata = Get-Content -LiteralPath $MetadataPath -Raw | ConvertFrom-Json if ($metadata.version) { return [string]$metadata.version } } catch {} } if (Test-Path -LiteralPath $ExecutablePath) { try { $version = [Diagnostics.FileVersionInfo]::GetVersionInfo($ExecutablePath).ProductVersion if ($version) { return [string]$version } } catch {} } return $null } function Compare-Version([string]$Left, [string]$Right) { try { return ([version]$Left).CompareTo([version]$Right) } catch { return [string]::CompareOrdinal($Left, $Right) } } function Get-ReleaseManifest { Publish-InstallerState -Stage "checking" -Progress 3 -Title "Preparando o ZionDesk" -Detail "Consultando a versão mais recente..." $uri = $ManifestUrl + "?ts=" + [DateTime]::UtcNow.Ticks $manifest = Invoke-RestMethod -Uri $uri -Method Get -Headers @{ "Cache-Control" = "no-cache" } if ($manifest.product -ne "ZionDesk Desktop") { Fail "Manifesto de release inválido." } if ($manifest.channel -ne "powershell") { Fail "Canal de release inválido." } if ([string]$manifest.platform -ne "windows" -or [string]$manifest.arch -ne "x64") { Fail "Release incompatível com este Windows." } if ([string]$manifest.version -notmatch '^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$') { Fail "Versão do manifesto inválida." } if ([string]$manifest.sha256 -notmatch '^[A-Fa-f0-9]{64}$') { Fail "SHA-256 do manifesto inválido." } if ([long]$manifest.bytes -le 0) { Fail "Tamanho do pacote inválido." } $download = [Uri]([string]$manifest.versionedUrl) if ($download.Scheme -ne "https" -or $download.Host -ne "download.ziondesk.app") { Fail "Origem do pacote não é confiável." } if (-not $download.AbsolutePath.StartsWith("/desktop/windows/v")) { Fail "Caminho do pacote não é permitido." } if (-not $download.AbsolutePath.EndsWith(".zip")) { Fail "O pacote publicado não é ZIP." } return $manifest } function Invoke-DownloadWithProgress { param( [string]$Url, [string]$Destination, [long]$ExpectedBytes, [string]$Version ) $attempt = 0 while ($attempt -lt 4) { $attempt++ $existing = if (Test-Path -LiteralPath $Destination) { (Get-Item -LiteralPath $Destination).Length } else { 0 } if ($existing -gt $ExpectedBytes) { Remove-Item -LiteralPath $Destination -Force $existing = 0 } if ($existing -eq $ExpectedBytes) { Publish-InstallerState -Stage "downloading" -Progress 76 -Title "Download concluído" -Detail "Preparando a verificação..." -Downloaded $existing -Total $ExpectedBytes -Version $Version return } $request = [Net.HttpWebRequest]::Create($Url) $request.Method = "GET" $request.UserAgent = "ZionDesk-Installer/1.0" $request.Timeout = 30000 $request.ReadWriteTimeout = 30000 $request.KeepAlive = $true if ($existing -gt 0) { $request.AddRange($existing) } $response = $null $input = $null $output = $null try { $response = [Net.HttpWebResponse]$request.GetResponse() $append = $existing -gt 0 -and $response.StatusCode -eq [Net.HttpStatusCode]::PartialContent if (-not $append) { $existing = 0 } $mode = if ($append) { [IO.FileMode]::Append } else { [IO.FileMode]::Create } $input = $response.GetResponseStream() $output = New-Object IO.FileStream($Destination, $mode, [IO.FileAccess]::Write, [IO.FileShare]::Read, 262144, $true) $buffer = New-Object byte[] 262144 $downloaded = [long]$existing $watch = [Diagnostics.Stopwatch]::StartNew() $lastBytes = [long]$downloaded $lastMs = [long]0 while ($true) { $read = $input.Read($buffer, 0, $buffer.Length) if ($read -le 0) { break } $output.Write($buffer, 0, $read) $downloaded += $read $now = $watch.ElapsedMilliseconds if (($now - $lastMs) -ge 150 -or $downloaded -ge $ExpectedBytes) { $seconds = [Math]::Max(0.001, ($now - $lastMs) / 1000.0) $speed = ($downloaded - $lastBytes) / $seconds $ratio = if ($ExpectedBytes -gt 0) { [Math]::Min(1, $downloaded / [double]$ExpectedBytes) } else { 0 } $progress = 6 + ($ratio * 70) Publish-InstallerState -Stage "downloading" -Progress $progress -Title "Baixando o ZionDesk" -Detail "O aplicativo está sendo preparado em segundo plano." -Downloaded $downloaded -Total $ExpectedBytes -Speed $speed -Version $Version $lastBytes = $downloaded $lastMs = $now } } $output.Flush() $finalSize = (Get-Item -LiteralPath $Destination).Length if ($finalSize -ne $ExpectedBytes) { throw "Download incompleto. Esperado $ExpectedBytes bytes, recebido $finalSize." } Publish-InstallerState -Stage "downloading" -Progress 76 -Title "Download concluído" -Detail "Preparando a verificação..." -Downloaded $finalSize -Total $ExpectedBytes -Version $Version return } catch { if ($attempt -ge 4) { throw } Publish-InstallerState -Stage "downloading" -Progress 6 -Title "Reconectando..." -Detail "A conexão foi interrompida. Tentando novamente em 2 segundos." -Downloaded $existing -Total $ExpectedBytes -Version $Version Start-Sleep -Seconds 2 } finally { if ($output) { $output.Dispose() } if ($input) { $input.Dispose() } if ($response) { $response.Dispose() } } } } function Test-ZipEntries([string]$ArchivePath) { Add-Type -AssemblyName System.IO.Compression.FileSystem $archive = [IO.Compression.ZipFile]::OpenRead($ArchivePath) try { foreach ($entry in $archive.Entries) { $name = $entry.FullName.Replace('/', '\') if ([IO.Path]::IsPathRooted($name) -or $name.Contains(':') -or $name -match '(^|\\)\.\.(\\|$)') { Fail "Pacote ZIP contém caminho inseguro." } } } finally { $archive.Dispose() } } function New-Shortcut([string]$Path, [string]$Target) { $shell = New-Object -ComObject WScript.Shell $shortcut = $null try { $shortcut = $shell.CreateShortcut($Path) $shortcut.TargetPath = $Target $shortcut.WorkingDirectory = Split-Path -Parent $Target $shortcut.IconLocation = "$Target,0" $shortcut.Save() } finally { if ($shortcut) { [Runtime.InteropServices.Marshal]::FinalReleaseComObject($shortcut) | Out-Null } if ($shell) { [Runtime.InteropServices.Marshal]::FinalReleaseComObject($shell) | Out-Null } } } function Register-Application([string]$Version) { if ($IsolatedTest) { return } if (-not (Test-Path -LiteralPath $ExecutablePath)) { Fail "Executável instalado não encontrado." } $startMenuDir = Split-Path -Parent $StartMenuShortcut New-Item -ItemType Directory -Force -Path $startMenuDir | Out-Null New-Shortcut -Path $StartMenuShortcut -Target $ExecutablePath if (-not $NoDesktopShortcut) { New-Shortcut -Path $DesktopShortcut -Target $ExecutablePath } New-Item -Path $RunKey -Force | Out-Null if ($NoAutoStart) { Remove-ItemProperty -Path $RunKey -Name "ZionDesk" -ErrorAction SilentlyContinue } else { Set-ItemProperty -Path $RunKey -Name "ZionDesk" -Value ('"' + $ExecutablePath + '"') } foreach ($legacyName in @("CommandGPT", "CommandGPT Desktop")) { Remove-ItemProperty -Path $RunKey -Name $legacyName -ErrorAction SilentlyContinue } foreach ($legacyShortcut in @( (Join-Path ([Environment]::GetFolderPath("Desktop")) "CommandGPT.lnk"), (Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs\CommandGPT.lnk"), (Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs\CommandGPT Desktop.lnk") )) { Remove-Item -LiteralPath $legacyShortcut -Force -ErrorAction SilentlyContinue } Remove-Item -LiteralPath $LegacyBootstrapperUninstallKey -Recurse -Force -ErrorAction SilentlyContinue New-Item -Path $UninstallKey -Force | Out-Null Set-ItemProperty -Path $UninstallKey -Name "DisplayName" -Value "ZionDesk" Set-ItemProperty -Path $UninstallKey -Name "DisplayVersion" -Value $Version Set-ItemProperty -Path $UninstallKey -Name "Publisher" -Value "ZionDesk" Set-ItemProperty -Path $UninstallKey -Name "InstallLocation" -Value $InstallRoot Set-ItemProperty -Path $UninstallKey -Name "DisplayIcon" -Value $ExecutablePath Set-ItemProperty -Path $UninstallKey -Name "NoModify" -Type DWord -Value 1 Set-ItemProperty -Path $UninstallKey -Name "NoRepair" -Type DWord -Value 1 $uninstallCommand = 'powershell.exe -NoProfile -ExecutionPolicy Bypass -File "' + $InstalledScriptPath + '" -Uninstall' Set-ItemProperty -Path $UninstallKey -Name "UninstallString" -Value $uninstallCommand Set-ItemProperty -Path $UninstallKey -Name "QuietUninstallString" -Value ($uninstallCommand + " -Quiet") } function Start-ZionDesk { if ($NoLaunch -or $IsolatedTest) { return } if (Test-Path -LiteralPath $ExecutablePath) { try { Start-Process -FilePath $ExecutablePath | Out-Null } catch {} } } function Remove-LegacyCommandGPTInstallation([string]$BackupRoot) { if ($IsolatedTest) { return } if (-not (Test-Path -LiteralPath $LegacyUninstaller)) { return } $profileBackup = Join-Path $BackupRoot "legacy-commandgpt-profile" $hadLegacyProfile = Test-Path -LiteralPath $LegacyProfileRoot if ($hadLegacyProfile) { Copy-Item -LiteralPath $LegacyProfileRoot -Destination $profileBackup -Recurse -Force } try { Start-Process -FilePath $LegacyUninstaller -ArgumentList "/S" -PassThru -Wait | Out-Null } catch {} if ($hadLegacyProfile -and -not (Test-Path -LiteralPath $LegacyProfileRoot) -and (Test-Path -LiteralPath $profileBackup)) { Copy-Item -LiteralPath $profileBackup -Destination $LegacyProfileRoot -Recurse -Force } } function Invoke-UninstallWorker { Publish-InstallerState -Stage "uninstalling" -Progress 20 -Title "Removendo o ZionDesk" -Detail "Encerrando o aplicativo e limpando os arquivos instalados..." if ($IsolatedTest) { Remove-Item -LiteralPath $InstallRoot -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $StateRoot -Recurse -Force -ErrorAction SilentlyContinue Publish-InstallerState -Stage "complete" -Status "success" -Progress 100 -Title "ZionDesk removido" -Detail "Teste isolado concluído." return } Stop-ZionDeskProcesses Remove-ItemProperty -Path $RunKey -Name "ZionDesk" -ErrorAction SilentlyContinue Remove-Item -LiteralPath $StartMenuShortcut -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $DesktopShortcut -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $UninstallKey -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $LegacyBootstrapperUninstallKey -Recurse -Force -ErrorAction SilentlyContinue Publish-InstallerState -Stage "uninstalling" -Progress 75 -Title "Finalizando" -Detail "Removendo arquivos locais..." if ((Test-Path -LiteralPath $InstallRoot) -or (Test-Path -LiteralPath $StateRoot)) { $cleanup = Join-Path $env:TEMP ("ziondesk-cleanup-" + [Guid]::NewGuid().ToString("N") + ".cmd") @" @echo off ping 127.0.0.1 -n 4 >nul rmdir /s /q "$InstallRoot" rmdir /s /q "$StateRoot" del /q "%~f0" "@ | Set-Content -LiteralPath $cleanup -Encoding Ascii Start-Process -FilePath "cmd.exe" -ArgumentList @("/d", "/c", ('"' + $cleanup + '"')) -WindowStyle Hidden | Out-Null } Publish-InstallerState -Stage "complete" -Status "success" -Progress 100 -Title "ZionDesk removido" -Detail "O aplicativo foi removido. Seus dados de pareamento foram preservados." } function Invoke-InstallWorker { if (-not [Environment]::Is64BitOperatingSystem) { Fail "O ZionDesk requer Windows x64." } Wait-ForProcessExit -PidToWait $WaitForPid if ($Uninstall) { Invoke-UninstallWorker return } $manifest = $null $currentVersion = Read-InstalledVersion try { $manifest = Get-ReleaseManifest } catch { if ($Update -and (Test-Path -LiteralPath $ExecutablePath)) { Start-ZionDesk } throw } $latestVersion = [string]$manifest.version Publish-InstallerState -Stage "checking" -Progress 5 -Title "ZionDesk $latestVersion" -Detail "Versão encontrada. Preparando o download..." -Version $latestVersion if ($Update -and $currentVersion -and (Compare-Version $currentVersion $latestVersion) -ge 0) { Start-ZionDesk Publish-InstallerState -Stage "complete" -Status "success" -Progress 100 -Title "ZionDesk já está atualizado" -Detail "Você já está usando a versão mais recente ($currentVersion)." -Version $currentVersion return } $workRoot = Join-Path $StateRoot ("InstallerCache\" + [Guid]::NewGuid().ToString("N")) $archivePath = Join-Path $workRoot "ziondesk.zip" $newRoot = "$InstallRoot.new-" + [Guid]::NewGuid().ToString("N") $backupRoot = "$InstallRoot.old-" + [Guid]::NewGuid().ToString("N") $selfCopy = Join-Path $workRoot "ziondesk-install.ps1" $swappedInstall = $false New-Item -ItemType Directory -Force -Path $workRoot | Out-Null Copy-Item -LiteralPath $PSCommandPath -Destination $selfCopy -Force try { Invoke-DownloadWithProgress -Url ([string]$manifest.versionedUrl) -Destination $archivePath -ExpectedBytes ([long]$manifest.bytes) -Version $latestVersion Publish-InstallerState -Stage "verifying" -Progress 80 -Title "Verificando integridade" -Detail "Conferindo o SHA-256 antes de instalar qualquer arquivo..." -Version $latestVersion $actualHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash.ToLowerInvariant() $expectedHash = ([string]$manifest.sha256).ToLowerInvariant() if ($actualHash -ne $expectedHash) { Fail "Falha de integridade: SHA-256 do pacote não confere." } Test-ZipEntries -ArchivePath $archivePath Publish-InstallerState -Stage "extracting" -Progress 86 -Title "Preparando arquivos" -Detail "Extraindo o ZionDesk com segurança..." -Version $latestVersion Remove-Item -LiteralPath $newRoot -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Force -Path $newRoot | Out-Null Expand-Archive -LiteralPath $archivePath -DestinationPath $newRoot -Force $newExe = Join-Path $newRoot "ZionDesk.exe" $newAsar = Join-Path $newRoot "resources\app.asar" $channelMarker = Join-Path $newRoot "resources\ziondesk-install-channel.json" if (-not (Test-Path -LiteralPath $newExe) -or -not (Test-Path -LiteralPath $newAsar)) { Fail "Pacote incompleto: arquivos essenciais não encontrados." } if (-not (Test-Path -LiteralPath $channelMarker)) { Fail "Pacote não pertence ao canal oficial do ZionDesk." } Publish-InstallerState -Stage "installing" -Progress 92 -Title "Instalando o ZionDesk" -Detail "Aplicando a nova versão e preservando seu pareamento..." -Version $latestVersion Stop-ZionDeskProcesses Start-Sleep -Milliseconds 500 if (Test-Path -LiteralPath $InstallRoot) { Move-Item -LiteralPath $InstallRoot -Destination $backupRoot } try { Move-Item -LiteralPath $newRoot -Destination $InstallRoot $swappedInstall = $true } catch { if ((Test-Path -LiteralPath $backupRoot) -and -not (Test-Path -LiteralPath $InstallRoot)) { Move-Item -LiteralPath $backupRoot -Destination $InstallRoot } throw } New-Item -ItemType Directory -Force -Path $StateRoot | Out-Null Copy-Item -LiteralPath $selfCopy -Destination $InstalledScriptPath -Force $metadata = [ordered]@{ product = "ZionDesk Desktop" channel = "powershell" installer = "wpf" version = $latestVersion installedAt = (Get-Date).ToUniversalTime().ToString("o") sha256 = $expectedHash source = [string]$manifest.versionedUrl } $metadata | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $MetadataPath -Encoding UTF8 Publish-InstallerState -Stage "finalizing" -Progress 97 -Title "Finalizando" -Detail "Criando atalhos e preparando a inicialização..." -Version $latestVersion Register-Application -Version $latestVersion Remove-LegacyCommandGPTInstallation -BackupRoot $workRoot if (-not $IsolatedTest) { Remove-Item -LiteralPath (Join-Path $StateRoot "Installer\ZionDeskInstaller.exe") -Force -ErrorAction SilentlyContinue try { $installerDir = Join-Path $StateRoot "Installer" if ((Test-Path -LiteralPath $installerDir) -and -not (Get-ChildItem -LiteralPath $installerDir -Force -ErrorAction SilentlyContinue)) { Remove-Item -LiteralPath $installerDir -Force -ErrorAction SilentlyContinue } } catch {} } Remove-Item -LiteralPath $backupRoot -Recurse -Force -ErrorAction SilentlyContinue Start-ZionDesk Publish-InstallerState -Stage "complete" -Status "success" -Progress 100 -Title "Tudo pronto" -Detail "ZionDesk $latestVersion foi instalado. Seu pareamento e suas configurações foram preservados." -Version $latestVersion } catch { $installError = $_ Publish-InstallerState -Stage "rollback" -Progress 95 -Title "Restaurando versão anterior" -Detail "A instalação não foi concluída. Restaurando os arquivos anteriores..." -Version $latestVersion if ($swappedInstall) { if (Test-Path -LiteralPath $backupRoot) { Remove-Item -LiteralPath $InstallRoot -Recurse -Force -ErrorAction SilentlyContinue Move-Item -LiteralPath $backupRoot -Destination $InstallRoot -ErrorAction SilentlyContinue } elseif (Test-Path -LiteralPath $InstallRoot) { Remove-Item -LiteralPath $InstallRoot -Recurse -Force -ErrorAction SilentlyContinue } } elseif (-not (Test-Path -LiteralPath $InstallRoot) -and (Test-Path -LiteralPath $backupRoot)) { Move-Item -LiteralPath $backupRoot -Destination $InstallRoot -ErrorAction SilentlyContinue } if ($Update -and (Test-Path -LiteralPath $ExecutablePath)) { Start-ZionDesk } throw $installError } finally { Remove-Item -LiteralPath $newRoot -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $workRoot -Recurse -Force -ErrorAction SilentlyContinue } } function Invoke-WorkerEntry { try { Invoke-InstallWorker exit 0 } catch { $message = if ($_.Exception.Message) { $_.Exception.Message } else { [string]$_ } Publish-InstallerState -Stage "error" -Status "error" -Progress 0 -Title "Não foi possível concluir" -Detail $message -ErrorMessage $message exit 1 } } function New-WorkerArgumentList([string]$WorkerStatePath) { $args = New-Object Collections.Generic.List[string] foreach ($value in @("-NoProfile", "-WindowStyle", "Hidden", "-ExecutionPolicy", "Bypass", "-File", ('"' + $PSCommandPath + '"'), "-Worker", "-StateFile", ('"' + $WorkerStatePath + '"'))) { $args.Add($value) } if ($Update) { $args.Add("-Update") } if ($Uninstall) { $args.Add("-Uninstall") } if ($NoLaunch) { $args.Add("-NoLaunch") } if ($NoDesktopShortcut) { $args.Add("-NoDesktopShortcut") } if ($NoAutoStart) { $args.Add("-NoAutoStart") } if ($IsolatedTest) { $args.Add("-IsolatedTest") } if ($WaitForPid -gt 0) { $args.Add("-WaitForPid") $args.Add([string]$WaitForPid) } if ($InstallRootOverride) { $args.Add("-InstallRootOverride") $args.Add('"' + $InstallRootOverride + '"') } if ($StateRootOverride) { $args.Add("-StateRootOverride") $args.Add('"' + $StateRootOverride + '"') } return $args.ToArray() } function Show-InstallerWindow { Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName PresentationCore Add-Type -AssemblyName WindowsBase [xml]$xaml = @"