# Deploy-GoogleInstallerX86.ps1 - Generated by AppUpdater Builder # Runs as SYSTEM via Intune. Creates all folders, scripts, task, and shortcut. # No UAC needed by end users - task DACL grants standard users execute rights. $AppDir = 'C:\ProgramData\AppUpdater\GoogleInstallerX86' $LogDir = 'C:\ProgramData\AppUpdater\GoogleInstallerX86\logs' $TaskName = 'AppUpdater_GoogleInstallerX86' $TaskPs1 = "$AppDir\GoogleInstallerX86-in-task.ps1" $MainSc = "$AppDir\Update-GoogleInstallerX86.ps1" $ShowLog = "$AppDir\Show-GoogleInstallerX86Log.ps1" $BatchPath = "$LogDir\Trigger-GoogleInstallerX86.bat" # SECTION 1 - Folders + permissions (Admins+SYSTEM=Full, Users=ReadExecute) Write-Host "Creating folders..." foreach ($dir in @($AppDir,$LogDir)) { if(-not(Test-Path $dir)){New-Item $dir -ItemType Directory -Force|Out-Null} } $aclFailed=$false icacls $AppDir /inheritance:r 2>&1|Out-Null;if($LASTEXITCODE -ne 0){$aclFailed=$true} icacls $AppDir /grant "BUILTIN\Administrators:(OI)(CI)F" 2>&1|Out-Null;if($LASTEXITCODE -ne 0){$aclFailed=$true} icacls $AppDir /grant "NT AUTHORITY\SYSTEM:(OI)(CI)F" 2>&1|Out-Null;if($LASTEXITCODE -ne 0){$aclFailed=$true} icacls $AppDir /grant "BUILTIN\Users:(OI)(CI)RX" 2>&1|Out-Null;if($LASTEXITCODE -ne 0){$aclFailed=$true} if($aclFailed){Write-Host " WARNING: One or more icacls commands failed"}else{Write-Host " OK Admins+SYSTEM:Full, Users:ReadExecute"} # SECTION 2 - Write Update-GoogleInstallerX86.ps1 Write-Host "Writing Update-GoogleInstallerX86.ps1..." $updateContent = @' # Update-GoogleInstallerX86.ps1 - Auto-update script for Google Installer (x86) # Generated by AppUpdater Builder. Do not edit manually. $AppID = 'GoogleInstallerX86' $DisplayName = 'Google Installer (x86)' $WorkerURL = 'https://updates.lyvvy.xyz' $RegistryName = 'Google Installer (x86)' $SilentArgs = '/silent' $AppDir = 'C:\ProgramData\AppUpdater\GoogleInstallerX86' $LogDir = 'C:\ProgramData\AppUpdater\GoogleInstallerX86\logs' $DetailLog = "$LogDir\update-detail.log" $TempInstaller = "$env:TEMP\GoogleInstallerX86-installer.exe" if (-not (Test-Path $LogDir)) { New-Item $LogDir -ItemType Directory -Force | Out-Null } Start-Transcript -Path $DetailLog -Force | Out-Null Write-Host "=== RUN START ===" Write-Host "=== AppUpdater - $DisplayName ===" # Worker health check $workerOK = $false try { if ((Invoke-RestMethod -Uri "$WorkerURL/health" -TimeoutSec 10 -ErrorAction Stop).status -eq "ok") { $workerOK = $true } } catch { Write-Host " WARNING: Worker not reachable - $($_.Exception.Message)" } if (-not $workerOK) { Write-Host "=== COMPLETE WITH ERRORS: Worker unreachable ==="; Stop-Transcript|Out-Null; return } # Fetch manifest $appEntry = $null try { $manifest = (Invoke-RestMethod -Uri "$WorkerURL/appVersions.xml" -TimeoutSec 30 -ErrorAction Stop) $appEntry = $manifest.AppManifest.App | Where-Object { $_.ID -eq $AppID } | Select-Object -First 1 } catch { Write-Host " ERROR: Could not fetch manifest - $($_.Exception.Message)"; Write-Host "=== COMPLETE WITH ERRORS: Manifest fetch failed ==="; Stop-Transcript|Out-Null; return } # Check if admin has flagged this app for removal via the status page $removedEntry = $null try { $removedEntry = $manifest.AppManifest.RemovedApps.App | Where-Object { $_.id -eq $AppID } | Select-Object -First 1 } catch {} if ($removedEntry) { Write-Host " REMOVAL FLAG: $AppID has been marked for uninstall (flagged $($removedEntry.removedAt))" Write-Host " Performing silent self-removal..." Unregister-ScheduledTask -TaskName "AppUpdater_$AppID" -Confirm:$false -ErrorAction SilentlyContinue Write-Host " OK Scheduled task removed" $sc = "C:\Users\Public\Desktop\Update $DisplayName.lnk" if (Test-Path $sc) { Remove-Item $sc -Force -ErrorAction SilentlyContinue; Write-Host " OK Shortcut removed" } if (Test-Path $AppDir) { Remove-Item $AppDir -Recurse -Force -ErrorAction SilentlyContinue; Write-Host " OK AppUpdater folder removed" } Write-Host "$DisplayName has been removed from this machine." Write-Host "=== COMPLETE: SELF-REMOVED ===" try { Stop-Transcript | Out-Null } catch {} return } if (-not $appEntry) { Write-Host " ERROR: App ID '$AppID' not found in manifest"; Write-Host "=== COMPLETE WITH ERRORS: App not in manifest ==="; Stop-Transcript|Out-Null; return } $downloadURL = $appEntry.DownloadURL $fallbackURL = $appEntry.FallbackURL $xmlVersion = $appEntry.Version # Check installed version (with stale registry guards - mirrors NetDocuments v3.5) function Convert-GuidToPacked { param([string]$Guid) $g = ($Guid -replace '[{}\-]','').ToUpper() if ($g.Length -ne 32) { return $null } return (-join $g[7..0])+(-join $g[11..8])+(-join $g[15..12])+($g[17]+$g[16]+$g[19]+$g[18])+($g[21]+$g[20]+$g[23]+$g[22]+$g[25]+$g[24]+$g[27]+$g[26]+$g[29]+$g[28]+$g[31]+$g[30]) } function Get-InstalledVersion { param([string]$Name) foreach ($base in @("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*","HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*")) { $m = Get-ItemProperty $base -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -eq $Name } | Select-Object -First 1 if (-not $m) { continue } if (-not $m.UninstallString) { Write-Host " STALE: No UninstallString -> treating as not installed"; return $null } if ($m.InstallLocation -and $m.InstallLocation.Trim() -ne "" -and -not (Test-Path $m.InstallLocation)) { Write-Host " STALE: InstallLocation missing -> treating as not installed"; return $null } if ($m.UninstallString -imatch 'msiexec' -and $m.UninstallString -match '\{([A-Fa-f0-9-]{36})\}') { $packed = Convert-GuidToPacked $matches[1] if ($packed -and -not (Test-Path "HKLM:\SOFTWARE\Classes\Installer\Products\$packed")) { Write-Host " STALE: MSI GUID not in Installer DB -> treating as not installed"; return $null } } if ($m.DisplayVersion) { return $m.DisplayVersion } if ($m.ProductVersion) { return $m.ProductVersion } } return $null } $installed = Get-InstalledVersion $RegistryName Write-Host " Installed : $(if($installed){$installed}else{'Not installed'})" Write-Host " Manifest : $(if($xmlVersion){$xmlVersion}else{'Not specified - will check file version'})" # Fast path: version already matches if ($xmlVersion -and $installed) { try { if ([version]$installed -ge [version]$xmlVersion) { Write-Host "$DisplayName : UP TO DATE (installed=$installed)" Write-Host "Nothing to install - all products are up to date." Stop-Transcript | Out-Null return } } catch { Write-Host " WARNING: Version parse failed - will download to verify" } } # Download Write-Host "$DisplayName : $(if($installed){'OUTDATED'}else{'NOT INSTALLED'}) - downloading..." $downloaded = $false foreach ($url in @($downloadURL,$fallbackURL) | Where-Object { $_ }) { Write-Host " Trying: $url" $fileStream = $null; $respStream = $null; $resp = $null try { $req = [System.Net.HttpWebRequest]::Create($url); $req.Timeout = 120000 $resp = $req.GetResponse(); $totalLen = $resp.ContentLength $respStream = $resp.GetResponseStream() $fileStream = [System.IO.File]::Create($TempInstaller) $buf = New-Object byte[] 65536; $bytesRead = 0; $lastPct = -1 while (($chunk = $respStream.Read($buf,0,$buf.Length)) -gt 0) { $fileStream.Write($buf,0,$chunk); $bytesRead += $chunk if ($totalLen -gt 0) { $pct = [int](($bytesRead/$totalLen)*100) if ($pct -ne $lastPct -and $pct % 5 -eq 0) { Write-Host "PROGRESS:${AppID}:${pct}:${bytesRead}:${totalLen}"; $lastPct = $pct } } } Write-Host "PROGRESS:${AppID}:100:${totalLen}:${totalLen}"; $downloaded = $true; break } catch { Write-Host " Failed from $url: $($_.Exception.Message)" } finally { if ($fileStream) { $fileStream.Close(); $fileStream = $null } if ($respStream) { $respStream.Close(); $respStream = $null } if ($resp) { $resp.Close(); $resp = $null } } } if (-not $downloaded) { Write-Host " ERROR: All download attempts failed"; Write-Host "=== COMPLETE WITH ERRORS: Download failed ==="; Stop-Transcript|Out-Null; return } # File version check if no XML version if (-not $xmlVersion) { $fv = (Get-Item $TempInstaller -ErrorAction SilentlyContinue).VersionInfo.FileVersion if ($fv -and $installed) { try { if ([version]($fv -replace ',','.') -le [version]$installed) { Write-Host "$DisplayName : UP TO DATE (file version check)" Remove-Item $TempInstaller -Force -ErrorAction SilentlyContinue Write-Host "Nothing to install - all products are up to date." Stop-Transcript | Out-Null return } } catch {} } } # Kill processes before install Start-Sleep -Seconds 2 # Install with live heartbeat (mirrors NetDocuments v3.4 pattern) Write-Host "Installing $DisplayName..." $elapsed = 0; $timeoutSec = 7200; $installError = $false try { $proc = Start-Process -FilePath $TempInstaller -ArgumentList '/silent' -PassThru -ErrorAction Stop while (-not $proc.HasExited -and $elapsed -lt $timeoutSec) { Start-Sleep -Seconds 2; $elapsed += 2 Write-Host "INSTALLING:${AppID}:${elapsed}" } if (-not $proc.HasExited) { $proc | Stop-Process -Force -ErrorAction SilentlyContinue Write-Host "INSTALLING:${AppID}:DONE:TIMEOUT" Write-Host " ERROR: Installer timed out" $installError = $true } else { $ec = $proc.ExitCode Write-Host "INSTALLING:${AppID}:DONE:$ec" if ($ec -ne 0 -and $ec -ne 3010) { Write-Host " ERROR: Installer exited with code $ec" $installError = $true } elseif ($ec -eq 3010) { Write-Host " $DisplayName installed successfully (reboot may be required)" } } } catch { Write-Host " ERROR: Could not launch installer - $($_.Exception.Message)" $installError = $true } finally { Remove-Item $TempInstaller -Force -ErrorAction SilentlyContinue } if ($installError) { Write-Host "=== COMPLETE WITH ERRORS: $DisplayName install failed ===" } else { Write-Host "$DisplayName install complete" Write-Host "=== COMPLETE ===" } Stop-Transcript | Out-Null # Use return not exit - exit kills the host before the task wrapper writes [SUCCESS] return '@ $updateContent | Out-File $MainSc -Encoding UTF8 -Force Write-Host " OK Update-GoogleInstallerX86.ps1 written" # SECTION 3a - Write GoogleInstallerX86-in-task.ps1 Write-Host "Writing GoogleInstallerX86-in-task.ps1..." $inTaskContent = @' # GoogleInstallerX86-in-task.ps1 - Scheduled task wrapper for Google Installer (x86) # Logs START/SUCCESS/ERROR. Calls Update-GoogleInstallerX86.ps1 as SYSTEM. $logFile = "C:\ProgramData\AppUpdater\GoogleInstallerX86\logs\update-task-run.log" $detailLog = "C:\ProgramData\AppUpdater\GoogleInstallerX86\logs\update-detail.log" if (-not (Test-Path "C:\ProgramData\AppUpdater\GoogleInstallerX86\logs")) { New-Item "C:\ProgramData\AppUpdater\GoogleInstallerX86\logs" -ItemType Directory -Force | Out-Null } # Trim task-run log to 200 lines if (Test-Path $logFile) { try { $tail = Get-Content $logFile -Tail 200 -ErrorAction SilentlyContinue; $tail | Set-Content $logFile -Force } catch {} } # Write sentinel immediately so Show-Log knows the task started "=== TASK STARTING ===" | Out-File $detailLog -Force "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [START] Task triggered" | Out-File $logFile -Append try { & "C:\ProgramData\AppUpdater\GoogleInstallerX86\Update-GoogleInstallerX86.ps1" "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [SUCCESS] Update finished" | Out-File $logFile -Append } catch { "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [ERROR] $($_.Exception.Message)" | Out-File $logFile -Append } '@ $inTaskContent | Out-File $TaskPs1 -Encoding UTF8 -Force Write-Host " OK GoogleInstallerX86-in-task.ps1 written" # SECTION 3b - Write Show-GoogleInstallerX86Log.ps1 Write-Host "Writing Show-GoogleInstallerX86Log.ps1..." $showLogContent = @' # Show-GoogleInstallerX86Log.ps1 - Live update status window for Google Installer (x86) $host.UI.RawUI.WindowTitle = "Google Installer (x86) Update - Live Output" try { $ui=$host.UI.RawUI; $b=$ui.BufferSize;$b.Width=72;$ui.BufferSize=$b; $w=$ui.WindowSize;$w.Width=72;$w.Height=32;$ui.WindowSize=$w } catch {} function Show-Header { Clear-Host Write-Host "" Write-Host " +======================================================================+" -ForegroundColor DarkCyan Write-Host " | |" -ForegroundColor DarkCyan Write-Host " | Google Installer (x86) Update - Live Output" -ForegroundColor Cyan Write-Host " | Balls |" -ForegroundColor DarkGray Write-Host " | |" -ForegroundColor DarkCyan Write-Host " +======================================================================+" -ForegroundColor DarkCyan Write-Host "" } function Write-StatusLine { param([string]$Tag,[string]$Msg,[ConsoleColor]$TC=[ConsoleColor]::White,[ConsoleColor]$MC=[ConsoleColor]::Gray); Write-Host " " -NoNewline; Write-Host " $Tag " -ForegroundColor $TC -NoNewline; Write-Host " $Msg" -ForegroundColor $MC } function Write-Rule { Write-Host " +----------------------------------------------------------------------+" -ForegroundColor DarkGray } function Draw-ProgressBar { param([string]$AppId,[int]$Pct,[long]$DlBytes,[long]$TtBytes,[hashtable]$Rows) $bw=26;$filled=[int]($Pct/100.0*$bw);$bar='['+(('#'*$filled)+('-'*($bw-$filled)))+']' $mbD='{0:N1}' -f ($DlBytes/1MB);$mbT='{0:N1}' -f ($TtBytes/1MB) $colour=if($Pct -ge 100){[ConsoleColor]::Green}else{[ConsoleColor]::Cyan} $line=" DL $AppId $bar $Pct% ($mbD/$mbT MB)" if (-not $Rows.ContainsKey($AppId)) { $Rows[$AppId]=$host.UI.RawUI.CursorPosition.Y; Write-Host $line.PadRight(71) -ForegroundColor $colour } else { $cur=$host.UI.RawUI.CursorPosition; $host.UI.RawUI.CursorPosition=New-Object System.Management.Automation.Host.Coordinates 0,$Rows[$AppId]; Write-Host $line.PadRight(71) -ForegroundColor $colour; $host.UI.RawUI.CursorPosition=$cur } } function Draw-InstallLine { param([string]$AppId,[string]$Status,[int]$ElapsedSec,[hashtable]$Rows) if ($Status -eq 'DONE') { $line=" INST $AppId Install finished"; $colour=[ConsoleColor]::Green } else { $timer='{0:D2}:{1:D2}' -f [int]($ElapsedSec/60),($ElapsedSec%60); $line=" INST $AppId Installing... [$timer]"; $colour=[ConsoleColor]::Cyan } if (-not $Rows.ContainsKey($AppId)) { $Rows[$AppId]=$host.UI.RawUI.CursorPosition.Y; Write-Host $line.PadRight(71) -ForegroundColor $colour } else { $cur=$host.UI.RawUI.CursorPosition; $host.UI.RawUI.CursorPosition=New-Object System.Management.Automation.Host.Coordinates 0,$Rows[$AppId]; Write-Host $line.PadRight(71) -ForegroundColor $colour; $host.UI.RawUI.CursorPosition=$cur } } $DetailLog = "C:\ProgramData\AppUpdater\GoogleInstallerX86\logs\update-detail.log" $TaskLog = "C:\ProgramData\AppUpdater\GoogleInstallerX86\logs\update-task-run.log" $noisePatterns = @('^Windows PowerShell\s*$','^Copyright \(C\) Microsoft','^Install the latest PowerShell','aka\.ms/PSWindows','^Transcript (started|stopped)','^Start time:','^End time:','^Username:','^RunAs User:','^Configuration Name:','^Machine:','^Host Application:','^Process ID:','^PSVersion','^PSEdition','^GitCommitId','^\s*OS:','^Platform:','^PSCompatibleVersions','^PSRemotingProtocolVersion','^SerializationVersion','^WSManStackVersion','^\*{6,}','^=== AppUpdater','^PROGRESS:','^INSTALLING:','^=== RUN START ===','^=== TASK STARTING','^\s*$') function Test-IsNoise { param([string]$line); foreach($p in $noisePatterns){if($line -match $p){return $true}}; return $false } Show-Header; Write-Rule; Write-Host "" Write-StatusLine "WAIT" "Connecting to update service..." Yellow DarkYellow Write-Host "" $t=0;$waitMax=90 while(-not(Test-Path $DetailLog) -and $t -lt $waitMax){ Start-Sleep 1;$t++ if($t%10 -eq 0 -and (Test-Path $TaskLog)){ $ll=Get-Content $TaskLog -Tail 1 -ErrorAction SilentlyContinue if($ll -match '\[START\]' -and $t -ge ($waitMax-30)){$waitMax=$t+30} } } if(-not(Test-Path $DetailLog)){ $taskErr=$null if(Test-Path $TaskLog){$taskErr=Get-Content $TaskLog -Tail 3 -ErrorAction SilentlyContinue|Where-Object{$_ -match '\[ERROR\]'}|Select-Object -Last 1} if($taskErr){Write-StatusLine "FAIL" "Task crashed:" Red Red;Write-StatusLine " " ($taskErr -replace '.*\[ERROR\]\s*','') DarkGray DarkGray} else{Write-StatusLine "FAIL" "Could not start the update process." Red Red;Write-StatusLine " " "Please contact Balls support." DarkGray DarkGray} Write-Host "";Write-Rule;Write-Host "";exit 1 } Show-Header;Write-Rule;Write-Host "" Write-StatusLine " OK " "Connected. Checking for updates..." Green Green Write-Host "" $seen=@{};$progressRows=@{};$installRows=@{};$done=$false;$elapsed=0;$maxWait=600 $runStarted=$false;$hadErrors=$false;$hadUpdates=$false while(-not $done -and $elapsed -lt $maxWait){ $lines=Get-Content $DetailLog -ErrorAction SilentlyContinue foreach($line in $lines){ if($line -match '^PROGRESS:'){continue};if($line -match '^INSTALLING:'){continue};if([string]::IsNullOrWhiteSpace($line)){continue} if($line -match '=== RUN START ==='){if(-not $runStarted){$runStarted=$true;$seen=@{};$progressRows=@{};$installRows=@{}};continue} if(-not $runStarted){continue};if(Test-IsNoise $line){continue} if ($line -match 'NOT INSTALLED|OUTDATED' -and -not $seen['dl']) {Write-StatusLine "DOWN" "Google Installer (x86) - Update found, downloading" Yellow Yellow;$seen['dl']=$true} elseif($line -match 'UP TO DATE' -and -not $seen['ok']) {Write-StatusLine " OK " "Google Installer (x86) - Already up to date" Green Green; $seen['ok']=$true} elseif($line -match 'install complete' -and -not $seen['done']) {$seen['done']=$true;$hadUpdates=$true} elseif($line -match 'Nothing to install|all products are up to date'-and -not $seen['nothing']){Write-StatusLine " OK " "Already up to date" Green Green;$seen['nothing']=$true;$done=$true} elseif($line -match '=== COMPLETE WITH ERRORS') {$done=$true;$hadErrors=$true} elseif($line -match '=== COMPLETE ===') {$done=$true} } $lp=@{};foreach($line in $lines){if($line -match '^PROGRESS:([^:]+):(\d+):(\d+):(\d+)'){$lp[$matches[1]]=@{Pct=[int]$matches[2];DlBytes=[long]$matches[3];TtBytes=[long]$matches[4]}}} foreach($aid in $lp.Keys){$p=$lp[$aid];Draw-ProgressBar -AppId $aid -Pct $p.Pct -DlBytes $p.DlBytes -TtBytes $p.TtBytes -Rows $progressRows} $li=@{};foreach($line in $lines){if($line -match '^INSTALLING:([^:]+):(\d+)$'){$li[$matches[1]]=@{Status='RUNNING';Elapsed=[int]$matches[2]}}elseif($line -match '^INSTALLING:([^:]+):DONE'){$li[$matches[1]]=@{Status='DONE';Elapsed=0}}} foreach($aid in $li.Keys){$i=$li[$aid];Draw-InstallLine -AppId $aid -Status $i.Status -ElapsedSec $i.Elapsed -Rows $installRows} if(-not $done -and (Test-Path $TaskLog)){$tail=Get-Content $TaskLog -Tail 1 -ErrorAction SilentlyContinue;if($tail -match '\[SUCCESS\]|\[ERROR\]'){$done=$true}} if(-not $done){Start-Sleep -Seconds 1;$elapsed++} } Write-Host "";Write-Rule;Write-Host "" $taskText=if(Test-Path $TaskLog){Get-Content $TaskLog -Raw -ErrorAction SilentlyContinue}else{""} if($elapsed -ge $maxWait){ Write-Host " +======================================================================+" -ForegroundColor DarkYellow Write-Host " | [TIMEOUT] Update is taking longer than expected (10 min). |" -ForegroundColor Yellow Write-Host " | Please contact Balls support if this continues. |" -ForegroundColor DarkYellow Write-Host " +======================================================================+" -ForegroundColor DarkYellow }elseif($hadErrors -or $taskText -match '\[ERROR\]'){ Write-Host " +======================================================================+" -ForegroundColor DarkRed Write-Host " | [ERROR] Something went wrong during the update. |" -ForegroundColor Red Write-Host " | Please contact Balls support. |" -ForegroundColor DarkRed Write-Host " +======================================================================+" -ForegroundColor DarkRed }elseif($hadUpdates){ Write-Host " +======================================================================+" -ForegroundColor DarkGreen Write-Host " | [DONE] Google Installer (x86) updated successfully! |" -ForegroundColor Green Write-Host " | You can continue as normal. |" -ForegroundColor DarkGreen Write-Host " +======================================================================+" -ForegroundColor DarkGreen }else{ Write-Host " +======================================================================+" -ForegroundColor DarkGreen Write-Host " | [DONE] Google Installer (x86) is already up to date. |" -ForegroundColor Green Write-Host " | You can continue as normal. |" -ForegroundColor DarkGreen Write-Host " +======================================================================+" -ForegroundColor DarkGreen } Write-Host "";Write-Host " Close this window when you are done." -ForegroundColor DarkGray;Write-Host "" '@ $showLogContent | Out-File $ShowLog -Encoding UTF8 -Force Write-Host " OK Show-GoogleInstallerX86Log.ps1 written" # SECTION 4 - Register scheduled task as SYSTEM Write-Host "Registering scheduled task as SYSTEM..." Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue try { $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -File `"$TaskPs1`"" $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest $settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Hours 2) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable Register-ScheduledTask -TaskName $TaskName -Action $action -Principal $principal -Settings $settings -Force | Out-Null Write-Host " OK Scheduled task registered as SYSTEM" } catch { Write-Host " ERROR: Could not register task - $($_.Exception.Message)"; exit 1 } # SECTION 4b - Grant standard users execute rights on task via COM DACL # 0x1201bb = Read+Execute+Run - required for schtasks /run on Win11 22H2+ Write-Host "Setting task DACL (Users: execute+run, no modify)..." $daclAce= "(A;;0x1201bb;;;BU)"; $taskReady=$false $sched = New-Object -ComObject "Schedule.Service" for($w=0;$w -lt 15;$w++){ try{$sched.Connect();$null=$sched.GetFolder("\").GetTask($TaskName);$taskReady=$true;break}catch{Start-Sleep 1} } if($taskReady){ try{ $sched.Connect();$task=$sched.GetFolder("\").GetTask($TaskName);$sd=$task.GetSecurityDescriptor(0xF) if($sd -notmatch [regex]::Escape($daclAce)){ $newSd=if($sd -match 'D:'){$sd+$daclAce}else{$sd+"D:"+$daclAce} $task.SetSecurityDescriptor($newSd,0) Write-Host " OK Task DACL set - users can run but not modify" }else{Write-Host " OK Task DACL already present"} }catch{Write-Host " WARNING: Could not set task DACL - $($_.Exception.Message)"} }else{Write-Host " WARNING: Task not found after 15s - DACL not set"} # SECTION 5 - Batch launcher Write-Host "Writing batch launcher..." $bat=@('@echo off','chcp 65001>nul','cls','title Google Installer (x86) Update','color 0A','echo.','echo +======================================================================+','echo ^| Google Installer (x86) Update ^|','echo ^| Balls ^|','echo +======================================================================+','echo.','echo Starting update, please wait...','echo.','schtasks /run /tn "AppUpdater_GoogleInstallerX86" >nul 2>&1','if errorlevel 1 (',' echo.',' echo +======================================================================+',' echo ^| [ERROR] Could not start the update task. ^|',' echo ^| Please contact Balls support. ^|',' echo +======================================================================+',' echo.',' pause',' exit /b 1',')','echo Task started. Opening status window...','timeout /t 2 /nobreak >nul','start "Google Installer (x86) Update - Live Output" powershell.exe -NoProfile -ExecutionPolicy Bypass -NoExit -File "C:\ProgramData\AppUpdater\GoogleInstallerX86\Show-GoogleInstallerX86Log.ps1"','exit') $bat | Out-File $BatchPath -Encoding ASCII -Force Write-Host " OK Batch launcher written" # SECTION 6 - Desktop shortcut (Public Desktop - visible to all users) Write-Host "Creating desktop shortcut..." try { $wsh=New-Object -ComObject WScript.Shell $sc=$wsh.CreateShortcut("C:\Users\Public\Desktop\Update Google Installer (x86).lnk") $sc.TargetPath=$BatchPath; $sc.WorkingDirectory=$LogDir $sc.IconLocation='shell32.dll,270'; $sc.WindowStyle=1 $sc.Description='Checks and updates Google Installer (x86) - no admin rights needed' $sc.Save() Write-Host " OK Shortcut on Public Desktop" }catch{Write-Host " WARNING: Could not create shortcut - $($_.Exception.Message)"} Write-Host "" Write-Host "=== DEPLOY COMPLETE: Google Installer (x86) ===" Write-Host " Folder : $AppDir" Write-Host " Task : $TaskName (SYSTEM, Users=execute-only)" Write-Host " Shortcut : C:\Users\Public\Desktop\Update Google Installer (x86).lnk" exit 0