param( [Alias("Token")] [Parameter(Mandatory = $true)] [string]$EnrollmentToken, [string]$ManagerUrl = "http://vulnfixer.codata.pb.gov.br", [ValidateSet("amd64", "arm64")] [string]$Architecture = "", [string]$InstallRoot = "C:\\Program Files\\VulnFixer", [string]$DataRoot = "C:\\ProgramData\\VulnFixer", [string]$TaskName = "VulnFixerAgent", [int]$PollIntervalSeconds = 900, [int]$InventoryIntervalSeconds = 7200, [string]$AllowCommands = "apt,apt-get,sh,bash,cmd,powershell,winget,brew,softwareupdate,yum,dnf,zypper,snap", [switch]$InsecureTls, [switch]$SkipWingetBootstrap ) $ErrorActionPreference = "Stop" $script:InvokeWebRequestCompat = @{} $script:InvokeRestMethodCompat = @{} if ((Get-Command Invoke-WebRequest).Parameters.ContainsKey("UseBasicParsing")) { $script:InvokeWebRequestCompat.UseBasicParsing = $true } if ((Get-Command Invoke-RestMethod).Parameters.ContainsKey("UseBasicParsing")) { $script:InvokeRestMethodCompat.UseBasicParsing = $true } function Test-IsAdministrator { $currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($currentIdentity) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function Convert-ToPowerShellLiteral { param( [Parameter(Mandatory = $true)] [string]$Value ) return "'" + $Value.Replace("'", "''") + "'" } function Convert-ToSecureManagerUrl { param( [Parameter(Mandatory = $true)] [string]$Value ) $normalized = $Value.Trim() if ([string]::IsNullOrWhiteSpace($normalized)) { throw "Informe a URL do servidor." } if ($normalized -notmatch "^[a-zA-Z][a-zA-Z0-9+.-]*://") { $normalized = "https://$normalized" } $uri = $null if (-not [Uri]::TryCreate($normalized, [System.UriKind]::Absolute, [ref]$uri)) { throw "URL do servidor invalida: $Value" } if ($uri.Scheme -ne "http" -and $uri.Scheme -ne "https") { throw "A URL do servidor deve usar http ou https." } $managerHost = $uri.Host $isLocal = $managerHost -eq "localhost" -or $managerHost -eq "127.0.0.1" -or $managerHost -eq "::1" if ($uri.Scheme -eq "http" -and -not $isLocal) { $builder = New-Object System.UriBuilder($uri) $builder.Scheme = "https" if ($builder.Port -eq 80) { $builder.Port = 443 } $uri = $builder.Uri } return $uri.AbsoluteUri.TrimEnd("/") } function Refresh-ProcessPath { $machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine") $userPath = [Environment]::GetEnvironmentVariable("Path", "User") if ([string]::IsNullOrWhiteSpace($userPath)) { $env:Path = $machinePath return } $env:Path = $machinePath + ";" + $userPath } function Test-WinGetAvailable { Refresh-ProcessPath return [bool](Get-Command winget.exe -ErrorAction SilentlyContinue) } function Test-WinGetSupported { $version = [Environment]::OSVersion.Version return ($version.Major -gt 10) -or ($version.Major -eq 10 -and $version.Build -ge 17763) } function Get-PreferredPowerShellCommand { param( [switch]$RequireCore ) $candidates = @("pwsh.exe") if (-not $RequireCore) { $candidates += "powershell.exe" } foreach ($candidate in $candidates) { $command = Get-Command $candidate -ErrorAction SilentlyContinue if ($command -and -not [string]::IsNullOrWhiteSpace($command.Source)) { return $command.Source } } return $null } function Invoke-WinGetRepair { $repairScript = @' $ErrorActionPreference = "Stop" $progressPreference = "SilentlyContinue" try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { } if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) { Install-PackageProvider -Name NuGet -Force | Out-Null } $module = Get-Module -ListAvailable -Name Microsoft.WinGet.Client | Sort-Object Version -Descending | Select-Object -First 1 if (-not $module) { try { Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop } catch { } Install-Module -Name Microsoft.WinGet.Client -Force -Repository PSGallery -Scope AllUsers -AllowClobber | Out-Null } Import-Module Microsoft.WinGet.Client -Force -ErrorAction Stop if (-not (Get-Command Repair-WinGetPackageManager -ErrorAction SilentlyContinue)) { throw "O modulo Microsoft.WinGet.Client foi instalado, mas o cmdlet Repair-WinGetPackageManager nao ficou disponivel." } Repair-WinGetPackageManager -AllUsers -ErrorAction Stop | Out-Null '@ if ($PSVersionTable.PSEdition -eq "Core") { $scriptBlock = [scriptblock]::Create($repairScript) & $scriptBlock return } $pwshPath = Get-PreferredPowerShellCommand -RequireCore if ([string]::IsNullOrWhiteSpace($pwshPath)) { throw "Repair-WinGetPackageManager requer PowerShell 7 (pwsh.exe), mas ele nao foi encontrado neste Windows." } & $pwshPath -NoLogo -NoProfile -ExecutionPolicy Bypass -Command $repairScript if ($LASTEXITCODE -ne 0) { throw "Repair-WinGetPackageManager falhou ao executar via PowerShell 7." } } function Ensure-WinGetInstalled { if ($SkipWingetBootstrap) { Write-Host "Bootstrap do WinGet ignorado por parametro." return } if (Test-WinGetAvailable) { Write-Host "WinGet ja esta disponivel neste Windows." return } if (-not (Test-WinGetSupported)) { Write-Warning "Este Windows nao atende ao requisito minimo do WinGet (Windows 10 1809 / build 17763 ou superior). O agente sera instalado sem WinGet." return } Write-Host "WinGet nao encontrado. Tentando registrar e instalar o Windows Package Manager..." try { Add-AppxPackage -RegisterByFamilyName -MainPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -ErrorAction Stop | Out-Null } catch { Write-Host "Registro rapido do App Installer nao concluiu: $($_.Exception.Message)" } if (Test-WinGetAvailable) { Write-Host "WinGet registrado com sucesso." return } try { Invoke-WinGetRepair } catch { Write-Warning "Nao foi possivel instalar o WinGet automaticamente. O agente sera instalado, mas atualizacoes via WinGet podem falhar ate o Windows Package Manager ser reparado manualmente. Detalhe: $($_.Exception.Message)" return } if (Test-WinGetAvailable) { try { & winget source reset --force | Out-Null & winget source update | Out-Null } catch { Write-Host "WinGet instalado, mas a limpeza das fontes retornou aviso: $($_.Exception.Message)" } Write-Host "WinGet instalado com sucesso." return } Write-Warning "O bootstrap do WinGet foi executado, mas o comando winget.exe ainda nao ficou disponivel nesta sessao. O agente sera instalado e voce pode reparar o WinGet depois." } function Get-VulnFixerAgentProcesses { param( [Parameter(Mandatory = $true)] [string]$AgentPath ) return @(Get-Process -Name "vulnfixer-agent" -ErrorAction SilentlyContinue | Where-Object { try { -not $_.Path -or [string]::Equals($_.Path, $AgentPath, [System.StringComparison]::OrdinalIgnoreCase) } catch { $true } }) } function Stop-VulnFixerRuntime { param( [Parameter(Mandatory = $true)] [string]$TaskName, [Parameter(Mandatory = $true)] [string]$AgentPath ) $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue if ($task) { try { Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue } catch { Write-Host "Nao foi possivel parar a tarefa '$TaskName' imediatamente: $($_.Exception.Message)" } $deadline = (Get-Date).AddSeconds(15) do { Start-Sleep -Milliseconds 500 $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue } while ($task -and $task.State -eq "Running" -and (Get-Date) -lt $deadline) } $deadline = (Get-Date).AddSeconds(15) do { $runningProcesses = Get-VulnFixerAgentProcesses -AgentPath $AgentPath if ($runningProcesses.Count -eq 0) { return } foreach ($process in $runningProcesses) { try { Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue } catch { Write-Host "Nao foi possivel encerrar o processo do agente $($process.Id): $($_.Exception.Message)" } } Start-Sleep -Milliseconds 500 } while ((Get-Date) -lt $deadline) $remainingProcesses = Get-VulnFixerAgentProcesses -AgentPath $AgentPath if ($remainingProcesses.Count -gt 0) { throw "Nao foi possivel interromper o agente em execucao. Feche o processo vulnfixer-agent.exe e tente novamente." } } function Enable-InsecureTlsIfRequested { if ($InsecureTls) { try { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } } catch { # no-op } } try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { # no-op } } function New-EnrollmentHeaders { return @{ "X-Enrollment-Token" = $EnrollmentToken } } function Invoke-EnrollmentWebRequest { param( [Parameter(Mandatory = $true)] [string]$Uri, [string]$OutFile = "", [switch]$AsJson ) $headers = New-EnrollmentHeaders if ($AsJson) { return Invoke-RestMethod -Method Get -Uri $Uri -Headers $headers @script:InvokeRestMethodCompat } if (-not [string]::IsNullOrWhiteSpace($OutFile)) { Invoke-WebRequest -Method Get -Uri $Uri -Headers $headers -OutFile $OutFile @script:InvokeWebRequestCompat | Out-Null return } return Invoke-WebRequest -Method Get -Uri $Uri -Headers $headers @script:InvokeWebRequestCompat } function Resolve-ArtifactArchitecture { if (-not [string]::IsNullOrWhiteSpace($Architecture)) { return $Architecture } try { $detected = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant() if ($detected -eq "arm64") { return "arm64" } } catch { # no-op } return "amd64" } function Get-AgentBinaryMetadata { param( [Parameter(Mandatory = $true)] [string]$RequestedArchitecture ) $uri = "$ManagerUrl/api/v1/agent-updates/latest?platform=windows&architecture=$RequestedArchitecture" try { return Invoke-EnrollmentWebRequest -Uri $uri -AsJson } catch { if ($RequestedArchitecture -ne "amd64") { Write-Host "Binario $RequestedArchitecture nao encontrado. Tentando amd64..." $fallbackUri = "$ManagerUrl/api/v1/agent-updates/latest?platform=windows&architecture=amd64" return Invoke-EnrollmentWebRequest -Uri $fallbackUri -AsJson } throw "Nao foi possivel consultar o binario do agente no manager. Detalhe: $($_.Exception.Message)" } } function Restart-ElevatedInstaller { $argumentList = @( "-NoLogo" "-NoProfile" "-ExecutionPolicy" "Bypass" "-File" (Convert-ToPowerShellLiteral -Value $PSCommandPath) "-EnrollmentToken" (Convert-ToPowerShellLiteral -Value $EnrollmentToken) "-ManagerUrl" (Convert-ToPowerShellLiteral -Value $ManagerUrl) "-InstallRoot" (Convert-ToPowerShellLiteral -Value $InstallRoot) "-DataRoot" (Convert-ToPowerShellLiteral -Value $DataRoot) "-TaskName" (Convert-ToPowerShellLiteral -Value $TaskName) "-PollIntervalSeconds" $PollIntervalSeconds "-InventoryIntervalSeconds" $InventoryIntervalSeconds "-AllowCommands" (Convert-ToPowerShellLiteral -Value $AllowCommands) ) if (-not [string]::IsNullOrWhiteSpace($Architecture)) { $argumentList += "-Architecture" $argumentList += $Architecture } if ($InsecureTls) { $argumentList += "-InsecureTls" } if ($SkipWingetBootstrap) { $argumentList += "-SkipWingetBootstrap" } try { $powerShellPath = Get-PreferredPowerShellCommand if ([string]::IsNullOrWhiteSpace($powerShellPath)) { throw "Nenhum executavel do PowerShell foi encontrado neste Windows." } $process = Start-Process -FilePath $powerShellPath -ArgumentList $argumentList -Verb RunAs -Wait -PassThru exit $process.ExitCode } catch { throw "A instalacao precisa ser executada como Administrador para gravar em '$InstallRoot' e registrar a tarefa '$TaskName'." } } $ManagerUrl = Convert-ToSecureManagerUrl -Value $ManagerUrl Enable-InsecureTlsIfRequested if (-not (Test-IsAdministrator)) { Write-Host "Elevando privilegios para concluir a instalacao do agente..." Restart-ElevatedInstaller } Ensure-WinGetInstalled $requestedArchitecture = Resolve-ArtifactArchitecture $binaryMetadata = Get-AgentBinaryMetadata -RequestedArchitecture $requestedArchitecture $downloadUrl = [string]$binaryMetadata.download_url if ([string]::IsNullOrWhiteSpace($downloadUrl)) { throw "O manager nao retornou a URL de download do binario." } if ($downloadUrl.StartsWith("/")) { $downloadUrl = "$ManagerUrl$downloadUrl" } $downloadDir = Join-Path $env:TEMP ("VulnFixerBootstrap-" + [guid]::NewGuid().ToString("N")) New-Item -ItemType Directory -Path $downloadDir -Force | Out-Null try { $binaryPath = Join-Path $downloadDir "vulnfixer-agent.exe" Invoke-EnrollmentWebRequest -Uri $downloadUrl -OutFile $binaryPath if (-not (Test-Path -Path $binaryPath)) { throw "Falha ao baixar o binario do agente." } $expectedHash = [string]$binaryMetadata.sha256 if (-not [string]::IsNullOrWhiteSpace($expectedHash)) { $actualHash = (Get-FileHash -Algorithm SHA256 -Path $binaryPath).Hash.ToLowerInvariant() if (-not [string]::Equals($actualHash, $expectedHash.ToLowerInvariant(), [System.StringComparison]::OrdinalIgnoreCase)) { throw "SHA256 do binario baixado nao confere. Esperado: $expectedHash Atual: $actualHash" } } New-Item -ItemType Directory -Path $InstallRoot -Force | Out-Null New-Item -ItemType Directory -Path $DataRoot -Force | Out-Null $agentTarget = Join-Path $InstallRoot "vulnfixer-agent.exe" $launcherTarget = Join-Path $InstallRoot "Start-VulnFixerAgent.ps1" $configPath = Join-Path $DataRoot "agent-launch.json" $statePath = Join-Path $DataRoot "agent-state.json" Stop-VulnFixerRuntime -TaskName $TaskName -AgentPath $agentTarget if (Test-Path -Path $statePath) { Write-Host "Estado existente do agente preservado: $statePath" } else { @{ agent_id = "" agent_secret = "" } | ConvertTo-Json | Set-Content -Path $statePath -Encoding UTF8 Write-Host "Arquivo de estado inicial criado: $statePath" } Copy-Item -Path $binaryPath -Destination $agentTarget -Force $launcherScript = @' param( [string]$ConfigPath = "$env:ProgramData\VulnFixer\agent-launch.json" ) $ErrorActionPreference = "Stop" if (-not (Test-Path -Path $ConfigPath)) { throw "Arquivo de configuracao nao encontrado: $ConfigPath" } $config = Get-Content -Raw -Path $ConfigPath | ConvertFrom-Json $agentPath = Join-Path $PSScriptRoot "vulnfixer-agent.exe" $statePath = [string]$config.StatePath if (-not (Test-Path -Path $agentPath)) { throw "Binario do agente nao encontrado: $agentPath" } if (-not [string]::IsNullOrWhiteSpace($statePath)) { $stateDirectory = Split-Path -Path $statePath -Parent if (-not [string]::IsNullOrWhiteSpace($stateDirectory)) { New-Item -ItemType Directory -Path $stateDirectory -Force | Out-Null } if (-not (Test-Path -Path $statePath)) { @{ agent_id = "" agent_secret = "" } | ConvertTo-Json | Set-Content -Path $statePath -Encoding UTF8 Write-Host "Arquivo de estado do agente criado: $statePath" } } $env:VULNFIXER_MANAGER_URL = [string]$config.ManagerUrl $env:VULNFIXER_ENROLLMENT_TOKEN = [string]$config.EnrollmentToken $env:VULNFIXER_POLL_INTERVAL_SECONDS = [string]$config.PollIntervalSeconds $env:VULNFIXER_INVENTORY_INTERVAL_SECONDS = [string]$config.InventoryIntervalSeconds $env:VULNFIXER_STATE_PATH = $statePath $env:VULNFIXER_AUTO_UPDATE = "true" if ($config.InsecureTls) { $env:VULNFIXER_INSECURE_TLS = "true" } else { $env:VULNFIXER_INSECURE_TLS = "false" } if ($config.AllowCommands) { $env:VULNFIXER_ALLOW_COMMANDS = [string]$config.AllowCommands } & $agentPath exit $LASTEXITCODE '@ Set-Content -Path $launcherTarget -Value $launcherScript -Encoding UTF8 $config = [ordered]@{ ManagerUrl = $ManagerUrl EnrollmentToken = $EnrollmentToken InsecureTls = [bool]$InsecureTls PollIntervalSeconds = $PollIntervalSeconds InventoryIntervalSeconds = $InventoryIntervalSeconds StatePath = $statePath AllowCommands = $AllowCommands } $config | ConvertTo-Json -Depth 4 | Set-Content -Path $configPath -Encoding UTF8 $action = New-ScheduledTaskAction ` -Execute "powershell.exe" ` -Argument "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$launcherTarget`" -ConfigPath `"$configPath`"" $trigger = New-ScheduledTaskTrigger -AtStartup $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest $taskSettings = New-ScheduledTaskSettingsSet ` -AllowStartIfOnBatteries ` -DontStopIfGoingOnBatteries ` -StartWhenAvailable ` -MultipleInstances IgnoreNew ` -RestartCount 3 ` -RestartInterval (New-TimeSpan -Minutes 1) if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) { Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false } Register-ScheduledTask ` -TaskName $TaskName ` -Action $action ` -Trigger $trigger ` -Principal $principal ` -Settings $taskSettings ` -Description "Executa o agente do VulnFixer no startup do Windows." | Out-Null Start-ScheduledTask -TaskName $TaskName Write-Host "VulnFixer Agent instalado com sucesso." Write-Host "Manager: $ManagerUrl" Write-Host "Arquitetura: $($binaryMetadata.architecture)" Write-Host "Binario: $agentTarget" Write-Host "Config: $configPath" Write-Host "Scheduled Task: $TaskName" } finally { Remove-Item -Path $downloadDir -Recurse -Force -ErrorAction SilentlyContinue }