Initial OfficeCom Sentinel client and deployment assets
This commit is contained in:
131
installer/update-attacktracer-ninja.ps1
Normal file
131
installer/update-attacktracer-ninja.ps1
Normal file
@@ -0,0 +1,131 @@
|
||||
param(
|
||||
[string]$ManifestUrl = "",
|
||||
[string]$Channel = "stable",
|
||||
[string]$TempRoot = "$env:TEMP\OCSentinelUpdate",
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$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
|
||||
)
|
||||
|
||||
try {
|
||||
$leftVersion = [System.Version]$Left
|
||||
$rightVersion = [System.Version]$Right
|
||||
return $leftVersion.CompareTo($rightVersion)
|
||||
}
|
||||
catch {
|
||||
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 = Invoke-RestMethod -Method Get -Uri $resolvedManifestUrl -TimeoutSec 60
|
||||
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)"
|
||||
Invoke-WebRequest -Uri ([string]$manifest.artifactUrl) -OutFile $zipPath -TimeoutSec 300
|
||||
|
||||
$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"
|
||||
Reference in New Issue
Block a user