[CmdletBinding()] param( [string]$ManifestUrl = "https://gitea.officecom.cloud/officecom/oc-sentinel/raw/main/release/stable/version.json", [string]$WebhookUrl = "", [string]$SecretValue = "" ) $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" 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) } } } Initialize-OCSentinelTls function Get-NinjaValue { param([Parameter(Mandatory)][string]$Name) $value = [Environment]::GetEnvironmentVariable($Name, "Process") if ($null -eq $value) { return "" } return $value.Trim() } if ([string]::IsNullOrWhiteSpace($WebhookUrl)) { $WebhookUrl = Get-NinjaValue -Name "webhookurl" } if ([string]::IsNullOrWhiteSpace($SecretValue)) { $SecretValue = Get-NinjaValue -Name "secretvalue" } if ([string]::IsNullOrWhiteSpace($WebhookUrl) -or [string]::IsNullOrWhiteSpace($SecretValue)) { throw "WebhookUrl and SecretValue must be set as NinjaOne script variables." } $manifest = Get-OCSentinelManifest -Uri $ManifestUrl if ([string]::IsNullOrWhiteSpace($manifest.artifactUrl) -or [string]::IsNullOrWhiteSpace($manifest.sha256)) { throw "The release manifest is incomplete." } $downloadRoot = Join-Path $env:ProgramData ("OCSentinel\\install-" + [Guid]::NewGuid().ToString("N")) $zipPath = Join-Path $downloadRoot "OCSentinelClient.zip" $extractPath = Join-Path $downloadRoot "payload" try { New-Item -ItemType Directory -Force -Path $extractPath | Out-Null Write-Host "Downloading OCSentinel $($manifest.version)." Invoke-WebRequest -Uri $manifest.artifactUrl -OutFile $zipPath -TimeoutSec 300 $actualHash = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToLowerInvariant() if ($actualHash -ne ([string]$manifest.sha256).ToLowerInvariant()) { throw "Release package SHA-256 validation failed." } Expand-Archive -LiteralPath $zipPath -DestinationPath $extractPath -Force $installer = Get-ChildItem -Path $extractPath -Recurse -Filter "install-ocsentinel.ps1" | Select-Object -First 1 if ($null -eq $installer) { throw "The release package does not contain the installer." } & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $installer.FullName if ($LASTEXITCODE -ne 0) { throw "Installer failed with code $LASTEXITCODE" } } finally { if (Test-Path -LiteralPath $downloadRoot) { Remove-Item -LiteralPath $downloadRoot -Recurse -Force } } $installRoot = Join-Path $env:ProgramFiles "OCSentinel" $configPath = Join-Path $installRoot "config\ocsentinel-client.json" $secretScript = Join-Path $installRoot "scripts\protect-ocsentinel-secret.ps1" $monitorScript = Join-Path $installRoot "scripts\run-ocsentinel-monitor.ps1" $secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat" $config = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json $config.n8nWebhookUrl = $WebhookUrl $config.environment = "production" $ninjaContext = @( @{ EnvironmentName = "NINJA_ORGANIZATION_ID"; PropertyName = "ninjaOrganizationId" }, @{ EnvironmentName = "NINJA_ORGANIZATION_NAME"; PropertyName = "ninjaOrganizationName" }, @{ EnvironmentName = "NINJA_AGENT_MACHINE_ID"; PropertyName = "ninjaMachineId" }, @{ EnvironmentName = "NINJA_AGENT_NODE_ID"; PropertyName = "ninjaNodeId" }, @{ EnvironmentName = "NINJA_LOCATION_ID"; PropertyName = "ninjaLocationId" }, @{ EnvironmentName = "NINJA_LOCATION_NAME"; PropertyName = "ninjaLocationName" } ) foreach ($entry in $ninjaContext) { $value = [Environment]::GetEnvironmentVariable($entry.EnvironmentName, "Process") if (-not [string]::IsNullOrWhiteSpace($value)) { $config | Add-Member -NotePropertyName $entry.PropertyName -NotePropertyValue $value.Trim() -Force } } $config | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $configPath -Encoding UTF8 & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $secretScript -SecretValue $SecretValue if ($LASTEXITCODE -ne 0) { throw "Writing the protected upload secret failed with code $LASTEXITCODE" } Write-Host "Running initial signed scan and upload." & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $monitorScript -Mode status -ClientConfigPath $configPath -SecretPath $secretPath -UploadMode required exit $LASTEXITCODE