ebpf-for-windows/scripts/install_ebpf.psm1

418 строки
18 KiB
PowerShell

# Copyright (c) eBPF for Windows contributors
# SPDX-License-Identifier: MIT
param ([Parameter(Mandatory=$True)] [string] $WorkingDirectory,
[Parameter(Mandatory=$True)] [string] $LogFileName)
Push-Location $WorkingDirectory
Import-Module $PSScriptRoot\common.psm1 -Force -ArgumentList ($LogFileName) -WarningAction SilentlyContinue
$VcRedistPath = Join-Path $WorkingDirectory "vc_redist.x64.exe"
$MsiPath = Join-Path $WorkingDirectory "ebpf-for-windows.msi"
# eBPF drivers and services.
$EbpfDrivers = @{
"EbpfCore" = [PSCustomObject]@{
"Name" = "ebpfcore.sys"
"IsDriver" = $true
"InstalledByMsi" = $true
"ReplaceForRegressionTest" = $false
}
"NetEbpfExt" = [PSCustomObject]@{
"Name" = "netebpfext.sys"
"IsDriver" = $true
"InstalledByMsi" = $true
"ReplaceForRegressionTest" = $true
}
"SampleEbpfExt" = [PSCustomObject]@{
"Name" = "sample_ebpf_ext.sys"
"IsDriver" = $true
"InstalledByMsi" = $false
"ReplaceForRegressionTest" = $true
}
"EbpfSvc" = [PSCustomObject]@{
"Name" = "ebpfsvc.exe"
"IsDriver" = $false
"InstalledByMsi" = $true
"ReplaceForRegressionTest" = $false
}
}
# eBPF Debug Runtime DLLs.
$VCDebugRuntime = @(
"concrt140d.dll",
"msvcp140d.dll",
"msvcp140d_atomic_wait.dll",
"msvcp140d_codecvt_ids.dll",
"msvcp140_1d.dll",
"msvcp140_2d.dll",
"vccorlib140d.dll",
"vcruntime140d.dll",
"vcruntime140_1d.dll",
"vcruntime140_threadsd.dll",
"ucrtbased.dll"
)
function Enable-KMDFVerifier
{
# Enable KMDF verifier for the eBPF drivers.
$EbpfDrivers.GetEnumerator() | ForEach-Object {
if ($_.Value.IsDriver) {
Write-Log("Enabling KMDF verifier for $($_.Key)...")
New-Item -Path ("HKLM:\System\CurrentControlSet\Services\{0}\Parameters\Wdf" -f $_.Name) -Force -ErrorAction Stop
New-ItemProperty -Path ("HKLM:\System\CurrentControlSet\Services\{0}\Parameters\Wdf" -f $_.Name) -Name "VerifierOn" -Value 1 -PropertyType DWord -Force -ErrorAction Stop
New-ItemProperty -Path ("HKLM:\System\CurrentControlSet\Services\{0}\Parameters\Wdf" -f $_.Name) -Name "TrackHandles" -Value "*" -PropertyType MultiString -Force -ErrorAction Stop
}
}
}
#
# Start file/memory based wpr tracing (if enabled).
#
function Start-WPRTrace
{
param([parameter(Mandatory=$true)][bool] $KmTracing,
[parameter(Mandatory=$true)][string] $KmTraceType)
Write-Log("kernel mode ETW tracing: " + $KmTracing)
if ($KmTracing) {
if ($KmTraceType -eq "file") {
Write-Log "Starting KM ETW tracing (File)"
$ProcInfo = Start-Process -FilePath "wpr.exe" `
-ArgumentList "-start EbpfForWindows.wprp!EbpfForWindowsProvider-File -filemode" `
-NoNewWindow -Wait -PassThru `
-RedirectStandardError .\StdErr.txt
} else {
Write-Log "Starting KM ETW tracing (Memory)"
$ProcInfo = Start-Process -FilePath "wpr.exe" `
-ArgumentList "-start EbpfForWindows.wprp!EbpfForWindowsProvider-Memory" `
-NoNewWindow -Wait -PassThru `
-RedirectStandardError .\StdErr.txt
}
if ($ProcInfo.ExitCode -ne 0) {
Write-Log("wpr.exe start ETL trace failed. Exit code: " + $ProcInfo.ExitCode)
Write-log "wpr.exe (start) error output: "
foreach ($line in get-content -Path .\StdErr.txt) {
Write-Log( "`t" + $line)
}
throw "Start ETL trace failed."
}
Write-Log("Start ETL trace success. wpr.exe exit code: " + $ProcInfo.ExitCode + "`n")
Write-Log "Query ETL tracing status after trace start"
$ProcInfo = Start-Process -FilePath "wpr.exe" `
-ArgumentList "-status profiles collectors -details" `
-NoNewWindow -Wait -PassThru `
-RedirectStandardOut .\StdOut.txt -RedirectStandardError .\StdErr.txt
if ($ProcInfo.ExitCode -ne 0) {
Write-Log("wpr.exe query ETL trace status failed. Exit code: " + $ProcInfo.ExitCode)
Write-log "wpr.exe (query) error output: "
foreach ($line in get-content -Path .\StdErr.txt) {
Write-Log( "`t" + $line)
}
throw "Query ETL trace status failed."
} else {
Write-log "wpr.exe (query) results: "
foreach ($line in get-content -Path .\StdOut.txt) {
Write-Log( " `t" + $line)
}
}
Write-Log("Query ETL trace status success. wpr.exe exit code: " + $ProcInfo.ExitCode + "`n" )
}
}
# This function specifically tests that all eBPF drivers and services can be stopped.
function Stop-eBPFComponents {
# First, stop user mode service, so that EbpfCore does not hang on stop.
if (Get-Service "eBPFSvc" -ErrorAction SilentlyContinue) {
try {
Stop-Service "eBPFSvc" -ErrorAction Stop 2>&1 | Write-Log
Write-Log "eBPFSvc service stopped." -ForegroundColor Green
} catch {
throw "Failed to stop 'eBPFSvc' service: $_."
}
} else {
Write-Log "'eBPFSvc' service is not present (i.e., release build), skipping stopping." -ForegroundColor Green
}
# Stop the drivers and services.
$EbpfDrivers.GetEnumerator() | ForEach-Object {
try {
if ($_.Value.IsDriver) {
Stop-Service $_.Name -ErrorAction Stop 2>&1 | Write-Log
Write-Log "$($_.Key) driver stopped." -ForegroundColor Green
}
} catch {
throw "Failed to stop $($_.Key) driver: $_."
}
}
}
function Print-eBPFComponentsStatus([string] $message = "")
{
# Print the status of the eBPF drivers and services.
Write-Log($message)
$EbpfDrivers.GetEnumerator() | ForEach-Object {
Write-Log "Querying the status of $($_.Key)..."
sc.exe query $_.Key 2>&1 | Write-Log
}
}
function Install-eBPFComponents
{
param([parameter(Mandatory=$true)] [bool] $KmTracing,
[parameter(Mandatory=$true)] [string] $KmTraceType,
[parameter(Mandatory=$false)] [bool] $KMDFVerifier = $false,
[parameter(Mandatory=$true)] [string] $TestMode)
# Print the status of the eBPF drivers and services before installation.
# This is useful for detecting issues with the runner baselines.
Print-eBPFComponentsStatus "Querying the status of eBPF drivers and services before the installation (none should be present)..." | Out-Null
# Install the Visual C++ Redistributable (Release version, which is required for the MSI installation).
Write-Log("Installing Visual C++ Redistributable from '$VcRedistPath'...")
$process = Start-Process -FilePath $VcRedistPath -ArgumentList "/quiet", "/norestart" -Wait -PassThru
if ($process.ExitCode -ne 0) {
Write-Log("Visual C++ Redistributable installation FAILED. Exit code: $($process.ExitCode).") -ForegroundColor Red
throw ("Visual C++ Redistributable installation FAILED. Exit code: $($process.ExitCode).")
}
Write-Log("Cleaning up...")
Remove-Item $VcRedistPath -Force
Write-Log("Visual C++ Redistributable installation completed successfully!") -ForegroundColor Green
# Copy the VC debug runtime DLLs to the system32 directory,
# so that debug versions of the MSI can be installed (i.e., export_program_info.exe will not fail).
Write-Log("Copying VC debug runtime DLLs to the $system32Path directory...")
# Test if the VC debug runtime DLLs are present in the working directory (indicating a debug build).
$VCDebugRuntime = $VCDebugRuntime | Where-Object { Test-Path (Join-Path $WorkingDirectory $_) }
if (-not $VCDebugRuntime) {
Write-Log("VC debug runtime DLLs not found in the working directory (i.e., release build). Skipping this step.") -ForegroundColor Yellow
} else {
$system32Path = Join-Path $env:SystemRoot "System32"
$VCDebugRuntime | ForEach-Object {
$sourcePath = Join-Path $WorkingDirectory $_
$destinationPath = Join-Path $system32Path $_
Write-Log("Copying '$sourcePath' to '$destinationPath'...")
Copy-Item -Path $sourcePath -Destination $destinationPath -Force
}
Write-Log("VC debug runtime DLLs copied successfully!") -ForegroundColor Green
}
# Install the MSI package.
$arguments = "/i $MsiPath ADDLOCAL=ALL /qn /norestart /l*v msi-install.log"
Write-Log("Installing the eBPF MSI package: 'msiexec.exe $arguments'...")
$process = Start-Process -FilePath msiexec.exe -ArgumentList $arguments -Wait -PassThru
if ($process.ExitCode -ne 0) {
Write-Log("MSI installation FAILED. Exit code: $($process.ExitCode).") -ForegroundColor Red
# For clear readability within the CICD pipeline and final uploaded log output,
# read each line of the log file and print it (otherwise all the log content is printed as a single line).
Write-Log("Contents of msi-install.log:")
Get-Content -Path "msi-install.log" | ForEach-Object {
Write-Log($_)
}
throw ("MSI installation FAILED. Exit code: $($process.ExitCode).")
}
Write-Log("eBPF MSI installation completed successfully!") -ForegroundColor Green
# Install the extra drivers that are not installed by the MSI package.
$EbpfDrivers.GetEnumerator() | ForEach-Object {
if (-not $_.Value.InstalledByMsi) {
$driverPath = if (Test-Path -Path ("$pwd\{0}" -f $_.Value.Name)) {
"$pwd\{0}" -f $_.Value.Name
} elseif (Test-Path -Path ("$pwd\drivers\{0}" -f $_.Value.Name)) {
"$pwd\drivers\{0}" -f $_.Value.Name
} else {
throw ("Driver file not found for $($_.Key).")
}
Write-Log("Installing $($_.Key)...") -ForegroundColor Green
sc.exe create $_.Key type=kernel start=demand binpath=$driverPath 2>&1 | Write-Log
if ($LASTEXITCODE -ne 0) {
throw ("Failed to create $($_.Key) driver.")
} else {
Write-Log("$($_.Key) driver created.") -ForegroundColor Green
# Start the service.
Write-Log("Starting $($_.Key) service...") -ForegroundColor Green
sc.exe start $_.Key 2>&1 | Write-Log
if ($LASTEXITCODE -ne 0) {
throw ("Failed to start $($_.Key) service.")
} else {
Write-Log("$($_.Key) service started.") -ForegroundColor Green
}
}
}
}
# If TestMode is "Regression", reinstall the extension drivers from the regression test artifacts.
if ($TestMode -eq "Regression") {
Write-Log("Reinstalling the extension drivers from the regression test artifacts...") -ForegroundColor Green
$EbpfDrivers.GetEnumerator() | ForEach-Object {
if ($_.Value.InstalledByMsi) {
if ($_.Value.ReplaceForRegressionTest) {
# Stop and delete the service.
Write-Log("Regression Tests: Stopping $($_.Key) service...") -ForegroundColor Green
sc.exe stop $_.Key 2>&1 | Write-Log
if ($LASTEXITCODE -ne 0) {
throw ("Failed to stop $($_.Key) service.")
} else {
Write-Log("$($_.Key) service stopped.") -ForegroundColor Green
Write-Log("Deleting $($_.Key) service...") -ForegroundColor Green
sc.exe delete $_.Key 2>&1 | Write-Log
if ($LASTEXITCODE -ne 0) {
throw ("Failed to delete $($_.Key) service.")
} else {
Write-Log("$($_.Key) service deleted.") -ForegroundColor Green
}
}
# Install the driver.
$driverPath = if (Test-Path -Path ("$pwd\{0}" -f $_.Value.Name)) {
"$pwd\{0}" -f $_.Value.Name
} elseif (Test-Path -Path ("$pwd\drivers\{0}" -f $_.Value.Name)) {
"$pwd\drivers\{0}" -f $_.Value.Name
} else {
throw ("Driver file not found for $($_.Key).")
}
Write-Log("Installing $($_.Key) from path $driverPath ...") -ForegroundColor Green
sc.exe create $_.Key type=kernel start=demand binpath=$driverPath 2>&1 | Write-Log
if ($LASTEXITCODE -ne 0) {
throw ("Failed to create $($_.Key) driver.")
} else {
Write-Log("$($_.Key) driver created.") -ForegroundColor Green
# Start the service.
Write-Log("Starting $($_.Key) service...") -ForegroundColor Green
sc.exe start $_.Key 2>&1 | Write-Log
if ($LASTEXITCODE -ne 0) {
throw ("Failed to start $($_.Key) service.")
} else {
Write-Log("$($_.Key) service started.") -ForegroundColor Green
}
}
}
}
}
}
# Refresh Path so EbpfApi.dll can be found.
$machinepath = [system.environment]::getenvironmentvariable("path", [system.environmentvariabletarget]::machine)
$userpath = [system.environment]::getenvironmentvariable("path", [system.environmentvariabletarget]::user)
$env:path = $machinepath + ";" + $userpath
# Export program info for the sample driver.
Write-Log("Running 'export_program_info_sample.exe'...")
if (Test-Path -Path "export_program_info_sample.exe") {
.\export_program_info_sample.exe 2>&1 | Write-Log
if ($LASTEXITCODE -ne 0) {
throw ("Failed to run 'export_program_info_sample.exe'.");
} else {
Write-Log "'export_program_info_sample.exe' succeeded." -ForegroundColor Green
}
}
# Export program info for the sample driver as SYSTEM.
Write-Log("Running 'export_program_info_sample.exe' as SYSTEM...")
if (Test-Path -Path "export_program_info_sample.exe") {
$TestCommand = "$pwd\PsExec64.exe"
$Arguments = "-accepteula -nobanner -s -w `"$pwd`" `"$pwd\export_program_info_sample.exe`""
Start-Process -NoNewWindow -Wait "$TestCommand" -ArgumentList "$Arguments"
if ($LASTEXITCODE -ne 0) {
throw ("Failed to run 'export_program_info_sample.exe as SYSTEM'.");
} else {
Write-Log "'export_program_info_sample.exe' succeeded." -ForegroundColor Green
}
}
# Print the status of the eBPF drivers and services after installation.
Print-eBPFComponentsStatus "Verifying the status of eBPF drivers and services after the installation..." | Out-Null
# Optionally enable KMDF verifier and tag tracking.
if ($KMDFVerifier) {
Enable-KMDFVerifier
}
# Start KM tracing.
Start-WPRTrace -KmTracing $KmTracing -KmTraceType $KmTraceType
}
function Uninstall-eBPFComponents
{
# This section double-checks that all drivers and services are stopped before proceeding with uninstallation.
# It iterates through each driver and service, retrieving its status, and if any service is found to be running, it throws an error.
$allStopped = $true
if (Get-Service "eBPFSvc" -ErrorAction SilentlyContinue) {
$serviceStatus = (Get-Service "eBPFSvc").Status
if ($serviceStatus -ne "Stopped") {
Write-Log "eBPFSvc service is not stopped." -ForegroundColor Red
$allStopped = $false
}
Write-Log "eBPFSvc service stopped." -ForegroundColor Green
} else {
Write-Log "'eBPFSvc' service is not present (i.e., release build), skipping stopping." -ForegroundColor Green
}
$EbpfDrivers.GetEnumerator() | ForEach-Object {
if ($_.Value.IsDriver) {
$driverStatus = (Get-Service $_.Key).Status
if ($driverStatus -ne "Stopped") {
Write-Log "$($_.Key) driver is not stopped." -ForegroundColor Red
$allStopped = $false
}
}
}
if (-not $allStopped) {
throw "One or more services are not stopped."
}
# Firstly, uninstall the extra drivers that are not installed by the MSI package.
$EbpfDrivers.GetEnumerator() | ForEach-Object {
if (-not $_.Value.InstalledByMsi) {
Write-Log("Deleting $($_.Key) service...") -ForegroundColor Green
sc.exe delete $_.Key 2>&1 | Write-Log
if ($LASTEXITCODE -ne 0) {
throw ("Failed to delete $($_.Key) service.")
} else {
Write-Log("$($_.Key) service deleted.") -ForegroundColor Green
}
}
}
# Clear export program info for the sample driver.
Write-Log("Running 'export_program_info_sample.exe --clear'...")
if (Test-Path -Path "export_program_info_sample.exe --clear") {
.\export_program_info_sample.exe --clear
if ($LASTEXITCODE -ne 0) {
throw ("Failed to run 'export_program_info_sample.exe --clear'.")
} else {
Write-Log("'export_program_info_sample.exe --clear' succeeded.") -ForegroundColor Green
}
}
Write-Log("Clearing export program info for the sample driver completed successfully!") -ForegroundColor Green
# Uninstall the MSI package.
$arguments = "/x $MsiPath /qn /norestart /l*v msi-uninstall.log"
Write-Log("Uninstalling eBPF MSI package at 'msiexec.exe $arguments'...")
$process = Start-Process -FilePath msiexec.exe -ArgumentList $arguments -Wait -PassThru
if ($process.ExitCode -eq 0) {
Write-Log("Uninstallation successful!") -ForegroundColor Green
} else {
Write-Log("Uninstallation FAILED. Exit code: $($process.ExitCode)") -ForegroundColor Red
# For clear readability within the CICD pipeline and final uploaded log output,
# read each line of the log file and print it (otherwise all the log content is printed as a single line).
Write-Log("Contents of msi-uninstall.log:")
Get-Content -Path "msi-uninstall.log" | ForEach-Object {
Write-Log($_)
}
throw ("MSI uninstallation FAILED. Exit code: $($process.ExitCode).")
}
Write-Log("MSI uninstallation completed successfully!") -ForegroundColor Green
# Stop KM tracing.
$process = Start-Process -FilePath wpr.exe -ArgumentList "-cancel" -NoNewWindow -Wait -PassThru
if ($process.ExitCode -ne 0) {
Write-Log("Failed to stop WPR session with error: $($process.ExitCode)")
}
}