scripts/install-du.ps1 (398 lines of code) (raw):
<#
.SYNOPSIS
Locally install / Package the Device Update product.
.DESCRIPTION
Assumes the product has been built locally using build.ps1
.EXAMPLE
PS> .\scripts\du_install.ps1 -Type Debug -Install -Package
#>
Param(
# Output directory. Default is {git_root}/out/{Type}
[string]$BuildOutputPath,
# Build type
[ValidateSet('Release', 'RelWithDebInfo', 'Debug')]
[string]$Type = 'Debug',
[ValidateSet('x64', 'arm64')]
[string]$Arch = 'x64',
# Build package?
[switch]$Package = $false,
# Run registration of components on this device?
[switch]$RegisterComponents = $false
)
function Show-Warning {
Param([Parameter(mandatory = $true, position = 0)][string]$Message)
Write-Host -ForegroundColor Yellow -NoNewline 'Warning:'
Write-Host " $Message"
}
function Show-Error {
Param([Parameter(mandatory = $true, position = 0)][string]$Message)
Write-Host -ForegroundColor Red -NoNewline 'Error:'
Write-Host " $Message"
}
function Show-Header {
Param([Parameter(mandatory = $true, position = 0)][string]$Message)
$sep = ":{0}:" -f (' ' * ($Message.Length + 2))
Write-Host -ForegroundColor DarkYellow -BackgroundColor DarkBlue $sep
Write-Host -ForegroundColor White -BackgroundColor DarkBlue (" {0} " -f $Message)
Write-Host -ForegroundColor DarkYellow -BackgroundColor DarkBlue $sep
''
}
function Show-Bullet {
Param([Parameter(mandatory = $true, position = 0)][string]$Message)
Write-Host -ForegroundColor Blue -NoNewline '*'
Write-Host " $Message"
}
function Invoke-CopyFile {
Param(
[Parameter(mandatory = $true)][string]$Source,
[Parameter(mandatory = $true)][string]$Destination)
if (-not (Test-Path -LiteralPath $Source -PathType Leaf)) {
throw "$Source is not a file or doesn't exist"
}
if (-not (Test-Path -LiteralPath $Destination -PathType Container)) {
throw "$Destination is not a folder"
}
$copyNeeded = $true
$destinationFile = Join-Path $Destination (Split-Path $Source -Leaf)
if (Test-Path $destinationFile -PathType Leaf) {
$d_lwt = (Get-ChildItem $destinationFile).LastWriteTime
$s_lwt = (Get-ChildItem $Source).LastWriteTime
if ($d_lwt -ge $s_lwt) {
# Destination is up to date or newer
$copyNeeded = $false
}
}
if ($copyNeeded) {
"Copied: $Source => $Destination"
# Force, in case destination is marked read-only
Copy-Item -Force -LiteralPath $Source -Destination $Destination
}
else {
"Same (or newer): $destinationFile"
}
}
function Register-Components {
Param(
[Parameter(Mandatory = $true)][string]$BinPath,
[Parameter(Mandatory = $true)][string]$DataPath
)
Show-Header 'Registering components'
# Launch agent to write config files
# Based on postinst : register_reference_extensions
$aduciotagent_path = $BinPath + '/AducIotAgent.exe'
$adu_extensions_dir = "$DataPath/extensions"
$adu_extensions_sources_dir = "$adu_extensions_dir/sources"
#
# contentDownloader
#
# curl content downlaoader not used on Windows.
# /var/lib/adu/extensions/content_downloader/extension.json
# $curl_content_downloader_file = 'curl_content_downloader.dll'
# & $aduciotagent_path --register-extension "$adu_extensions_sources_dir/$curl_content_downloader_file" --extension-type contentDownloader --log-level 2
# /var/lib/adu/extensions/content_downloader/extension.json
$do_content_downloader_file = "$adu_extensions_sources_dir/deliveryoptimization_content_downloader.dll"
& $aduciotagent_path --register-extension $do_content_downloader_file --extension-type contentDownloader --log-level 2
if ($LASTEXITCODE -ne 0) {
Show-Error "Registration of '$do_content_downloader_file' failed: $LASTEXITCODE"
}
#
# updateContentHandler
#
# /var/lib/adu/extensions/update_content_handlers/microsoft_script_1/content_handler.json
$microsoft_script_1_handler_file = "$adu_extensions_sources_dir/microsoft_script_1.dll"
& $aduciotagent_path --register-extension $microsoft_script_1_handler_file --extension-type updateContentHandler --extension-id 'microsoft/script:1' --log-level 2
if ($LASTEXITCODE -ne 0) {
Show-Error "Registration of '$do_content_downloader_file' failed: $LASTEXITCODE"
}
# /var/lib/adu/extensions/update_content_handlers/microsoft_simulator_1/content_handler.json
$microsoft_simulator_1_file = "$adu_extensions_sources_dir/microsoft_simulator_1.dll"
& $aduciotagent_path --register-extension $microsoft_simulator_1_file --extension-type updateContentHandler --extension-id 'microsoft/simulator:1'
if ($LASTEXITCODE -ne 0) {
Show-Error "Registration of '$do_content_downloader_file' failed: $LASTEXITCODE"
}
# /var/lib/adu/extensions/update_content_handlers/microsoft_steps_1/content_handler.json
$microsoft_steps_1_file = "$adu_extensions_sources_dir/microsoft_steps_1.dll"
& $aduciotagent_path --register-extension $microsoft_steps_1_file --extension-type updateContentHandler --extension-id 'microsoft/steps:1' --log-level 2
if ($LASTEXITCODE -ne 0) {
Show-Error "Registration of '$do_content_downloader_file' failed: $LASTEXITCODE"
}
& $aduciotagent_path --register-extension $microsoft_steps_1_file --extension-type updateContentHandler --extension-id 'microsoft/update-manifest' --log-level 2
if ($LASTEXITCODE -ne 0) {
Show-Error "Registration of '$do_content_downloader_file' failed: $LASTEXITCODE"
}
& $aduciotagent_path --register-extension $microsoft_steps_1_file --extension-type updateContentHandler --extension-id 'microsoft/update-manifest:4' --log-level 2
if ($LASTEXITCODE -ne 0) {
Show-Error "Registration of '$do_content_downloader_file' failed: $LASTEXITCODE"
}
& $aduciotagent_path --register-extension $microsoft_steps_1_file --extension-type updateContentHandler --extension-id 'microsoft/update-manifest:5' --log-level 2
if ($LASTEXITCODE -ne 0) {
Show-Error "Registration of '$do_content_downloader_file' failed: $LASTEXITCODE"
}
# /var/lib/adu/extensions/update_content_handlers/microsoft_wim_1/content_handler.json
$microsoft_wim_1_handler_file = "$adu_extensions_sources_dir/microsoft_wim_1.dll"
& $aduciotagent_path --register-extension $microsoft_wim_1_handler_file --extension-type updateContentHandler --extension-id 'microsoft/wim:1' --log-level 2
if ($LASTEXITCODE -ne 0) {
Show-Error "Registration of '$do_content_downloader_file' failed: $LASTEXITCODE"
}
''
}
function Create-Adu-Folders {
[Parameter(Mandatory = $true)][string]$BinPath,
[Parameter(Mandatory = $true)][string]$DataPath,
[Parameter(Mandatory = $true)][string]$LibPath
# Not needed -- test data
# mkdir -Force /tmp/adu/testdata | Out-Null
# Unused on Windows
# mkdir -Force /usr/local/lib/systemd/system | Out-Null
mkdir -Force $BinPath | Out-Null
mkdir -Force $LibPath | Out-Null
mkdir -Force "$DataPath" | Out-Null
mkdir -Force "$DataPath/diagnosticsoperationids" | Out-Null
mkdir -Force "$DataPath/downloads" | Out-Null
mkdir -Force "$DataPath/extensions/content_downloader" | Out-Null
mkdir -Force "$DataPath/extensions/sources" | Out-Null
mkdir -Force "$DataPath/sdc" | Out-Null
}
function Install-Adu-Components {
Param(
[Parameter(Mandatory = $true)][string]$BuildBinPath,
[Parameter(Mandatory = $true)][string]$BuildLibPath,
[Parameter(Mandatory = $true)][string]$BinPath,
[Parameter(Mandatory = $true)][string]$LibPath,
[Parameter(Mandatory = $true)][string]$DataPath
)
Show-Header 'Installing ADU Agent'
Create-Adu-Folders `
-BinPath $BinPath `
-DataPath $DataPath `
-LibPath $LibPath
# contoso_component_enumerator
# curl_content_downloader
# microsoft_apt_1
# microsoft_delta_download_handler
Invoke-CopyFile "$BuildBinPath/adu-shell.exe" $LibPath
# Healthcheck expects this file to be read-only.
'Marking adu-shell.exe as read-only.'
Set-ItemProperty -LiteralPath "$LibPath/adu-shell.exe" -Name IsReadOnly -Value $true
Invoke-CopyFile "$BuildBinPath/AducIotAgent.exe" $BinPath
# Determine Windows Kits version.
$WindowsKitsVer = Get-ChildItem -Path 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows Kits\Installed Roots' -Name | Sort-Object -Descending
switch ($WindowsKitsVer.Count) {
0 {
Show-Error 'No Windows Kits installed'
exit 1
}
1 {
}
default {
$WindowsKitsVer = $WindowsKitsVer[0]
Show-Warning "Multiple Windows Kits installed. Using latest ($WindowsKitsVer)."
}
}
if ($Type -eq 'Debug') {
$BuildToolsDPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\BuildTools\VC\Redist\MSVC\14.36.32532\debug_nonredist\$Arch\Microsoft.VC143.DebugCRT"
$WindowsKitsDPath = "${env:ProgramFiles(x86)}\Windows Kits\10\bin\$WindowsKitsVer\$Arch\ucrt"
# Only needed if dynamically linking: getopt, pthreadVC3d, libcrypto-1_1-x64
$dependencies = `
(Join-Path $BuildBinPath 'getopt'), `
(Join-Path $BuildBinPath "libcrypto-1_1-$Arch"), `
(Join-Path $BuildBinPath 'pthreadVC3d'), `
(Join-Path $BuildToolsDPath 'msvcp140d'), `
(Join-Path $BuildToolsDPath 'vcruntime140d'), `
(Join-Path $BuildToolsDPath 'vcruntime140_1d'), `
(Join-Path $WindowsKitsDPath 'ucrtbased')
}
else {
$BuildToolsPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\BuildTools\VC\Redist\MSVC\14.36.32532\$Arch\Microsoft.VC143.CRT"
$WindowsKitsPath = "${env:ProgramFiles(x86)}\Windows Kits\10\Redist\$WindowsKitsVer\ucrt\DLLs\$Arch"
$dependencies = `
(Join-Path $BuildBinPath 'getopt'), `
(Join-Path $BuildBinPath "libcrypto-1_1-$Arch"), `
(Join-Path $BuildBinPath 'pthreadVC3'), `
(Join-Path $BuildToolsPath 'msvcp140'), `
(Join-Path $BuildToolsPath 'vcruntime140'), `
(Join-Path $BuildToolsPath 'vcruntime140_1'), `
(Join-Path $WindowsKitsPath 'ucrtbase')
}
$dependencies | ForEach-Object {
Invoke-CopyFile "$_.dll" $LibPath
Invoke-CopyFile "$_.dll" $BinPath
}
# 'microsoft_swupdate_1', 'microsoft_swupdate_2'
$extensions = 'deliveryoptimization_content_downloader', 'microsoft_script_1', 'microsoft_simulator_1', 'microsoft_steps_1', 'microsoft_wim_1'
$extensions | ForEach-Object {
Invoke-CopyFile "$BuildLibPath/$_.dll" "$DataPath/extensions/sources"
}
if ($Type -eq 'Debug') {
$pthread_dll = 'pthreadVC3d.dll'
}
else {
$pthread_dll = 'pthreadVC3.dll'
}
Invoke-CopyFile "$BuildBinPath/$pthread_dll" $LibPath
Invoke-CopyFile "$BuildBinPath/$pthread_dll" $BinPath
''
if ($RegisterComponents) {
Register-Components -BinPath $BinPath -DataPath $DataPath
}
}
function Create-DataFiles {
Param(
[Parameter(Mandatory = $true)][string]$ConfPath
)
"Data file path: $ConfPath"
''
mkdir -Force $ConfPath | Out-Null
#
# /etc/adu/du-diagnostics-config.json
#
$dest = Join-Path $ConfPath 'du-diagnostics-config.json'
if (-not (Test-Path -LiteralPath $dest -PathType Leaf)) {
"Creating $dest"
@'
{
"logComponents": [
{
"componentName": "DU",
"logPath": "/var/log/adu/"
}
],
"maxKilobytesToUploadPerLogPath": 5
}
'@ | Out-File -Encoding ASCII $dest
}
#
# /etc/adu/du-config.json
#
$dest = Join-Path $ConfPath 'du-config.json'
if (-not (Test-Path -LiteralPath $dest -PathType Leaf)) {
"Creating $dest"
@'
{
"schemaVersion": "1.1",
"aduShellTrustedUsers": [
"adu",
"do"
],
"iotHubProtocol": "mqtt",
"compatPropertyNames": "manufacturer,model",
"manufacturer": "manufacturer",
"model": "model",
"agents": [
{
"name": "aduagent",
"runas": "adu",
"connectionSource": {
"connectionType": "string",
"connectionData": "[NOT_SPECIFIED]"
},
"$description": "manufacturer, model will be matched against update manifest 'compability' attributes",
"manufacturer": "[NOT_SPECIFIED]",
"model": "[NOT_SPECIFIED]"
}
]
}
'@ | Out-File -Encoding ASCII $dest
}
}
function New-TemporaryDirectory {
$dir = New-Item -ItemType Directory -Path (Join-Path ([IO.Path]::GetTempPath()) ([Guid]::NewGuid()))
$dir.FullName
}
#
# _ __ __ _(_)_ _
# | ' \/ _` | | ' \
# |_|_|_\__,_|_|_||_|
#
$root_dir = git.exe rev-parse --show-toplevel
if (!$root_dir) {
Show-Error 'Unable to determine repo root.'
exit 1
}
# Build folders
if (!$BuildOutputPath) {
$BuildOutputPath = "$root_dir/out/$Type"
}
$BuildBinPath = "$BuildOutputPath/bin/$Type"
$BuildLibPath = "$BuildOutputPath/lib/$Type"
# Local install folders
$ETC_FOLDER = '/etc'
$USR_FOLDER = '/usr'
$VAR_FOLDER = '/var'
$ADUC_CONF_FOLDER = "$ETC_FOLDER/adu"
$ADUC_AGENT_FOLDER = "$USR_FOLDER/bin"
$ADUSHELL_FOLDER = "$USR_FOLDER/lib/adu"
$ADUC_DATA_FOLDER = "$VAR_FOLDER/lib/adu"
$ADUC_DOWNLOAD_FOLDER = "$VAR_FOLDER/lib/adu/downloads"
$ADUC_EXTENSIONS_FOLDER = "$ADUC_DATA_FOLDER/extensions"
# Create template data files, if they don't exist already.
Create-DataFiles -ConfPath $ADUC_CONF_FOLDER
# Install binaries locally
Install-Adu-Components `
-BuildBinPath $BuildBinPath `
-BuildLibPath $BuildLibPath `
-BinPath $ADUC_AGENT_FOLDER `
-LibPath $ADUSHELL_FOLDER `
-DataPath $ADUC_DATA_FOLDER
# Sanity check
@($ADUC_CONF_FOLDER, $ADUC_AGENT_FOLDER, $ADUSHELL_FOLDER, $ADUC_DATA_FOLDER, $ADUC_DOWNLOAD_FOLDER, $ADUC_EXTENSIONS_FOLDER) `
| ForEach-Object {
if (-not (Test-Path -LiteralPath $_ -PathType Container)) {
Show-Error "Path not found: $_"
exit 1
}
}
if (Select-String -Pattern '[NOT_SPECIFIED]' -LiteralPath "$ADUC_CONF_FOLDER/du-config.json" -SimpleMatch) {
Show-Warning "Need to edit connectionData, agents.manufacturer and/or agents.model in $ADUC_CONF_FOLDER/du-config.json"
}
#
# Package
#
if ($Package) {
Show-Header 'Packaging Agent'
# Not including:
# /tmp/adu/testdata has test files, but we're not packaging test collateral.
# $ADUC_CONF_FOLDER is configuration, and might have secrets.
# Need to copy everything to a temporary directory, as Compress-Archive
# can't persist relative paths into the archive.
'Preparing directory . . .'
$temp = New-TemporaryDirectory
$dest = Join-Path $temp $ADUC_AGENT_FOLDER
"$ADUC_AGENT_FOLDER => $dest"
Copy-Item -Recurse -Path $ADUC_AGENT_FOLDER -Destination $dest
$dest = Join-Path $temp $ADUSHELL_FOLDER
"$ADUSHELL_FOLDER => $dest"
Copy-Item -Recurse -Path $ADUSHELL_FOLDER -Destination $dest
$dest = Join-Path $temp $ADUC_EXTENSIONS_FOLDER
"$ADUC_EXTENSIONS_FOLDER => $dest"
Copy-Item -Recurse -Path $ADUC_EXTENSIONS_FOLDER -Destination $dest
$dest = Join-Path $temp (Join-Path $ADUC_AGENT_FOLDER 'symbols')
"Symbols => $dest"
New-Item -ItemType Directory -Path $dest | Out-Null
Get-ChildItem `
-Path $BuildBinPath `
-Filter '*.pdb' `
-Exclude '*unit_test.pdb', '*unit_tests.pdb', '*_ut.pdb', '*_tests_helper.pdb' `
-Recurse `
| ForEach-Object {
Copy-Item -LiteralPath $_.FullName -Destination $dest
}
# Add git info as '_git-info.txt' in symbols folder.
"Date: $(Get-Date)", "Head: $(git.exe rev-parse --short HEAD)", "Release: $(git.exe rev-parse --abbrev-ref HEAD)" `
| Out-File -Encoding ascii (Join-Path $dest '_git-info.txt')
$dest = Join-Path $temp $ADUC_CONF_FOLDER
New-Item -ItemType Directory -Path $dest | Out-Null
Create-DataFiles -ConfPath $dest
$dest = Join-Path $temp $ADUC_DOWNLOAD_FOLDER
New-Item -ItemType Directory -Path $dest | Out-Null
# -Format FileDateTime includes milliseconds, which is overkill.
$archive = Join-Path ([IO.Path]::GetTempPath()) ('du-{0}-{1}.zip' -f $Type, (Get-Date -Format 'yyyyMMddTHHmmss'))
''
"Archive file: $archive"
Compress-Archive `
-CompressionLevel Optimal `
-DestinationPath $archive `
-Update `
-Path "$temp/*"
Remove-Item -Recurse -Force $temp
}