Files
oc-sentinel/installer/update-ocsentinel.ps1
OfficeCom Codex c3ca95dfa5
All checks were successful
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
OfficeCom Sentinel Client / build-client-windows (push) Successful in 51s
Implement reversible Sentinel beta foundation
2026-07-30 01:05:30 +02:00

207 lines
7.2 KiB
PowerShell

param(
[string]$ManifestUrl = "",
[string]$Channel = "stable",
[string]$TempRoot = "$env:TEMP\OCSentinelUpdate",
[switch]$Force
)
$ErrorActionPreference = "Stop"
function Initialize-OCSentinelTls {
$protocols = [Net.SecurityProtocolType]::Tls12
if ([Enum]::GetNames([Net.SecurityProtocolType]) -contains "Tls13") {
$protocols = $protocols -bor [Net.SecurityProtocolType]::Tls13
}
[Net.ServicePointManager]::SecurityProtocol = $protocols
[Net.ServicePointManager]::Expect100Continue = $false
}
function Get-OCSentinelManifest {
param([Parameter(Mandatory)][string]$Uri)
$parameters = @{ Method = "Get"; Uri = $Uri; TimeoutSec = 60 }
if ((Get-Command Invoke-RestMethod).Parameters.ContainsKey("UseBasicParsing")) {
$parameters.UseBasicParsing = $true
}
for ($attempt = 1; $attempt -le 3; $attempt++) {
try {
return Invoke-RestMethod @parameters
}
catch {
if ($attempt -eq 3) {
throw "Could not retrieve the OCSentinel release manifest after 3 attempts. Verify that the device can reach gitea.officecom.cloud with TLS 1.2 or newer. Last error: $($_.Exception.Message)"
}
Start-Sleep -Seconds (3 * $attempt)
}
}
}
function Get-OCSentinelArtifact {
param(
[Parameter(Mandatory)][string]$Uri,
[Parameter(Mandatory)][string]$DestinationPath
)
$parameters = @{ Uri = $Uri; OutFile = $DestinationPath; TimeoutSec = 300 }
if ((Get-Command Invoke-WebRequest).Parameters.ContainsKey("UseBasicParsing")) {
$parameters.UseBasicParsing = $true
}
for ($attempt = 1; $attempt -le 3; $attempt++) {
try {
Remove-Item -LiteralPath $DestinationPath -Force -ErrorAction SilentlyContinue
Invoke-WebRequest @parameters
if (-not (Test-Path -LiteralPath $DestinationPath) -or (Get-Item -LiteralPath $DestinationPath).Length -eq 0) {
throw "The downloaded artifact is empty."
}
return
}
catch {
if ($attempt -eq 3) {
throw "Could not download the OCSentinel package after 3 attempts. Last error: $($_.Exception.Message)"
}
Write-Warning "Package download attempt $attempt failed. Retrying."
Start-Sleep -Seconds (5 * $attempt)
}
}
}
Initialize-OCSentinelTls
$installRoot = Join-Path ${env:ProgramFiles} "OCSentinel"
$appExe = Join-Path $installRoot "app\OCSentinelCli.exe"
$installScript = Join-Path $installRoot "scripts\install-ocsentinel.ps1"
$versionFile = Join-Path $installRoot "VERSION.txt"
function Get-InstalledVersion {
if (Test-Path $versionFile) {
return (Get-Content $versionFile -Raw).Trim()
}
if (Test-Path $appExe) {
return (Get-Item $appExe).VersionInfo.ProductVersion
}
return "0.0.0"
}
function Compare-Version {
param(
[Parameter(Mandatory)][string]$Left,
[Parameter(Mandatory)][string]$Right
)
$pattern = '^(?<version>\d+(?:\.\d+){0,3})(?:-(?<prerelease>.+))?$'
$leftMatch = [regex]::Match($Left, $pattern)
$rightMatch = [regex]::Match($Right, $pattern)
if ($leftMatch.Success -and $rightMatch.Success) {
$numericComparison = ([System.Version]$leftMatch.Groups['version'].Value).CompareTo([System.Version]$rightMatch.Groups['version'].Value)
if ($numericComparison -ne 0) {
return $numericComparison
}
$leftPrerelease = $leftMatch.Groups['prerelease'].Value
$rightPrerelease = $rightMatch.Groups['prerelease'].Value
if ([string]::IsNullOrWhiteSpace($leftPrerelease) -and -not [string]::IsNullOrWhiteSpace($rightPrerelease)) { return 1 }
if (-not [string]::IsNullOrWhiteSpace($leftPrerelease) -and [string]::IsNullOrWhiteSpace($rightPrerelease)) { return -1 }
return [string]::Compare($leftPrerelease, $rightPrerelease, $true)
}
return [string]::Compare($Left, $Right, $true)
}
function Get-Sha256Hex {
param([Parameter(Mandatory)][string]$Path)
return (Get-FileHash -Path $Path -Algorithm SHA256).Hash.ToLowerInvariant()
}
function Resolve-ManifestUrl {
param(
[Parameter(Mandatory)][string]$ManifestUrl,
[Parameter(Mandatory)][string]$Channel
)
if ($ManifestUrl -match '\.json($|\?)') {
return $ManifestUrl
}
return ($ManifestUrl.TrimEnd('/') + "/$Channel/version.json")
}
if ([string]::IsNullOrWhiteSpace($ManifestUrl)) {
throw "ManifestUrl is required."
}
$resolvedManifestUrl = Resolve-ManifestUrl -ManifestUrl $ManifestUrl -Channel $Channel
Write-Host "Checking update manifest: $resolvedManifestUrl"
$manifest = Get-OCSentinelManifest -Uri $resolvedManifestUrl
if (-not $manifest.version -or -not $manifest.artifactUrl -or -not $manifest.sha256) {
throw "Update manifest is missing required fields: version, artifactUrl, sha256."
}
$installedVersion = Get-InstalledVersion
$availableVersion = [string]$manifest.version
Write-Host "Installed version: $installedVersion"
Write-Host "Available version: $availableVersion"
if (-not $Force -and (Compare-Version -Left $installedVersion -Right $availableVersion) -ge 0) {
Write-Host "OfficeCom Sentinel is already up to date."
exit 0
}
$downloadRoot = Join-Path $TempRoot ([Guid]::NewGuid().ToString("N"))
$zipPath = Join-Path $downloadRoot "OCSentinelClient.zip"
$extractRoot = Join-Path $downloadRoot "payload"
New-Item -ItemType Directory -Force -Path $downloadRoot, $extractRoot | Out-Null
Write-Host "Downloading artifact: $($manifest.artifactUrl)"
Get-OCSentinelArtifact -Uri ([string]$manifest.artifactUrl) -DestinationPath $zipPath
$actualHash = Get-Sha256Hex -Path $zipPath
$expectedHash = ([string]$manifest.sha256).ToLowerInvariant()
if ($actualHash -ne $expectedHash) {
throw "SHA-256 mismatch for downloaded artifact. Expected $expectedHash but got $actualHash."
}
Write-Host "Artifact hash verified"
Expand-Archive -Path $zipPath -DestinationPath $extractRoot -Force
$payloadAppExe = Get-ChildItem -Path $extractRoot -Recurse -Filter "OCSentinelCli.exe" | Select-Object -First 1
if ($null -eq $payloadAppExe) {
throw "Downloaded payload did not contain OCSentinelCli.exe"
}
$signature = Get-AuthenticodeSignature -FilePath $payloadAppExe.FullName
if ($signature.Status -notin @("Valid", "NotSigned")) {
throw "Executable signature validation failed with status: $($signature.Status)"
}
if ($signature.Status -eq "NotSigned") {
Write-Warning "Downloaded executable is not code-signed yet. Hash validation succeeded, but code signing should be added before production rollout."
}
else {
Write-Host "Executable signature verified: $($signature.SignerCertificate.Subject)"
}
$payloadInstallScript = Get-ChildItem -Path $extractRoot -Recurse -Filter "install-ocsentinel.ps1" | Select-Object -First 1
if ($null -eq $payloadInstallScript) {
throw "Downloaded payload did not contain install-ocsentinel.ps1"
}
Write-Host "Installing OfficeCom Sentinel $availableVersion"
& powershell.exe -ExecutionPolicy Bypass -File $payloadInstallScript.FullName
if ($LASTEXITCODE -ne 0) {
throw "Installer exited with code $LASTEXITCODE"
}
Write-Host "Update complete: $installedVersion -> $availableVersion"