Add MSI Support

This commit is contained in:
Twangboy 2022-12-22 18:18:28 -07:00
parent 689882d2d3
commit 76731ff26f
No known key found for this signature in database
GPG key ID: ED267D5C0DE6F8A6
139 changed files with 4288 additions and 326 deletions

View file

@ -79,23 +79,22 @@ $ErrorActionPreference = "Stop"
#-------------------------------------------------------------------------------
$SCRIPT_DIR = (Get-ChildItem "$($myInvocation.MyCommand.Definition)").DirectoryName
$PROJECT_DIR = $(git rev-parse --show-toplevel)
$SALT_SRC_DIR = "$( (Get-Item $PROJECT_DIR).Parent.FullName )\salt"
#-------------------------------------------------------------------------------
# Verify Salt and Version
#-------------------------------------------------------------------------------
if ( [String]::IsNullOrEmpty($Version) ) {
if ( ! (Test-Path -Path $SALT_SRC_DIR) ) {
Write-Host "Missing Salt Source Directory: $SALT_SRC_DIR"
if ( ! (Test-Path -Path $PROJECT_DIR) ) {
Write-Host "Missing Salt Source Directory: $PROJECT_DIR"
exit 1
}
Push-Location $SALT_SRC_DIR
Push-Location $PROJECT_DIR
$Version = $( git describe )
$Version = $Version.Trim("v")
Pop-Location
if ( [String]::IsNullOrEmpty($Version) ) {
Write-Host "Failed to get version from $SALT_SRC_DIR"
Write-Host "Failed to get version from $PROJECT_DIR"
exit 1
}
}
@ -114,15 +113,27 @@ Write-Host $("v" * 80)
#-------------------------------------------------------------------------------
# Install NSIS
#-------------------------------------------------------------------------------
& "$SCRIPT_DIR\install_nsis.ps1"
if ( ! $? ) {
Write-Host "Failed to install NSIS"
exit 1
}
#-------------------------------------------------------------------------------
# Install WIX
#-------------------------------------------------------------------------------
& "$SCRIPT_DIR\install_wix.ps1"
if ( ! $? ) {
Write-Host "Failed to install WIX"
exit 1
}
#-------------------------------------------------------------------------------
# Install Visual Studio Build Tools
#-------------------------------------------------------------------------------
& "$SCRIPT_DIR\install_vs_buildtools.ps1"
if ( ! $? ) {
Write-Host "Failed to install Visual Studio Build Tools"
@ -132,6 +143,7 @@ if ( ! $? ) {
#-------------------------------------------------------------------------------
# Build Python
#-------------------------------------------------------------------------------
$KeywordArguments = @{
Version = $PythonVersion
Architecture = $Architecture
@ -148,6 +160,7 @@ if ( ! $? ) {
#-------------------------------------------------------------------------------
# Install Salt
#-------------------------------------------------------------------------------
& "$SCRIPT_DIR\install_salt.ps1"
if ( ! $? ) {
Write-Host "Failed to install Salt"
@ -157,6 +170,7 @@ if ( ! $? ) {
#-------------------------------------------------------------------------------
# Prep Salt for Packaging
#-------------------------------------------------------------------------------
& "$SCRIPT_DIR\prep_salt.ps1"
if ( ! $? ) {
Write-Host "Failed to Prepare Salt for packaging"
@ -164,20 +178,36 @@ if ( ! $? ) {
}
#-------------------------------------------------------------------------------
# Build Package
# Build NSIS Package
#-------------------------------------------------------------------------------
$KeywordArguments = @{}
if ( ! [String]::IsNullOrEmpty($Version) ) {
$KeywordArguments.Add("Version", $Version)
}
powershell -file "$SCRIPT_DIR\build_pkg.ps1" @KeywordArguments
powershell -file "$SCRIPT_DIR\nsis\build_pkg.ps1" @KeywordArguments
if ( ! $? ) {
Write-Host "Failed to build package"
Write-Host "Failed to build NSIS package"
exit 1
}
#-------------------------------------------------------------------------------
# Build MSI Package
#-------------------------------------------------------------------------------
powershell -file "$SCRIPT_DIR\msi\build_pkg.ps1" @KeywordArguments
if ( ! $? ) {
Write-Host "Failed to build NSIS package"
exit 1
}
#-------------------------------------------------------------------------------
# Script Complete
#-------------------------------------------------------------------------------
Write-Host $("^" * 80)
Write-Host "Build Salt $Architecture Completed" -ForegroundColor Cyan
Write-Host $("#" * 80)

View file

@ -55,11 +55,23 @@ param(
)
#-------------------------------------------------------------------------------
# Script Preferences
#-------------------------------------------------------------------------------
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
$ProgressPreference = "SilentlyContinue"
$ErrorActionPreference = "Stop"
#-------------------------------------------------------------------------------
# Script Functions
#-------------------------------------------------------------------------------
function Write-Result($result, $ForegroundColor="Green") {
$position = 80 - $result.Length - [System.Console]::CursorLeft
Write-Host -ForegroundColor $ForegroundColor ("{0,$position}$result" -f "")
}
#-------------------------------------------------------------------------------
# Start the Script
#-------------------------------------------------------------------------------
@ -90,9 +102,9 @@ if ( Test-Path -Path "$profile" ) {
Write-Host "Backing up PowerShell Profile: " -NoNewline
Move-Item -Path "$profile" -Destination "$profile.salt_bak"
if ( Test-Path -Path "$profile.salt_bak" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}
@ -104,9 +116,9 @@ if ( ! (Test-Path -Path "$(Split-Path "$profile" -Parent)") ) {
New-Item -Path "$(Split-Path "$profile" -Parent)" -ItemType Directory | Out-Null
if ( Test-Path -Path "$(Split-Path "$profile" -Parent)" ) {
$CREATED_POWERSHELL_PROFILE_DIRECTORY = $true
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}
@ -114,7 +126,7 @@ if ( ! (Test-Path -Path "$(Split-Path "$profile" -Parent)") ) {
Write-Host "Creating Temporary PowerShell Profile: " -NoNewline
'$ProgressPreference = "SilentlyContinue"' | Out-File -FilePath $profile
'$ErrorActionPreference = "Stop"' | Out-File -FilePath $profile
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
#-------------------------------------------------------------------------------
# Make sure we're not in a virtual environment
@ -124,10 +136,10 @@ if ( $env:VIRTUAL_ENV ) {
. deactivate
Write-Host $env:VIRTUAL_ENV
if ( $env:VIRTUAL_ENV ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
@ -157,10 +169,10 @@ if ( Test-Path -Path "$SCRIPT_DIR\venv" ) {
Write-Host "Removing virtual environment directory: " -NoNewline
Remove-Item -Path "$SCRIPT_DIR\venv" -Recurse -Force
if ( Test-Path -Path "$SCRIPT_DIR\venv" ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
@ -168,10 +180,10 @@ if ( Test-Path -Path "$RELENV_DIR" ) {
Write-Host "Removing existing relenv directory: " -NoNewline
Remove-Item -Path "$RELENV_DIR" -Recurse -Force
if ( Test-Path -Path "$RELENV_DIR" ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
@ -179,10 +191,10 @@ if ( Test-Path -Path "$BUILD_DIR" ) {
Write-Host "Removing existing build directory: " -NoNewline
Remove-Item -Path "$BUILD_DIR" -Recurse -Force
if ( Test-Path -Path "$BUILD_DIR" ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
@ -195,18 +207,18 @@ Start-Process -FilePath "$SYS_PY_BIN" `
-WorkingDirectory "$SCRIPT_DIR" `
-Wait -WindowStyle Hidden
if ( Test-Path -Path "$SCRIPT_DIR\venv" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed"
Write-Result "Failed"
exit 1
}
Write-Host "Activating virtual environment: " -NoNewline
. "$SCRIPT_DIR\venv\Scripts\activate.ps1"
if ( $env:VIRTUAL_ENV ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -217,9 +229,9 @@ Write-Host "Installing Relenv: " -NoNewLine
pip install relenv --disable-pip-version-check | Out-Null
$output = pip list --disable-pip-version-check
if ("relenv" -in $output.split()) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -234,9 +246,9 @@ if ( $Build ) {
relenv fetch --arch $ARCH | Out-Null
}
if ( Test-Path -Path "$RELENV_DIR\build\$ARCH-win.tar.xz") {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -246,29 +258,28 @@ if ( Test-Path -Path "$RELENV_DIR\build\$ARCH-win.tar.xz") {
Write-Host "Extracting Python environment: " -NoNewLine
relenv create --arch $ARCH "$BUILD_DIR"
If ( Test-Path -Path "$BLD_PY_BIN" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
#-------------------------------------------------------------------------------
# Retrieving SSL Libraries
#-------------------------------------------------------------------------------
Write-Host "Retrieving SSL Libaries: " -NoNewline
$libeay_url = "$SALT_DEP_URL/openssl/1.1.1k/libeay32.dll"
$ssleay_url = "$SALT_DEP_URL/openssl/1.1.1k/ssleay32.dll"
Invoke-WebRequest -Uri "$libeay_url" -OutFile "$SCRIPTS_DIR\libeay32.dll" | Out-Null
Invoke-WebRequest -Uri "$ssleay_url" -OutFile "$SCRIPTS_DIR\ssleay32.dll" | Out-Null
if ( ! (Test-Path -Path "$SCRIPTS_DIR\libeay32.dll") ) {
Write-Host "Failed" -ForegroundColor Red
exit 1
}
if ( Test-Path -Path "$SCRIPTS_DIR\ssleay32.dll" ) {
Write-Host "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
exit 1
$ssllibs = "libeay32.dll",
"ssleay32.dll"
$ssllibs | ForEach-Object {
$url = "$SALT_DEP_URL/openssl/1.1.1k/$_"
$file = "$SCRIPTS_DIR\$_"
Write-Host "Retrieving $_`: " -NoNewline
Invoke-WebRequest -Uri "$url" -OutFile "$file" | Out-Null
if ( Test-Path -Path "$file" ) {
Write-Result "Success" -ForegroundColor Green
} else {
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}
#-------------------------------------------------------------------------------
@ -283,10 +294,10 @@ $remove | ForEach-Object {
Write-Host "Removing $_`: " -NoNewline
Remove-Item -Path "$BUILD_DIR\Lib\$_" -Recurse -Force
if (Test-Path -Path "$BUILD_DIR\Lib\$_") {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
}
@ -298,9 +309,9 @@ if ( $CREATED_POWERSHELL_PROFILE_DIRECTORY ) {
Write-Host "Removing PowerShell Profile Directory: " -NoNewline
Remove-Item -Path "$(Split-Path "$profile" -Parent)" -Recurse -Force
if ( ! (Test-Path -Path "$(Split-Path "$profile" -Parent)") ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failure" -ForegroundColor Red
Write-Result "Failure" -ForegroundColor Red
exit 1
}
}
@ -309,9 +320,9 @@ if ( Test-Path -Path "$profile" ) {
Write-Host "Removing Temporary PowerShell Profile: " -NoNewline
Remove-Item -Path "$profile" -Force
if ( ! (Test-Path -Path "$profile") ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}
@ -320,9 +331,9 @@ if ( Test-Path -Path "$profile.salt_bak" ) {
Write-Host "Restoring Original PowerShell Profile: " -NoNewline
Move-Item -Path "$profile.salt_bak" -Destination "$profile"
if ( Test-Path -Path "$profile" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}

View file

@ -26,11 +26,21 @@ $ProgressPreference = "SilentlyContinue"
$ErrorActionPreference = "Stop"
#-------------------------------------------------------------------------------
# Variables
# Script Variables
#-------------------------------------------------------------------------------
$SCRIPT_DIR = (Get-ChildItem "$($myInvocation.MyCommand.Definition)").DirectoryName
$RELENV_DIR = "${env:LOCALAPPDATA}\relenv"
#-------------------------------------------------------------------------------
# Script Functions
#-------------------------------------------------------------------------------
function Write-Result($result, $ForegroundColor="Green") {
$position = 80 - $result.Length - [System.Console]::CursorLeft
Write-Host -ForegroundColor $ForegroundColor ("{0,$position}$result" -f "")
}
#-------------------------------------------------------------------------------
# Start the Script
#-------------------------------------------------------------------------------
@ -54,10 +64,10 @@ if ( Test-Path -Path "$SCRIPT_DIR\venv" ) {
Write-Host "Removing venv directory: " -NoNewline
Remove-Item -Path "$SCRIPT_DIR\venv" -Recurse -Force
if ( Test-Path -Path "$SCRIPT_DIR\venv" ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
@ -68,10 +78,10 @@ if ( Test-Path -Path "$SCRIPT_DIR\build" ) {
Write-Host "Removing build directory: " -NoNewline
Remove-Item -Path "$SCRIPT_DIR\build" -Recurse -Force
if ( Test-Path -Path "$SCRIPT_DIR\build" ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
@ -82,10 +92,10 @@ if ( Test-Path -Path "$SCRIPT_DIR\buildenv" ) {
Write-Host "Removing buildenv directory: " -NoNewline
Remove-Item -Path "$SCRIPT_DIR\buildenv" -Recurse -Force
if ( Test-Path -Path "$SCRIPT_DIR\buildenv" ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
@ -96,10 +106,10 @@ if ( Test-Path -Path "$SCRIPT_DIR\prereqs" ) {
Write-Host "Removing prereqs directory: " -NoNewline
Remove-Item -Path "$SCRIPT_DIR\prereqs" -Recurse -Force
if ( Test-Path -Path "$SCRIPT_DIR\prereqs" ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
@ -110,10 +120,10 @@ if ( Test-Path -Path "$RELENV_DIR" ) {
Write-Host "Removing relenv directory: " -NoNewline
Remove-Item -Path "$RELENV_DIR" -Recurse -Force
if ( Test-Path -Path "$RELENV_DIR" ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}

View file

@ -10,44 +10,22 @@ required to build the Salt installer
install_nsis.ps1
#>
#-------------------------------------------------------------------------------
# Script Preferences
#-------------------------------------------------------------------------------
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
$ProgressPreference = "SilentlyContinue"
$ErrorActionPreference = "Stop"
#-------------------------------------------------------------------------------
# Import Modules
# Script Functions
#-------------------------------------------------------------------------------
$SCRIPT_DIR = (Get-ChildItem "$($myInvocation.MyCommand.Definition)").DirectoryName
Import-Module $SCRIPT_DIR\Modules\uac-module.psm1
Import-Module $SCRIPT_DIR\Modules\zip-module.psm1
#-------------------------------------------------------------------------------
# Check for Elevated Privileges
#-------------------------------------------------------------------------------
If (!(Get-IsAdministrator)) {
If (Get-IsUacEnabled) {
# We are not running "as Administrator" - so relaunch as administrator
# Create a new process object that starts PowerShell
$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
# Specify the current script path and name as a parameter
$newProcess.Arguments = $myInvocation.MyCommand.Definition
# Specify the current working directory
$newProcess.WorkingDirectory = "$SCRIPT_DIR"
# Indicate that the process should be elevated
$newProcess.Verb = "runas";
# Start the new process
[System.Diagnostics.Process]::Start($newProcess);
# Exit from the current, unelevated, process
Exit
} Else {
Throw "You must be administrator to run this script"
}
function Write-Result($result, $ForegroundColor="Green") {
$position = 80 - $result.Length - [System.Console]::CursorLeft
Write-Host -ForegroundColor $ForegroundColor ("{0,$position}$result" -f "")
}
#-------------------------------------------------------------------------------
@ -71,79 +49,81 @@ Write-Host $("-" * 80)
#-------------------------------------------------------------------------------
# NSIS
#-------------------------------------------------------------------------------
Write-Host "Looking for NSIS: " -NoNewline
$check_file = "$NSIS_DIR\NSIS.exe"
if ( Test-Path -Path "$check_file" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Missing" -ForegroundColor Yellow
Write-Result "Missing" -ForegroundColor Yellow
Write-Host "Downloading NSIS: " -NoNewline
$url = "$DEPS_URL/nsis-3.03-setup.exe"
$file = "$env:TEMP\install_nsis.exe"
Invoke-WebRequest -Uri $url -OutFile "$file"
if ( Test-Path -Path "$file" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
Write-Host "Installing NSIS: " -NoNewline
Start-Process $file -ArgumentList "/S" -Wait -NoNewWindow
if ( Test-Path -Path "$check_file" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
Write-Host "Cleaning up: " -NoNewline
Remove-Item -Path $file -Force
if ( ! (Test-Path -Path "$file") ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
# Not a hard fail
Write-Host "Failed" -ForegroundColor Yellow
Write-Result "Failed" -ForegroundColor Yellow
}
}
#-------------------------------------------------------------------------------
# NSIS NxS Unzip Plugin
#-------------------------------------------------------------------------------
Write-Host "Looking for NSIS NxS Unzip (ansi) Plugin: " -NoNewline
$check_file = "$NSIS_PLUG_A\nsisunz.dll"
if ( Test-Path -Path $check_file ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Missing" -ForegroundColor Yellow
Write-Result "Missing" -ForegroundColor Yellow
Write-Host "Downloading NSIS NxS Unzip (ansi) Plugin: " -NoNewline
$url = "$DEPS_URL/nsis-plugin-nsisunz.zip"
$file = "$env:TEMP\nsizunz.zip"
Invoke-WebRequest -Uri $url -OutFile "$file"
if ( Test-Path -Path "$file" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
Write-Host "Extracting NSIS NxS Unzip (ansi) Plugin: " -NoNewline
Expand-Archive -Path "$file" -DestinationPath "$env:TEMP"
if ( Test-Path -Path "$env:TEMP\nsisunz\Release\nsisunz.dll") {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
Write-Host "Moving DLL to plugins directory: " -NoNewline
Move-Item -Path "$env:TEMP\nsisunz\Release\nsisunz.dll" -Destination "$check_file" -Force
if ( Test-Path -Path $check_file ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -152,49 +132,49 @@ if ( Test-Path -Path $check_file ) {
Remove-Item -Path "$env:TEMP\nsisunz" -Force -Recurse | Out-Null
if ( Test-Path -Path "$file" ) {
# Not a hard fail
Write-Host "Failed" -ForegroundColor Yellow
Write-Result "Failed" -ForegroundColor Yellow
}
if ( Test-Path -Path "$env:TEMP\nsisunz" ) {
# Not a hard fail
Write-Host "Failed" -ForegroundColor Yellow
Write-Result "Failed" -ForegroundColor Yellow
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
Write-Host "Looking for NSIS NxS Unzip (unicode) Plugin: " -NoNewline
$check_file = "$NSIS_PLUG_U\nsisunz.dll"
if ( Test-Path -Path $check_file ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Missing" -ForegroundColor Yellow
Write-Result "Missing" -ForegroundColor Yellow
Write-Host "Downloading NSIS NxS Unzip (unicode) Plugin: " -NoNewline
$url = "$DEPS_URL/nsis-plugin-nsisunzu.zip"
$file = "$env:TEMP\nsisunzu.zip"
Invoke-WebRequest -Uri $url -OutFile "$file"
if ( Test-Path -Path "$file" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
Write-Host "Extracting NSIS NxS Unzip (unicode) Plugin: " -NoNewline
Expand-Archive -Path "$file" -DestinationPath "$env:TEMP"
if ( Test-Path -Path "$env:TEMP\NSISunzU\Plugin unicode\nsisunz.dll") {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
Write-Host "Moving DLL to plugins directory: " -NoNewline
Move-Item -Path "$env:TEMP\NSISunzU\Plugin unicode\nsisunz.dll" -Destination "$check_file" -Force
if ( Test-Path -Path $check_file ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -203,48 +183,49 @@ if ( Test-Path -Path $check_file ) {
Remove-Item -Path "$env:TEMP\NSISunzU" -Force -Recurse | Out-Null
if ( Test-Path -Path "$file" ) {
# Not a hard fail
Write-Host "Failed" -ForegroundColor Yellow
Write-Result "Failed" -ForegroundColor Yellow
}
if ( Test-Path -Path "$env:TEMP\NSISunzU" ) {
# Not a hard fail
Write-Host "Failed" -ForegroundColor Yellow
Write-Result "Failed" -ForegroundColor Yellow
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
#-------------------------------------------------------------------------------
# NSIS EnVar Plugin
#-------------------------------------------------------------------------------
Write-Host "Looking for NSIS EnVar Plugin: " -NoNewline
$check_file_a = "$NSIS_PLUG_A\EnVar.dll"
$check_file_u = "$NSIS_PLUG_U\EnVar.dll"
if ( (Test-Path -Path $check_file_a) -and (Test-Path -Path $check_file_u) ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Missing" -ForegroundColor Yellow
Write-Result "Missing" -ForegroundColor Yellow
Write-Host "Downloading NSIS EnVar Plugin: " -NoNewline
$url = "$DEPS_URL/nsis-plugin-envar.zip"
$file = "$env:TEMP\nsisenvar.zip"
Invoke-WebRequest -Uri $url -OutFile "$file"
if ( Test-Path -Path "$file" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
Write-Host "Extracting NSIS EnVar Plugin: " -NoNewline
Expand-Archive -Path "$file" -DestinationPath "$env:TEMP\nsisenvar\"
if ( ! (Test-Path -Path "$env:TEMP\nsisenvar\Plugins\x86-ansi\EnVar.dll") ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
if ( Test-Path -Path "$env:TEMP\nsisenvar\Plugins\x86-unicode\EnVar.dll" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -252,13 +233,13 @@ if ( (Test-Path -Path $check_file_a) -and (Test-Path -Path $check_file_u) ) {
Move-Item -Path "$env:TEMP\nsisenvar\Plugins\x86-ansi\EnVar.dll" -Destination "$check_file_a" -Force
Move-Item -Path "$env:TEMP\nsisenvar\Plugins\x86-unicode\EnVar.dll" -Destination "$check_file_u" -Force
if ( ! (Test-Path -Path $check_file_a) ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
if ( Test-Path -Path $check_file_u ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -267,48 +248,49 @@ if ( (Test-Path -Path $check_file_a) -and (Test-Path -Path $check_file_u) ) {
Remove-Item -Path "$env:TEMP\nsisenvar" -Force -Recurse | Out-Null
if ( Test-Path -Path "$file" ) {
# Not a hard fail
Write-Host "Failed" -ForegroundColor Yellow
Write-Result "Failed" -ForegroundColor Yellow
}
if ( Test-Path -Path "$env:TEMP\NSISunzU" ) {
# Not a hard fail
Write-Host "Failed" -ForegroundColor Yellow
Write-Result "Failed" -ForegroundColor Yellow
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
#-------------------------------------------------------------------------------
# NSIS AccessControl Plugin
#-------------------------------------------------------------------------------
Write-Host "Looking for NSIS AccessControl Plugin: " -NoNewline
$check_file_a = "$NSIS_PLUG_A\AccessControl.dll"
$check_file_u = "$NSIS_PLUG_U\AccessControl.dll"
if ( (Test-Path -Path $check_file_a) -and (Test-Path -Path $check_file_u) ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Missing" -ForegroundColor Yellow
Write-Result "Missing" -ForegroundColor Yellow
Write-Host "Downloading NSIS AccessControl Plugin: " -NoNewline
$url = "$DEPS_URL/nsis-plugin-accesscontrol.zip"
$file = "$env:TEMP\nsisaccesscontrol.zip"
Invoke-WebRequest -Uri $url -OutFile "$file"
if ( Test-Path -Path "$file" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
Write-Host "Extracting NSIS EnVar Plugin: " -NoNewline
Expand-Archive -Path "$file" -DestinationPath "$env:TEMP\nsisaccesscontrol\"
if ( ! (Test-Path -Path "$env:TEMP\nsisaccesscontrol\Plugins\i386-ansi\AccessControl.dll") ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
if ( Test-Path -Path "$env:TEMP\nsisaccesscontrol\Plugins\i386-unicode\AccessControl.dll" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -316,13 +298,13 @@ if ( (Test-Path -Path $check_file_a) -and (Test-Path -Path $check_file_u) ) {
Move-Item -Path "$env:TEMP\nsisaccesscontrol\Plugins\i386-ansi\AccessControl.dll" -Destination "$check_file_a" -Force
Move-Item -Path "$env:TEMP\nsisaccesscontrol\Plugins\i386-unicode\AccessControl.dll" -Destination "$check_file_u" -Force
if ( ! (Test-Path -Path $check_file_a) ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
if ( Test-Path -Path $check_file_u ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -331,38 +313,43 @@ if ( (Test-Path -Path $check_file_a) -and (Test-Path -Path $check_file_u) ) {
Remove-Item -Path "$env:TEMP\nsisaccesscontrol" -Force -Recurse | Out-Null
if ( Test-Path -Path "$file" ) {
# Not a hard fail
Write-Host "Failed" -ForegroundColor Yellow
Write-Result "Failed" -ForegroundColor Yellow
}
if ( Test-Path -Path "$env:TEMP\nsisaccesscontrol" ) {
# Not a hard fail
Write-Host "Failed" -ForegroundColor Yellow
Write-Result "Failed" -ForegroundColor Yellow
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
}
#-------------------------------------------------------------------------------
# NSIS MoveFileFolder Library
#-------------------------------------------------------------------------------
Write-Host "Looking for NSIS MoveFileFolder Library: " -NoNewline
$check_file = "$NSIS_LIB_DIR\MoveFileFolder.nsh"
if ( Test-Path -Path $check_file ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Missing" -ForegroundColor Yellow
Write-Result "Missing" -ForegroundColor Yellow
Write-Host "Installing NSIS MoveFileFolder Library: " -NoNewline
$url = "$DEPS_URL/nsis-MoveFileFolder.nsh"
$file = "$check_file"
Invoke-WebRequest -Uri $url -OutFile "$file"
if ( Test-Path -Path "$file" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}
#-------------------------------------------------------------------------------
# Script Finished
#-------------------------------------------------------------------------------
Write-Host $("-" * 80)
Write-Host "Install NullSoft Installer Software and Plugins Completed" `
-ForegroundColor Cyan

View file

@ -14,13 +14,25 @@ install_salt.ps1
#>
#-------------------------------------------------------------------------------
# Script Preferences
#-------------------------------------------------------------------------------
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
$ProgressPreference = "SilentlyContinue"
$ErrorActionPreference = "Stop"
#-------------------------------------------------------------------------------
# Define Variables
# Script Functions
#-------------------------------------------------------------------------------
function Write-Result($result, $ForegroundColor="Green") {
$position = 80 - $result.Length - [System.Console]::CursorLeft
Write-Host -ForegroundColor $ForegroundColor ("{0,$position}$result" -f "")
}
#-------------------------------------------------------------------------------
# Script Variables
#-------------------------------------------------------------------------------
# Python Variables
@ -57,9 +69,9 @@ Write-Host $("-" * 80)
# it is
Write-Host "Checking for existing Salt installation: " -NoNewline
if ( ! (Test-Path -Path "$SCRIPTS_DIR\salt-minion.exe") ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -70,9 +82,9 @@ $remove | ForEach-Object {
Write-Host "Removing $_`:" -NoNewline
Remove-Item -Path "$PROJECT_DIR\$_" -Recurse -Force
if ( ! (Test-Path -Path "$PROJECT_DIR\$_") ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}
@ -87,9 +99,9 @@ Start-Process -FilePath $SCRIPTS_DIR\pip3.exe `
-WorkingDirectory "$PROJECT_DIR" `
-Wait -WindowStyle Hidden
if ( Test-Path -Path "$SCRIPTS_DIR\distro.exe" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -101,9 +113,9 @@ if ( Test-Path -Path "$SCRIPTS_DIR\distro.exe" ) {
Write-Host "Removing wmitest scripts: " -NoNewline
Remove-Item -Path "$SCRIPTS_DIR\wmitest*" -Force | Out-Null
if ( ! (Test-Path -Path "$SCRIPTS_DIR\wmitest*") ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -115,23 +127,24 @@ if ( ! (Test-Path -Path "$SCRIPTS_DIR\wmitest*") ) {
# Move DLL's to Python Root and win32
# The dlls have to be in Python directory and the site-packages\win32 directory
# TODO: Change this to 310... maybe
$dlls = "pythoncom38.dll",
"pywintypes38.dll"
$dlls | ForEach-Object {
Write-Host "Copying $_ to Scripts: " -NoNewline
Copy-Item "$SITE_PKGS_DIR\pywin32_system32\$_" "$SCRIPTS_DIR" -Force | Out-Null
if ( Test-Path -Path "$SCRIPTS_DIR\$_") {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
Write-Host "Moving $_ to win32: " -NoNewline
Move-Item "$SITE_PKGS_DIR\pywin32_system32\$_" "$SITE_PKGS_DIR\win32" -Force | Out-Null
if ( Test-Path -Path "$SITE_PKGS_DIR\win32\$_" ){
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}
@ -140,9 +153,9 @@ $dlls | ForEach-Object {
Write-Host "Removing pywin32_system32 directory: " -NoNewline
Remove-Item -Path "$SITE_PKGS_DIR\pywin32_system32" | Out-Null
if ( ! (Test-Path -Path "$SITE_PKGS_DIR\pywin32_system32") ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -150,9 +163,9 @@ if ( ! (Test-Path -Path "$SITE_PKGS_DIR\pywin32_system32") ) {
Write-Host "Removing pywin32 post-install scripts: " -NoNewline
Remove-Item -Path "$SCRIPTS_DIR\pywin32_*" -Force | Out-Null
if ( ! (Test-Path -Path "$SCRIPTS_DIR\pywin32_*") ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -160,9 +173,9 @@ if ( ! (Test-Path -Path "$SCRIPTS_DIR\pywin32_*") ) {
Write-Host "Creating gen_py directory: " -NoNewline
New-Item -Path "$SITE_PKGS_DIR\win32com\gen_py" -ItemType Directory -Force | Out-Null
if ( Test-Path -Path "$SITE_PKGS_DIR\win32com\gen_py" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -181,9 +194,9 @@ try {
Remove-Item env:\RELENV_PIP_DIR
}
if ( Test-Path -Path "$BUILD_DIR\salt-minion.exe" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -203,9 +216,9 @@ $remove | ForEach-Object {
Write-Host "Removing $_`: " -NoNewline
Remove-Item -Path "$BUILD_DIR\$_*" -Recurse
if ( ! ( Test-Path -Path "$BUILD_DIR\$_*" ) ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}

View file

@ -12,43 +12,21 @@ install_vc_buildtools.ps1
#>
#-------------------------------------------------------------------------------
# Script Preferences
#-------------------------------------------------------------------------------
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
$ProgressPreference = "SilentlyContinue"
$ErrorActionPreference = "Stop"
#-------------------------------------------------------------------------------
# Import Modules
# Script Functions
#-------------------------------------------------------------------------------
$SCRIPT_DIR = (Get-ChildItem "$($myInvocation.MyCommand.Definition)").DirectoryName
Import-Module $SCRIPT_DIR\Modules\uac-module.psm1
#-------------------------------------------------------------------------------
# Check for Elevated Privileges
#-------------------------------------------------------------------------------
If (!(Get-IsAdministrator)) {
If (Get-IsUacEnabled) {
# We are not running "as Administrator" - so relaunch as administrator
# Create a new process object that starts PowerShell
$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
# Specify the current script path and name as a parameter
$newProcess.Arguments = $myInvocation.MyCommand.Definition
# Specify the current working directory
$newProcess.WorkingDirectory = "$SCRIPT_DIR"
# Indicate that the process should be elevated
$newProcess.Verb = "runas";
# Start the new process
[System.Diagnostics.Process]::Start($newProcess);
# Exit from the current, unelevated, process
Exit
} Else {
Throw "You must be administrator to run this script"
}
function Write-Result($result, $ForegroundColor="Green") {
$position = 80 - $result.Length - [System.Console]::CursorLeft
Write-Host -ForegroundColor $ForegroundColor ("{0,$position}$result" -f "")
}
#-------------------------------------------------------------------------------
@ -82,14 +60,14 @@ Write-Host "Confirming Presence of Visual Studio Build Tools: " -NoNewline
}
if ( $install_build_tools ) {
Write-Host "Missing" -ForegroundColor Yellow
Write-Result "Missing" -ForegroundColor Yellow
Write-Host "Checking available disk space: " -NoNewLine
$available = (Get-PSDrive $env:SystemDrive.Trim(":")).Free
if ( $available -gt (1024 * 1024 * 1024 * 9.1) ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
Write-Host "Not enough disk space"
exit 1
}
@ -97,9 +75,9 @@ if ( $install_build_tools ) {
Write-Host "Downloading Visual Studio 2017 build tools: " -NoNewline
Invoke-WebRequest -Uri "$VS_BLD_TOOLS" -OutFile "$env:TEMP\vs_buildtools.exe"
if ( Test-Path -Path "$env:TEMP\vs_buildtools.exe" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -122,9 +100,9 @@ if ( $install_build_tools ) {
"--wait" `
-Wait -WindowStyle Hidden
if ( Test-Path -Path "$env:TEMP\build_tools\vs_buildtools.exe" ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
@ -138,9 +116,9 @@ if ( $install_build_tools ) {
"$($env:TEMP)\build_tools\certificates\manifestCounterSignRootCertificate.cer" `
-Wait -WindowStyle Hidden
if ( Test-Path -Path Cert:\LocalMachine\Root\3b1efd3a66ea28b16697394703a72ca340a05bd5 ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Yellow
Write-Result "Failed" -ForegroundColor Yellow
}
}
@ -154,9 +132,9 @@ if ( $install_build_tools ) {
"$($env:TEMP)\build_tools\certificates\manifestRootCertificate.cer" `
-Wait -WindowStyle Hidden
if ( Test-Path -Path Cert:\LocalMachine\Root\8f43288ad272f3103b6fb1428485ea3014c0bcfe ) {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Failed" -ForegroundColor Yellow
Write-Result "Failed" -ForegroundColor Yellow
}
}
@ -166,18 +144,19 @@ if ( $install_build_tools ) {
-Wait
@($VS_CL_BIN, $MSBUILD_BIN, $WIN10_SDK_RC) | ForEach-Object {
if ( ! (Test-Path -Path $_) ) {
Write-Host "Failed" -ForegroundColor Red
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
} else {
Write-Host "Success" -ForegroundColor Green
Write-Result "Success" -ForegroundColor Green
}
#-------------------------------------------------------------------------------
# Finished
#-------------------------------------------------------------------------------
Write-Host $("-" * 80)
Write-Host "Install Visual Studio Build Tools Completed" -ForegroundColor Cyan
Write-Host $("=" * 80)

View file

@ -0,0 +1,3 @@
@ echo off
Set "CurDir=%~dp0"
PowerShell -ExecutionPolicy RemoteSigned -File "%CurDir%\install_wix.ps1" %*

106
pkg/windows/install_wix.ps1 Normal file
View file

@ -0,0 +1,106 @@
<#
.SYNOPSIS
Script that installs the Wix Toolset
.DESCRIPTION
This script installs the Wix Toolset and it's dependency .Net Framework 3.5
.EXAMPLE
install_wix.ps1
#>
# Script Preferences
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
$ProgressPreference = "SilentlyContinue"
$ErrorActionPreference = "Stop"
#-------------------------------------------------------------------------------
# Script Functions
#-------------------------------------------------------------------------------
function ProductcodeExists($productCode) {
# Verify product code in registry
Test-Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$productCode
}
function Write-Result($result, $ForegroundColor="Green") {
$position = 80 - $result.Length - [System.Console]::CursorLeft
Write-Host -ForegroundColor $ForegroundColor ("{0,$position}$result" -f "")
}
#-------------------------------------------------------------------------------
# Start the Script
#-------------------------------------------------------------------------------
Write-Host $("=" * 80)
Write-Host "Install Wix Toolset" -ForegroundColor Cyan
Write-Host $("-" * 80)
#-------------------------------------------------------------------------------
# .Net Framework 3.5
#-------------------------------------------------------------------------------
Write-Host "Looking for .Net Framework 3.5: " -NoNewline
if ( (Get-WindowsOptionalFeature -Online -FeatureName "NetFx3").State -eq "Enabled" ) {
Write-Result "Success" -ForegroundColor Green
} else {
Write-Result "Missing" -ForegroundColor Yellow
Write-Host "Installing .Net Framework 3.5: " -NoNewline
Dism /online /enable-feature /featurename:NetFx3 /all
if ( (Get-WindowsOptionalFeature -Online -FeatureName "NetFx3").State -eq "Enabled" ) {
Write-Result "Success" -ForegroundColor Green
} else {
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}
#-------------------------------------------------------------------------------
# Wix Toolset
#-------------------------------------------------------------------------------
Write-Host "Looking for Wix Toolset: " -NoNewline
# 64bit: {03368010-193D-4AE2-B275-DD2EB32CD427}
# 32bit: {07188017-A460-4C0D-A386-6B3CEB8E20CD}
if ((ProductcodeExists "{03368010-193D-4AE2-B275-DD2EB32CD427}") `
-or `
(ProductcodeExists "{07188017-A460-4C0D-A386-6B3CEB8E20CD}")) {
Write-Result "Success" -ForegroundColor Green
} else {
Write-Result "Missing" -ForegroundColor Yellow
Write-Host "Downloading Wix Toolset: " -NoNewline
$url = "https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311.exe"
$file = "$env:TEMP\wix_installer.exe"
Invoke-WebRequest -Uri $url -OutFile "$file"
if ( Test-Path -Path "$file" ) {
Write-Result "Success" -ForegroundColor Green
} else {
Write-Result "Failed" -ForegroundColor Red
exit 1
}
Write-Host "Installing Wix Toolset: " -NoNewline
Start-Process $file -ArgumentList "/install","/quiet","/norestart" -Wait -NoNewWindow
if ((ProductcodeExists "{03368010-193D-4AE2-B275-DD2EB32CD427}") `
-or `
(ProductcodeExists "{07188017-A460-4C0D-A386-6B3CEB8E20CD}")) {
Write-Result "Success" -ForegroundColor Green
} else {
Write-Result "Failed" -ForegroundColor Red
exit 1
}
Write-Host "Cleaning up: " -NoNewline
Remove-Item -Path $file -Force
if ( ! (Test-Path -Path "$file") ) {
Write-Result "Success" -ForegroundColor Green
} else {
# Not a hard fail
Write-Result "Failed" -ForegroundColor Yellow
}
}
Write-Host $("-" * 80)
Write-Host "Install Wix Toolset Completed" -ForegroundColor Cyan
Write-Host $("=" * 80)

View file

@ -1,12 +0,0 @@
function Get-IsAdministrator {
# Detect if the user is Administrator
$Identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$Principal = New-Object System.Security.Principal.WindowsPrincipal($Identity)
$Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Get-IsUacEnabled {
# Detect if UAC is enabled
(Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System).EnableLua -ne 0
}

View file

@ -1,33 +0,0 @@
Function Expand-ZipFile {
# A PowerShell 3.0 compatible function for unzipping files
Param(
[Parameter(Mandatory=$true)]
[string] $zipfile,
[Parameter(Mandatory=$true)]
[string] $destination
)
Begin { Write-Host " - Unzipping '$zipfile' to '$destination'" }
Process {
# Create a new directory if it doesn't exist
If (!(Test-Path -Path $destination)) {
New-Item -ItemType directory -Path $destination
}
# Define Objects
$objShell = New-Object -Com Shell.Application
# Open the zip file
$objZip = $objShell.NameSpace($zipfile)
# Unzip each item in the zip file
ForEach ($item in $objZip.Items()) {
$objShell.Namespace($destination).CopyHere($item, 0x14)
}
}
End { Write-Host " - Finished"}
}

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<!--
Use supportedRuntime tags to explicitly specify the version(s) of the .NET Framework runtime that
the custom action should run on. If no versions are specified, the chosen version of the runtime
will be the "best" match to what Microsoft.Deployment.WindowsInstaller.dll was built against.
WARNING: leaving the version unspecified is dangerous as it introduces a risk of compatibility
problems with future versions of the .NET Framework runtime. It is highly recommended that you specify
only the version(s) of the .NET Framework runtime that you have tested against.
Note for .NET Framework v3.0 and v3.5, the runtime version is still v2.0.
In order to enable .NET Framework version 2.0 runtime activation policy, which is to load all assemblies
by using the latest supported runtime, @useLegacyV2RuntimeActivationPolicy="true".
For more information, see http://msdn.microsoft.com/en-us/library/bbx34a2h.aspx
-->
<supportedRuntime version="v4.0" />
<supportedRuntime version="v2.0.50727"/>
</startup>
<!--
Add additional configuration settings here. For more information on application config files,
see http://msdn.microsoft.com/en-us/library/kza1yk3a.aspx
-->
</configuration>

View file

@ -0,0 +1,779 @@
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Tools.WindowsInstallerXml;
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.IO;
using System.Management; // Reference C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Management.dll
using System.Security.AccessControl;
using System.Security.Principal;
using System.ServiceProcess;
using System.Text.RegularExpressions;
using System.Collections.Generic;
namespace MinionConfigurationExtension {
public class MinionConfiguration : WixExtension {
[CustomAction]
public static ActionResult ReadConfig_IMCAC(Session session) {
/*
When the installation begins, there may be a previous installation with existing config.
If existing config is found, we need to verify that it is in a secure state. If it is
secure then it will be used as is, unchanged.
We will read the values from existing config to possibly display in the GUI, but it will
be for informational purposes only. The only CONFIG_TYPES that will be edited are
DEFAULT and CUSTOM.
The two config options and their defaults are:
- master: salt
- id: hostname
If the CONFIG_TYPE is not "Existing", and the master and minion id are not the defaults,
then those values will be used to update either the Default config or a Custom config.
This function writes msi properties:
- MASTER
- MINION_ID
- CONFIG_TYPE
A GUI installation can show these msi properties because this function is called before the GUI.
*/
session.Log("...BEGIN ReadConfig_IMCAC");
string ProgramData = System.Environment.GetEnvironmentVariable("ProgramData");
string oldRootDir = @"C:\salt";
string newRootDir = Path.Combine(ProgramData, @"Salt Project\Salt");
// Create msi proporties
session["ROOTDIR_old"] = oldRootDir;
session["ROOTDIR_new"] = newRootDir;
string abortReason = "";
// Insert the first abort reason here
if (abortReason.Length > 0) {
session["AbortReason"] = abortReason;
}
session.Log("...Looking for existing config");
string REGISTRY_ROOTDIR = session["EXISTING_ROOTDIR"]; // From registry
string reg_config = "";
if (REGISTRY_ROOTDIR.Length > 0){
reg_config = REGISTRY_ROOTDIR + @"\conf\minion";
}
// Search for configuration in this order: registry, new layout, old layout
string minion_config_file = cutil.get_file_that_exist(session, new string[] {
reg_config,
newRootDir + @"\conf\minion",
oldRootDir + @"\conf\minion"});
string minion_config_dir = "";
// Check for a minion.d directory
if (File.Exists(minion_config_file)) {
string minion_dot_d_dir = minion_config_file + ".d";
session.Log("...minion_dot_d_dir = " + minion_dot_d_dir);
if (Directory.Exists(minion_dot_d_dir)) {
session.Log("... folder exists minion_dot_d_dir = " + minion_dot_d_dir);
DirectorySecurity dirSecurity = Directory.GetAccessControl(minion_dot_d_dir);
IdentityReference sid = dirSecurity.GetOwner(typeof(SecurityIdentifier));
session.Log("...owner of the minion config dir " + sid.Value);
} else {
session.Log("... folder does not exist: " + minion_dot_d_dir);
}
}
// Check for existing config
if (File.Exists(minion_config_file)) {
// We found an existing config
session["CONFIG_TYPE"] = "Existing";
// Make sure the directory where the config was found is secure
minion_config_dir = Path.GetDirectoryName(minion_config_file);
// Owner must be one of "Local System" or "Administrators"
// It looks like the NullSoft installer sets the owner to
// Administrators while the MSI installer sets the owner to
// Local System. Salt only sets the owner of the `C:\salt`
// directory when it starts and doesn't concern itself with the
// conf directory. So we have to check for both.
List<string> valid_sids = new List<string>();
valid_sids.Add("S-1-5-18"); //Local System
valid_sids.Add("S-1-5-32-544"); //Administrators
// Get the SID for the owner of the conf directory
FileSecurity fileSecurity = File.GetAccessControl(minion_config_dir);
IdentityReference sid = fileSecurity.GetOwner(typeof(SecurityIdentifier));
session.Log("...owner of the minion config file " + sid.Value);
// Check to see if it's in the list of valid SIDs
if (!valid_sids.Contains(sid.Value)) {
// If it's not in the list we don't want to use it. Do the following:
// - set INSECURE_CONFIG_FOUND to the insecure config dir
// - set CONFIG_TYPE to Default
session.Log("...Insecure config found, using default config");
session["INSECURE_CONFIG_FOUND"] = minion_config_dir;
session["CONFIG_TYPE"] = "Default";
session["GET_CONFIG_TEMPLATE_FROM_MSI_STORE"] = "True"; // Use template instead
}
} else {
session["GET_CONFIG_TEMPLATE_FROM_MSI_STORE"] = "True"; // Use template
}
// Set the default values for master and id
String master_from_previous_installation = "";
String id_from_previous_installation = "";
// Read master and id from main config file (if such a file exists)
if (minion_config_file.Length > 0) {
read_master_and_id_from_file_IMCAC(session, minion_config_file, ref master_from_previous_installation, ref id_from_previous_installation);
}
// Read master and id from minion.d/*.conf (if they exist)
if (Directory.Exists(minion_config_dir)) {
var conf_files = System.IO.Directory.GetFiles(minion_config_dir, "*.conf");
foreach (var conf_file in conf_files) {
if (conf_file.Equals("_schedule.conf")) { continue; } // skip _schedule.conf
read_master_and_id_from_file_IMCAC(session, conf_file, ref master_from_previous_installation, ref id_from_previous_installation);
}
}
if (session["MASTER"] == "") {
session["MASTER"] = "salt";
}
if (session["MINION_ID"] == "") {
session["MINION_ID"] = "hostname";
}
session.Log("...CONFIG_TYPE msi property = " + session["CONFIG_TYPE"]);
session.Log("...MASTER msi property = " + session["MASTER"]);
session.Log("...MINION_ID msi property = " + session["MINION_ID"]);
// A list of config types that will be edited. Existing config will NOT be edited
List<string> editable_types = new List<string>();
editable_types.Add("Default");
editable_types.Add("Custom");
if (editable_types.Contains(session["CONFIG_TYPE"])) {
// master
if (master_from_previous_installation != "") {
session.Log("...MASTER kept config =" + master_from_previous_installation);
session["MASTER"] = master_from_previous_installation;
session["CONFIG_FOUND"] = "True";
session.Log("...MASTER set to kept config");
}
// minion id
if (id_from_previous_installation != "") {
session.Log("...MINION_ID kept config =" + id_from_previous_installation);
session.Log("...MINION_ID set to kept config ");
session["MINION_ID"] = id_from_previous_installation;
}
}
// Save the salt-master public key
// This assumes the install is silent.
// Saving should only occur in WriteConfig_DECAC,
// IMCAC is easier and no harm because there is no public master key in the installer.
string MASTER_KEY = cutil.get_property_IMCAC(session, "MASTER_KEY");
string ROOTDIR = cutil.get_property_IMCAC(session, "ROOTDIR");
string pki_minion_dir = Path.Combine(ROOTDIR, @"conf\minion.d\pki\minion");
var master_key_file = Path.Combine(pki_minion_dir, "minion_master.pub");
session.Log("...master_key_file = " + master_key_file);
bool MASTER_KEY_set = MASTER_KEY != "";
session.Log("...master key earlier config file exists = " + File.Exists(master_key_file));
session.Log("...master key msi property given = " + MASTER_KEY_set);
if (MASTER_KEY_set) {
String master_key_lines = ""; // Newline after 64 characters
int count_characters = 0;
foreach (char character in MASTER_KEY) {
master_key_lines += character;
count_characters += 1;
if (count_characters % 64 == 0) {
master_key_lines += Environment.NewLine;
}
}
string new_master_pub_key =
"-----BEGIN PUBLIC KEY-----" + Environment.NewLine +
master_key_lines + Environment.NewLine +
"-----END PUBLIC KEY-----";
if (!Directory.Exists(pki_minion_dir)) {
// The <Directory> declaration in Product.wxs does not create the folders
Directory.CreateDirectory(pki_minion_dir);
}
File.WriteAllText(master_key_file, new_master_pub_key);
}
session.Log("...END ReadConfig_IMCAC");
return ActionResult.Success;
}
private static void write_master_and_id_to_file_DECAC(Session session, String config_file, string csv_multimasters, String id) {
/* How to
* read line
* if line master, read multimaster, replace
* if line id, replace
* copy through line
*/
session.Log("...BEGIN write_master_and_id_to_file_DECAC");
session.Log("...want to write master and id to " + config_file);
session.Log("......master: " + csv_multimasters);
session.Log("......id: " + id);
if (File.Exists(config_file)) {
session.Log("...config_file exists: " + config_file);
} else {
session.Log("......ERROR: no config file found: {0}", config_file);
return;
}
// Load current config
string config_content = File.ReadAllText(config_file);
// Only attempt to replace master if master value is passed
// If master value is not passed, the default is "salt"
if (csv_multimasters != "salt") {
// Let's see if we have multiple masters
char[] separators = new char[] { ',', ' ' };
string[] multimasters = csv_multimasters.Split(separators, StringSplitOptions.RemoveEmptyEntries);
string masters = string.Join(Environment.NewLine, multimasters);
string master_value = "";
if (multimasters.Length > 1) {
// Multimaster
master_value = "master:";
foreach (string master in multimasters) {
master_value += Environment.NewLine + "- " + master;
}
master_value = master_value.Trim() + Environment.NewLine;
} else {
// Single Master
master_value = "master: " + masters.Trim() + Environment.NewLine;
}
session.Log("...New Master Value: {0}", master_value);
bool master_emitted = false;
// Single master entry
Regex regx_single_master = new Regex(@"(^master:[ \t]+\S+\r?\n?)", RegexOptions.Multiline);
// Search config using single master matcher
session.Log("...Searching for single_master");
session.Log(config_content);
MatchCollection master_matches = regx_single_master.Matches(config_content);
// If one is found, replace with the new master value and done
if (master_matches.Count == 1) {
session.Log("......Found single master, setting new master value");
config_content = regx_single_master.Replace(config_content, master_value);
master_emitted = true;
} else if (master_matches.Count > 1) {
session.Log("......ERROR Found multiple matches for single master");
}
if (!master_emitted) {
// Multimaster entry
Regex regx_multi_master = new Regex(@"(^master: *(?:\r?\n?- +.*\r?\n?)+\r?\n?)", RegexOptions.Multiline);
// Search config using multi master matcher
session.Log("...Searching for multi master");
master_matches = regx_multi_master.Matches(config_content);
// If one is found, replace with the new master value and done
if (master_matches.Count == 1) {
session.Log("......Found multi master, setting new master value");
config_content = regx_multi_master.Replace(config_content, master_value);
master_emitted = true;
} else if (master_matches.Count > 1) {
session.Log("......ERROR Found multiple matches for multi master");
}
}
if (!master_emitted) {
// Commented master entry
Regex regx_commented_master = new Regex(@"(^# *master: *\S+\r?\n?)", RegexOptions.Multiline);
// Search config using commented master matcher
session.Log("...Searching for commented master");
master_matches = regx_commented_master.Matches(config_content);
// If one is found, replace with the new master value and done
if (master_matches.Count == 1) {
session.Log("......Found commented master, setting new master value");
// This one's a little different, we want to keep the comment
// and add the new master on the next line
config_content = regx_commented_master.Replace(config_content, "$1" + master_value);
master_emitted = true;
} else if (master_matches.Count > 1) {
session.Log("......ERROR Found multiple matches for single master");
}
}
if (!master_emitted) {
// Commented multi master entry
Regex regx_commented_multi_master = new Regex(@"(^# *master: *(?:\r?\n?# *- +.+\r?\n?)+)", RegexOptions.Multiline);
// Search config using commented multi master matcher
session.Log("...Searching for commented multi master");
master_matches = regx_commented_multi_master.Matches(config_content);
// If one is found, replace with the new master value and done
if (master_matches.Count == 1) {
session.Log("......Found commented multi master, setting new master value");
// This one's a little different, we want to keep the comment
// and add the new master on the next line
config_content = regx_commented_multi_master.Replace(config_content, "$1" + master_value);
master_emitted = true;
} else if (master_matches.Count > 1) {
session.Log("......ERROR Found multiple matches for single master");
}
}
if (!master_emitted) {
session.Log("......No master found in config, appending master");
config_content = config_content + master_value;
master_emitted = true;
}
}
// Only attempt to replace the minion id if a minion id is passed
// If the minion id is not passed, the default is "hostname"
if (id != "hostname") {
string id_value = "id: " + id + Environment.NewLine;
bool id_emitted = false;
// id entry
Regex regx_id = new Regex(@"(^id:[ \t]+\S+\r?\n?)", RegexOptions.Multiline);
// Search config using id matcher
session.Log("...Searching for id");
MatchCollection id_matches = regx_id.Matches(config_content);
// If one is found, replace with the new id value and done
if (id_matches.Count == 1) {
session.Log("......Found id, setting new id value");
config_content = regx_id.Replace(config_content, id_value);
id_emitted = true;
} else if (id_matches.Count > 1) {
session.Log("......ERROR Found multiple matches for id");
}
if (!id_emitted) {
// commented id entry
Regex regx_commented_id = new Regex(@"(^# *id: *\S+\r?\n?)", RegexOptions.Multiline);
// Search config using commented id matcher
session.Log("...Searching for commented id");
id_matches = regx_commented_id.Matches(config_content);
// If one is found, replace with the new id value and done
if (id_matches.Count == 1) {
session.Log("......Found commented id, setting new id value");
config_content = regx_commented_id.Replace(config_content, "$1" + id_value);
id_emitted = true;
} else if (id_matches.Count > 1) {
session.Log("......ERROR Found multiple matches for commented id");
}
}
if (!id_emitted) {
// commented id entry
Regex regx_commented_id_empty = new Regex(@"(^# *id: *\r?\n?)", RegexOptions.Multiline);
// Search config using commented id matcher
session.Log("...Searching for commented id");
id_matches = regx_commented_id_empty.Matches(config_content);
// If one is found, replace with the new id value and done
if (id_matches.Count == 1) {
session.Log("......Found commented id, setting new id value");
config_content = regx_commented_id_empty.Replace(config_content, "$1" + id_value);
id_emitted = true;
} else if (id_matches.Count > 1) {
session.Log("......ERROR Found multiple matches for commented id");
}
}
if (!id_emitted) {
session.Log("......No minion ID found in config, appending minion ID");
config_content = config_content + id_value;
id_emitted = true;
}
}
session.Log("...Writing config content to: {0}", config_file);
File.WriteAllText(config_file, config_content);
session.Log("...END write_master_and_id_to_file_DECAC");
}
private static void read_master_and_id_from_file_IMCAC(Session session, String configfile, ref String ref_master, ref String ref_id) {
/* How to match multimasters *
match `master: `MASTER*:
if MASTER:
master = MASTER
else, a list of masters may follow:
while match `- ` MASTER:
master += MASTER
*/
if (configfile.Length == 0) {
session.Log("...configfile not passed");
return;
}
if (!File.Exists(configfile)) {
session.Log("...configfile does not exist: " + configfile);
return;
}
session.Log("...searching master and id in " + configfile);
bool configExists = File.Exists(configfile);
session.Log("......file exists " + configExists);
if (!configExists) { return; }
string[] configLines = File.ReadAllLines(configfile);
Regex line_key_maybe_value = new Regex(@"^([a-zA-Z_]+):\s*([0-9a-zA-Z_.-]*)\s*$");
Regex line_listvalue = new Regex(@"^\s*-\s*(.*)$");
bool look_for_keys_otherwise_look_for_multimasters = true;
List<string> multimasters = new List<string>();
foreach (string line in configLines) {
if (look_for_keys_otherwise_look_for_multimasters && line_key_maybe_value.IsMatch(line)) {
Match m = line_key_maybe_value.Match(line);
string key = m.Groups[1].ToString();
string maybe_value = m.Groups[2].ToString();
//session.Log("...ANY KEY " + key + " " + maybe_value);
if (key == "master") {
if (maybe_value.Length > 0) {
ref_master = maybe_value;
session.Log("......master " + ref_master);
} else {
session.Log("...... now searching multimasters");
look_for_keys_otherwise_look_for_multimasters = false;
}
}
if (key == "id" && maybe_value.Length > 0) {
ref_id = maybe_value;
session.Log("......id " + ref_id);
}
} else if (line_listvalue.IsMatch(line)) {
Match m = line_listvalue.Match(line);
multimasters.Add(m.Groups[1].ToString());
} else {
look_for_keys_otherwise_look_for_multimasters = true;
}
}
if (multimasters.Count > 0) {
ref_master = string.Join(",", multimasters.ToArray());
session.Log("......master " + ref_master);
}
}
[CustomAction]
public static void stop_service(Session session, string a_service) {
// the installer cannot assess the log file unless it is released.
session.Log("...stop_service " + a_service);
ServiceController service = new ServiceController(a_service);
service.Stop();
var timeout = new TimeSpan(0, 0, 1); // seconds
service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
}
[CustomAction]
public static ActionResult kill_python_exe(Session session) {
// because a running process can prevent removal of files
// Get full path and command line from running process
// see https://github.com/saltstack/salt/issues/42862
session.Log("...BEGIN kill_python_exe");
using (var wmi_searcher = new ManagementObjectSearcher
("SELECT ProcessID, ExecutablePath, CommandLine FROM Win32_Process WHERE Name = 'python.exe'")) {
foreach (ManagementObject wmi_obj in wmi_searcher.Get()) {
try {
String ProcessID = wmi_obj["ProcessID"].ToString();
Int32 pid = Int32.Parse(ProcessID);
String ExecutablePath = wmi_obj["ExecutablePath"].ToString();
String CommandLine = wmi_obj["CommandLine"].ToString();
if (CommandLine.ToLower().Contains("salt") || ExecutablePath.ToLower().Contains("salt")) {
session.Log("...kill_python_exe " + ExecutablePath + " " + CommandLine);
Process proc11 = Process.GetProcessById(pid);
proc11.Kill();
}
} catch (Exception) {
// ignore wmiresults without these properties
}
}
}
session.Log("...END kill_python_exe");
return ActionResult.Success;
}
[CustomAction]
public static ActionResult del_NSIS_DECAC(Session session) {
// Leaves the Config
/*
* If NSIS is installed:
* remove salt-minion service,
* remove registry
* remove files, except /salt/conf and /salt/var
*
* The msi cannot use uninst.exe because the service would no longer start.
*/
session.Log("...BEGIN del_NSIS_DECAC");
RegistryKey HKLM = Registry.LocalMachine;
string ARPstring = @"Microsoft\Windows\CurrentVersion\Uninstall\Salt Minion";
RegistryKey ARPreg = cutil.get_registry_SOFTWARE_key(session, ARPstring);
string uninstexe = "";
if (ARPreg != null) uninstexe = ARPreg.GetValue("UninstallString").ToString();
session.Log("from REGISTRY uninstexe = " + uninstexe);
string SOFTWAREstring = @"Salt Project\Salt";
RegistryKey SOFTWAREreg = cutil.get_registry_SOFTWARE_key(session, SOFTWAREstring);
var bin_dir = "";
if (SOFTWAREreg != null) bin_dir = SOFTWAREreg.GetValue("bin_dir").ToString();
session.Log("from REGISTRY bin_dir = " + bin_dir);
if (bin_dir == "") bin_dir = @"C:\salt\bin";
session.Log("bin_dir = " + bin_dir);
session.Log("Going to stop service salt-minion ...");
cutil.shellout(session, "sc stop salt-minion");
session.Log("Going to delete service salt-minion ...");
cutil.shellout(session, "sc delete salt-minion");
session.Log("Going to kill ...");
kill_python_exe(session);
session.Log("Going to delete ARP registry entry ...");
cutil.del_registry_SOFTWARE_key(session, ARPstring);
session.Log("Going to delete SOFTWARE registry entry ...");
cutil.del_registry_SOFTWARE_key(session, SOFTWAREstring);
session.Log("Going to delete uninst.exe ...");
cutil.del_file(session, uninstexe);
// This deletes any file that starts with "salt" from the install_dir
var bindirparent = Path.GetDirectoryName(bin_dir);
session.Log(@"Going to delete bindir\..\salt\*.* ... " + bindirparent);
if (Directory.Exists(bindirparent)){
try { foreach (FileInfo fi in new DirectoryInfo(bindirparent).GetFiles("salt*.*")) { fi.Delete(); } } catch (Exception) {; }
}
// This deletes the bin directory
session.Log("Going to delete bindir ... " + bin_dir);
cutil.del_dir(session, bin_dir);
session.Log("...END del_NSIS_DECAC");
return ActionResult.Success;
}
[CustomAction]
public static ActionResult WriteConfig_DECAC(Session session) {
/*
* This function must leave the config files according to the CONFIG_TYPE's 1-3
* This function is deferred (_DECAC)
* This function runs after the msi has created the c:\salt\conf\minion file, which is a comment-only text.
* If there was a previous install, there could be many config files.
* The previous install c:\salt\conf\minion file could contain non-comments.
* One of the non-comments could be master.
* It could be that this installer has a different master.
*
*/
// Must have this signature or cannot uninstall not even write to the log
session.Log("...BEGIN WriteConfig_DECAC");
// Get msi properties
string master = cutil.get_property_DECAC(session, "master");;
string id = cutil.get_property_DECAC(session, "id");;
string config_type = cutil.get_property_DECAC(session, "config_type");
string MINION_CONFIG = cutil.get_property_DECAC(session, "MINION_CONFIG");
string CONFDIR = cutil.get_property_DECAC(session, "CONFDIR");
string MINION_CONFIGFILE = Path.Combine(CONFDIR, "minion");
session.Log("...MINION_CONFIGFILE {0}", MINION_CONFIGFILE);
bool file_exists = File.Exists(MINION_CONFIGFILE);
session.Log("...file exists {0}", file_exists);
// Get environment variables
string ProgramData = System.Environment.GetEnvironmentVariable("ProgramData");
if (MINION_CONFIG.Length > 0) {
session.Log("...Found MINION_CONFIG: {0}", MINION_CONFIG);
apply_minion_config_DECAC(session, MINION_CONFIG); // A single msi property is written to file
session.Log("...END WriteConfig_DECAC");
return ActionResult.Success;
}
switch (config_type) {
case "Existing":
session.Log("...CONFIG_TYPE: Existing, no changes will be made");
return ActionResult.Success;
case "Custom":
// copy custom file before updating master and minion id
session.Log("...CONFIG_TYPE: Custom, copying custom config");
save_custom_config_file_DECAC(session);
break;
case "Default":
// This is just a placeholder for CONFIG_TYPE=Default
session.Log("...CONFIG_TYPE: Default, using default config");
break;
default:
session.Log("...UNKNOWN CONFIG_TYPE: " + config_type);
// Not sure if this is a valid ActionResult, but we need to die here
return ActionResult.Failure;
}
write_master_and_id_to_file_DECAC(session, MINION_CONFIGFILE, master, id); // Two msi properties are replaced inside files
session.Log("...END WriteConfig_DECAC");
return ActionResult.Success;
}
[CustomAction]
public static ActionResult MoveInsecureConfig_DECAC(Session session) {
// This appends .insecure-yyyy-MM-ddTHH-mm-ss to an insecure config directory
// C:\salt\conf.insecure-2021-10-01T12-23-32
// Only called when INSECURE_CONFIG_FOUND is set to an insecure minion config dir
session.Log("...BEGIN MoveInsecureConf_DECAC");
string minion_config_dir = cutil.get_property_DECAC(session, "INSECURE_CONFIG_FOUND");
string timestamp_bak = ".insecure-" + DateTime.Now.ToString("yyyy-MM-ddTHH-mm-ss");
cutil.Move_dir(session, minion_config_dir, timestamp_bak);
session.Log("...END MoveInsecureConf_DECAC");
return ActionResult.Success;
}
private static void save_custom_config_file_DECAC(Session session) {
session.Log("...BEGIN save_custom_config_file_DECAC");
string custom_config = cutil.get_property_DECAC(session, "custom_config");
string CONFDIR = cutil.get_property_DECAC(session, "CONFDIR");
// Make sure a CUSTOM_CONFIG file has been passed
if (!(custom_config.Length > 0 )) {
session.Log("...CUSTOM_CONFIG not passed");
return;
}
// Make sure the CUSTOM_CONFIG file exists
// Try as passed
if (File.Exists(custom_config)) {
session.Log("...found full path to CUSTOM_CONFIG: " + custom_config);
} else {
// try relative path
session.Log("...no CUSTOM_CONFIG: " + custom_config);
session.Log("...Try relative path");
string directory_of_the_msi = cutil.get_property_DECAC(session, "sourcedir");
custom_config = Path.Combine(directory_of_the_msi, custom_config);
if (File.Exists(custom_config)) {
session.Log("...found relative path to CUSTOM_CONFIG: " + custom_config);
} else {
// CUSTOM_CONFIG not found
session.Log("...CUSTOM_CONFIG not found: " + custom_config);
return;
}
}
// Copy the custom config (passed via the CLI, for now)
if (!File.Exists(CONFDIR)) {
session.Log("...Creating CONFDIR: " + CONFDIR);
Directory.CreateDirectory(CONFDIR);
}
File.Copy(custom_config, Path.Combine(CONFDIR, "minion"), true);
session.Log("...END save_custom_config_file_DECAC");
}
[CustomAction]
public static ActionResult DeleteConfig_DECAC(Session session) {
// This removes not only config, but ROOTDIR or subfolders of ROOTDIR, depending on properties CLEAN_INSTALL and REMOVE_CONFIG
// Called on install, upgrade and uninstall
session.Log("...BEGIN DeleteConfig_DECAC");
// Determine wether to delete everything and DIRS
string CLEAN_INSTALL = cutil.get_property_DECAC(session, "CLEAN_INSTALL");
string REMOVE_CONFIG = cutil.get_property_DECAC(session, "REMOVE_CONFIG");
string INSTALLDIR = cutil.get_property_DECAC(session, "INSTALLDIR");
string bindir = Path.Combine(INSTALLDIR, "bin");
string ROOTDIR = cutil.get_property_DECAC(session, "ROOTDIR");
string ProgramData = System.Environment.GetEnvironmentVariable("ProgramData");
string ROOTDIR_old = @"C:\salt";
string ROOTDIR_new = Path.Combine(ProgramData, @"Salt Project\Salt");
// The registry subkey deletes itself
if (CLEAN_INSTALL.Length > 0) {
session.Log("...CLEAN_INSTALL -- remove both old and new root_dirs");
cutil.del_dir(session, ROOTDIR_old);
cutil.del_dir(session, ROOTDIR_new);
}
session.Log("...deleting bindir (msi only deletes what it installed, not *.pyc) = " + bindir);
cutil.del_dir(session, bindir);
if (REMOVE_CONFIG.Length > 0) {
session.Log("...REMOVE_CONFIG -- remove the current root_dir");
cutil.del_dir(session, ROOTDIR);
} else {
session.Log("...Not REMOVE_CONFIG -- remove var and srv from the current root_dir");
cutil.del_dir(session, ROOTDIR, "var");
cutil.del_dir(session, ROOTDIR, "srv");
}
session.Log("...END DeleteConfig_DECAC");
return ActionResult.Success;
}
[CustomAction]
public static ActionResult MoveConfig_DECAC(Session session) {
// This moves the root_dir from the old location (C:\salt) to the
// new location (%ProgramData%\Salt Project\Salt)
session.Log("...BEGIN MoveConfig_DECAC");
// Get %ProgramData%
string ProgramData = System.Environment.GetEnvironmentVariable("ProgramData");
string RootDirOld = @"C:\salt";
string RootDirNew = Path.Combine(ProgramData, @"Salt Project\Salt");
string RootDirNewParent = Path.Combine(ProgramData, @"Salt Project");
session.Log("...RootDirOld " + RootDirOld + " exists: " + Directory.Exists(RootDirOld));
session.Log("...RootDirNew " + RootDirNew + " exists: " + Directory.Exists(RootDirNew));
session.Log("...RootDirNewParent " + RootDirNewParent + " exists: " + Directory.Exists(RootDirNewParent));
// Create parent dir if it doesn't exist
if (! Directory.Exists(RootDirNewParent)) {
Directory.CreateDirectory(RootDirNewParent);
}
// Requires that the parent directory exists
// Requires that the NewDir does NOT exist
Directory.Move(RootDirOld, RootDirNew);
session.Log("...END MoveConfig_DECAC");
return ActionResult.Success;
}
private static void apply_minion_config_DECAC(Session session, string MINION_CONFIG) {
// Precondition: parameter MINION_CONFIG contains the content of the MINION_CONFIG property and is not empty
// Remove all other config
session.Log("...apply_minion_config_DECAC BEGIN");
string CONFDIR = cutil.get_property_DECAC(session, "CONFDIR");
string MINION_D_DIR = Path.Combine(CONFDIR, "minion.d");
// Write conf/minion
string lines = MINION_CONFIG.Replace("^", Environment.NewLine);
cutil.Writeln_file(session, CONFDIR, "minion", lines);
// Remove conf/minion_id
string minion_id = Path.Combine(CONFDIR, "minion_id");
session.Log("...searching " + minion_id);
if (File.Exists(minion_id)) {
File.Delete(minion_id);
session.Log("...deleted " + minion_id);
}
// Remove conf/minion.d/*.conf
session.Log("...searching *.conf in " + MINION_D_DIR);
if (Directory.Exists(MINION_D_DIR)) {
var conf_files = System.IO.Directory.GetFiles(MINION_D_DIR, "*.conf");
foreach (var conf_file in conf_files) {
File.Delete(conf_file);
session.Log("...deleted " + conf_file);
}
}
session.Log(@"...apply_minion_config_DECAC END");
}
[CustomAction]
public static ActionResult BackupConfig_DECAC(Session session) {
session.Log("...BackupConfig_DECAC BEGIN");
string timestamp_bak = "-" + DateTime.Now.ToString("yyyy-MM-ddTHH-mm-ss") + ".bak";
session.Log("...timestamp_bak = " + timestamp_bak);
cutil.Move_file(session, @"C:\salt\conf\minion", timestamp_bak);
cutil.Move_file(session, @"C:\salt\conf\minion_id", timestamp_bak);
cutil.Move_dir(session, @"C:\salt\conf\minion.d", timestamp_bak);
session.Log("...BackupConfig_DECAC END");
return ActionResult.Success;
}
}
}

View file

@ -0,0 +1,68 @@
*******************************
*******************************
*******************************
*******************************
* 2016-11.15 mkr
If I set TargetFrameworkVersion to v4.0, in order to access the 32bit registry from 64bit Windows
0) The code
static RegistryKey wrGetKey(string k, bool sw32) {
return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, sw32 ? RegistryView.Registry32 : RegistryView.Registry64).OpenSubKey(k);
}
1) I get a warning that make no sense
C:\windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(983,5): warning MSB3644: The reference assemblies for framework ".
NETFramework,Version=v4.0" were not found. To resolve this, install the SDK or Targeting Pack for this framework version or retarget your a
pplication to a version of the framework for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from
the Global Assembly Cache (GAC) and will be used in place of reference assemblies. Therefore your assembly may not be correctly targeted f
or the framework you intend. [C:\git\salt-windows-msi\wix\MinionConfigurationExtension\MinionConfigurationExtension.csproj]
whereas the log contains
SFXCA: Binding to CLR version v4.0.30319
2) This program finds the 32 bit NSIS in the 64 bit registry.
This is no good.
I postpone to understand this and do not change TargetFrameworkVersion (leaving it at v2.0).
*******************************
*******************************
*******************************
*******************************
Archive for the attempt to read settings from conf/minion into a ini file.
Idea was
1) read simple keys from the config file into a ini file
2) read properties from ini file.
Idea failed because reading ini files (in Appsearch) always preceeds reading a config file in Customaction before="Appsearch".
The ini file Search path is c:\windows
The ini file is read by WiX IniFileSearch in product.wxs
List<string> iniContent = new List<string>();
iniContent.Add("[Backup]");
What should be the "known location" to store settings after uninstall?
string iniFilePath32 = @"C:\windows\system32\config\systemprofile\Local\SaltStack\Salt\";
string iniFilePath64 = @"C:\windows\SysWOW64\config\systemprofile\Local\SaltStack\Salt\";
string iniFile = iniFilePath32 + @"MinionConfigBackup.ini";
System.IO.Directory.CreateDirectory(iniFilePath32);
write_this(iniFile, iniContent.ToArray());
private static void write_this(string thefile, string[] thecontent) {
using (var fs = new FileStream(thefile, FileMode.OpenOrCreate, FileAccess.ReadWrite)) {
using (var fw = new StreamWriter(fs)) {
foreach (string line in thecontent) {
fw.Write(line);
fw.Write(System.Environment.NewLine);
};
fw.Flush(); // Added
}
fs.Flush();
}
}

View file

@ -0,0 +1,225 @@
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Tools.WindowsInstallerXml;
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.IO;
using System.Management; // Reference C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Management.dll
using System.ServiceProcess;
using System.Text.RegularExpressions;
namespace MinionConfigurationExtension {
public class cutil : WixExtension {
//
// DECAC means you must access data helper properties at session.CustomActionData[*]
// IMCAC means ou can directly access msi properties at session[*]
public static void del_file(Session session, string file) {
try {
File.Delete(file);
} catch (Exception ex) {
just_ExceptionLog("", session, ex);
}
}
public static void del_dir(Session session, string a_dir, string sub_dir = "") {
string abs_path = a_dir;
if (sub_dir.Length > 0) {
abs_path = Path.Combine(a_dir, sub_dir);
}
if (a_dir.Length>0 && Directory.Exists(a_dir) && Directory.Exists(abs_path)) {
try {
session.Log("...del_dir " + abs_path);
Directory.Delete(abs_path, true);
} catch (Exception ex) {
cutil.just_ExceptionLog("", session, ex);
}
}
}
public static void del_registry_key(Session session, String HKLM_reg_path) {
try {
session.Log("Going to delete HKLM registry key " + HKLM_reg_path);
RegistryKey HKLM = Registry.LocalMachine;
if (HKLM.OpenSubKey(HKLM_reg_path) == null) {
session.Log("does not exist");
}else{
session.Log("does exist. Now deleting");
HKLM.DeleteSubKeyTree(HKLM_reg_path);
}
} catch (Exception ex) {
cutil.just_ExceptionLog("", session, ex);
}
}
public static void del_registry_SOFTWARE_key(Session session, String SOFTWARE_reg_path) {
try {
session.Log("Going to delete SOFTWARE registry key " + SOFTWARE_reg_path);
del_registry_key(session, "SOFTWARE\\" + SOFTWARE_reg_path);
del_registry_key(session, "SOFTWARE\\WoW6432Node\\" + SOFTWARE_reg_path);
} catch (Exception ex) {
cutil.just_ExceptionLog("", session, ex);
}
}
public static RegistryKey get_registry_SOFTWARE_key(Session session, String SOFTWARE_reg_path) {
try {
session.Log("Going to get SOFTWARE registry key " + SOFTWARE_reg_path);
RegistryKey HKLM = Registry.LocalMachine;
RegistryKey r64 = HKLM.OpenSubKey("SOFTWARE\\" + SOFTWARE_reg_path);
if (r64 != null) return r64;
return HKLM.OpenSubKey("SOFTWARE\\WoW6432Node\\" + SOFTWARE_reg_path);
} catch (Exception ex) {
cutil.just_ExceptionLog("", session, ex);
}
return null;
}
public static void Write_file(Session session, string path, string filename, string filecontent) {
System.IO.Directory.CreateDirectory(path); // Creates all directories and subdirectories in the specified path unless they already exist
File.WriteAllText(Path.Combine(path, filename), filecontent); // throws an Exception if path does not exist
session.Log(@"...Write_file " + Path.Combine(path, filename));
}
public static void Writeln_file(Session session, string path, string filename, string filecontent) {
Write_file(session, path, filename, filecontent + Environment.NewLine);
}
public static void Move_file(Session session, string ffn, string timestamp_bak) {
string target = ffn + timestamp_bak;
session.Log("...Move_file? " + ffn);
if (File.Exists(ffn)) {
session.Log("...Move_file! " + ffn);
if (File.Exists(target)) {
session.Log("...target exists " + target);
} else {
File.Move(ffn, target);
}
}
}
public static void Move_dir(Session session, string ffn, string timestamp_bak, bool delete_target = false) {
string target = ffn + timestamp_bak;
session.Log("...Move_dir? " + ffn);
if (Directory.Exists(ffn)) {
session.Log("...Move_dir! " + ffn);
if (Directory.Exists(target)) {
session.Log("...target exists " + target);
if (delete_target) {
session.Log("...deleting target");
Directory.Delete(target, true);
Directory.Move(ffn, target);
}
} else {
Directory.Move(ffn, target);
}
}
}
public static void movedir_fromAbs_toRel(Session session, string abs_from0, string rel_tmp_dir, bool into_safety, string safedir) {
string abs_from;
string abs_to;
if (into_safety) {
abs_from = abs_from0;
abs_to = safedir + rel_tmp_dir;
} else {
abs_from = safedir + rel_tmp_dir;
abs_to = abs_from0;
}
session.Log("...We may need to move? does directory exist " + abs_from);
if (Directory.Exists(abs_from)) {
session.Log(".....yes");
} else {
session.Log(".....no");
return;
}
if (Directory.Exists(abs_to)) {
session.Log("....!I must first delete the TO directory " + abs_to);
shellout(session, @"rmdir /s /q " + abs_to);
}
// Now move
try {
session.Log("...now move to " + abs_to);
Directory.Move(abs_from, abs_to);
session.Log(".........ok");
} catch (Exception ex) {
just_ExceptionLog(@"...moving failed", session, ex);
}
}
public static string get_property_IMCAC(Session session, string key ) {
// IMMEDIATE means
// you can directly access msi properties at session[KEY]
// keys are case sensitive
// If key does not exist, its value will be empty
session.Log("...get_property_IMCAC key {0}", key);
string val = session[key];
session.Log("...get_property_IMCAC val {0}", val);
session.Log("...get_property_IMCAC len {0}", val.Length);
return val;
}
public static string get_property_DECAC(Session session, string key) {
// DEFERRED means
// you may modify the system because the transaction has started
// you must access msi properties via CustomActionData[KEY]
// If key does not exist, the msi will fail to install
session.Log("...get_property_DECAC key {0}", key);
string val = session.CustomActionData[key];
session.Log("...get_property_DECAC val {0}", val);
session.Log("...get_property_DECAC len {0}", val.Length);
return val;
}
public static void just_ExceptionLog(string description, Session session, Exception ex) {
session.Log(" ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ");
session.Log(description);
session.Log("Exception: {0}", ex.Message.ToString());
session.Log(ex.StackTrace.ToString());
}
public static string get_file_that_exist(Session session, string[] files) {
foreach (var file in files) {
session.Log("...looking for " + file);
if (File.Exists(file)) {
session.Log("...found " + file);
return file;
}
}
return "";
}
public static void shellout(Session session, string s) {
// This is a handmade shellout routine
session.Log("...shellout(" + s + ")");
try {
System.Diagnostics.Process process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/C " + s;
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
} catch (Exception ex) {
just_ExceptionLog("shellout tried " + s, session, ex);
}
}
}
}

View file

@ -0,0 +1,38 @@
using Microsoft.Tools.WindowsInstallerXml;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MinionConfigurationExtension")]
[assembly: AssemblyDescription("Custom Actions for the Salt Minion MSI")]
[assembly: AssemblyCompany("SaltStack, Inc")]
[assembly: AssemblyProduct("MinionConfigurationExtension")]
[assembly: AssemblyCopyright("Copyright © SaltStack, Inc 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyDefaultWixExtension(typeof(MinionConfigurationExtension.MinionConfiguration))]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ead7bf40-ca47-41e2-8187-6c346cccb46a")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,3 @@
.vs/
bin/
obj/

View file

@ -0,0 +1,200 @@
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.IO;
using System.Management; // Reference C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Management.dll
using System.ServiceProcess;
using System.Text.RegularExpressions;
namespace MinionConfigurationExtension {
public class cutil {
//
// DECAC means you must access data helper properties at session.CustomActionData[*]
// IMCAC means ou can directly access msi properties at session[*]
public static void del_file(Session session, string file) {
try {
File.Delete(file);
} catch (Exception ex) {
just_ExceptionLog("", session, ex);
}
}
public static void del_dir(Session session, string a_dir, string sub_dir = "") {
string abs_path = a_dir;
if (sub_dir.Length > 0) {
abs_path = Path.Combine(a_dir, sub_dir);
}
if (a_dir.Length>0 && Directory.Exists(a_dir) && Directory.Exists(abs_path)) {
try {
session.Log("...del_dir " + abs_path);
Directory.Delete(abs_path, true);
} catch (Exception ex) {
cutil.just_ExceptionLog("", session, ex);
}
}
}
public static void del_registry_key(Session session, String HKLM_reg_path) {
try {
session.Log("Going to delete HKLM registry key " + HKLM_reg_path);
RegistryKey HKLM = Registry.LocalMachine;
if (HKLM.OpenSubKey(HKLM_reg_path) == null) {
session.Log("does not exist");
}else{
session.Log("does exist. Now deleting");
HKLM.DeleteSubKeyTree(HKLM_reg_path);
}
} catch (Exception ex) {
cutil.just_ExceptionLog("", session, ex);
}
}
public static void del_registry_SOFTWARE_key(Session session, String SOFTWARE_reg_path) {
try {
session.Log("Going to delete SOFTWARE registry key " + SOFTWARE_reg_path);
del_registry_key(session, "SOFTWARE\\" + SOFTWARE_reg_path);
del_registry_key(session, "SOFTWARE\\WoW6432Node\\" + SOFTWARE_reg_path);
} catch (Exception ex) {
cutil.just_ExceptionLog("", session, ex);
}
}
public static RegistryKey get_registry_SOFTWARE_key(Session session, String SOFTWARE_reg_path) {
try {
session.Log("Going to get SOFTWARE registry key " + SOFTWARE_reg_path);
RegistryKey HKLM = Registry.LocalMachine;
RegistryKey r64 = HKLM.OpenSubKey("SOFTWARE\\" + SOFTWARE_reg_path);
if (r64 != null) return r64;
return HKLM.OpenSubKey("SOFTWARE\\WoW6432Node\\" + SOFTWARE_reg_path);
} catch (Exception ex) {
cutil.just_ExceptionLog("", session, ex);
}
return null;
}
public static void Write_file(Session session, string path, string filename, string filecontent) {
System.IO.Directory.CreateDirectory(path); // Creates all directories and subdirectories in the specified path unless they already exist
File.WriteAllText(Path.Combine(path, filename), filecontent); // throws an Exception if path does not exist
session.Log(@"...Write_file " + Path.Combine(path, filename));
}
public static void Writeln_file(Session session, string path, string filename, string filecontent) {
Write_file(session, path, filename, filecontent + Environment.NewLine);
}
public static void Move_file(Session session, string ffn, string timestamp_bak) {
string target = ffn + timestamp_bak;
session.Log("...Move_file? " + ffn);
if (File.Exists(ffn)) {
session.Log("...Move_file! " + ffn);
if (File.Exists(target)) {
session.Log("...target exists " + target);
} else {
File.Move(ffn, target);
}
}
}
public static void Move_dir(Session session, string ffn, string timestamp_bak, bool delete_target = false) {
string target = ffn + timestamp_bak;
session.Log("...Move_dir? " + ffn);
if (Directory.Exists(ffn)) {
session.Log("...Move_dir! " + ffn);
if (Directory.Exists(target)) {
session.Log("...target exists " + target);
if (delete_target) {
session.Log("...deleting target");
Directory.Delete(target, true);
Directory.Move(ffn, target);
}
} else {
Directory.Move(ffn, target);
}
}
}
public static void movedir_fromAbs_toRel(Session session, string abs_from0, string rel_tmp_dir, bool into_safety, string safedir) {
string abs_from;
string abs_to;
if (into_safety) {
abs_from = abs_from0;
abs_to = safedir + rel_tmp_dir;
} else {
abs_from = safedir + rel_tmp_dir;
abs_to = abs_from0;
}
session.Log("...We may need to move? does directory exist " + abs_from);
if (Directory.Exists(abs_from)) {
session.Log(".....yes");
} else {
session.Log(".....no");
return;
}
if (Directory.Exists(abs_to)) {
session.Log("....!I must first delete the TO directory " + abs_to);
shellout(session, @"rmdir /s /q " + abs_to);
}
// Now move
try {
session.Log("...now move to " + abs_to);
Directory.Move(abs_from, abs_to);
session.Log(".........ok");
} catch (Exception ex) {
just_ExceptionLog(@"...moving failed", session, ex);
}
}
public static void just_ExceptionLog(string description, Session session, Exception ex) {
session.Log(" ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ");
session.Log(description);
session.Log("Exception: " + ex.Message.ToString());
session.Log(ex.StackTrace.ToString());
}
public static string get_file_that_exist(Session session, string[] files) {
foreach (var file in files) {
session.Log("...looking for " + file);
if (File.Exists(file)) {
session.Log("...found " + file);
return file;
}
}
return "";
}
public static void shellout(Session session, string s) {
// This is a handmade shellout routine
session.Log("...shellout(" + s + ")");
try {
System.Diagnostics.Process process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/C " + s;
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
} catch (Exception ex) {
just_ExceptionLog("shellout tried " + s, session, ex);
}
}
}
}

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EE0051AC-7845-47B2-8918-4162BC1C527C}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>DebugMe</RootNamespace>
<AssemblyName>DebugMe</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Management" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CustomAction01Util.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,27 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31702.278
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DebugMe", "DebugMe.csproj", "{EE0051AC-7845-47B2-8918-4162BC1C527C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AAE92F38-5DBB-4EEC-B0AA-AE053C4EADB1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EE0051AC-7845-47B2-8918-4162BC1C527C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE0051AC-7845-47B2-8918-4162BC1C527C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE0051AC-7845-47B2-8918-4162BC1C527C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE0051AC-7845-47B2-8918-4162BC1C527C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {75271C6F-E08C-4593-B715-F4F4C7F7886F}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,326 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.IO;
using System.Management; // Reference C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Management.dll
using System.Security.AccessControl;
using System.Security.Principal;
using System.ServiceProcess;
using System.Text.RegularExpressions;
using System.Collections.Generic;
namespace MinionConfigurationExtension {
public class Session {
public void Log(String msg) {
Console.WriteLine(msg);
}
}
class ActionResult{
public static ActionResult Success;
}
class Program {
private static void write_master_and_id_to_file_DECAC(Session session, String configfile, string csv_multimasters, String id) {
/* How to
* read line
* if line master, read multimaster, replace
* if line id, replace
* copy through line
*/
char[] separators = new char[] { ',', ' ' };
string[] multimasters = csv_multimasters.Split(separators, StringSplitOptions.RemoveEmptyEntries);
session.Log("...want to write master and id to " + configfile);
bool configExists = File.Exists(configfile);
session.Log("......file exists " + configExists);
string[] configLinesINPUT = new List<string>().ToArray();
List<string> configLinesOUTPUT = new List<string>();
if (configExists) {
configLinesINPUT = File.ReadAllLines(configfile);
}
session.Log("...found config lines count " + configLinesINPUT.Length);
session.Log("...got master count " + multimasters.Length);
session.Log("...got id " + id);
Regex line_contains_key = new Regex(@"^([a-zA-Z_]+):");
Regex line_contains_one_multimaster = new Regex(@"^\s*-\s*([0-9a-zA-Z_.-]+)\s*$");
bool master_emitted = false;
bool id_emitted = false;
bool look_for_multimasters = false;
foreach (string line in configLinesINPUT) {
// search master and id
if (line_contains_key.IsMatch(line)) {
Match m = line_contains_key.Match(line);
string key = m.Groups[1].ToString();
if (key == "master") {
look_for_multimasters = true;
continue; // next line
} else if (key == "id") {
// emit id
configLinesOUTPUT.Add("id: " + id);
id_emitted = true;
continue; // next line
} else {
if (!look_for_multimasters) {
configLinesOUTPUT.Add(line); // copy through
continue; // next line
}
}
} else {
if (!look_for_multimasters) {
configLinesOUTPUT.Add(line); // copy through
continue; // next line
}
}
if (look_for_multimasters) {
// consume multimasters
if (line_contains_one_multimaster.IsMatch(line)) {
// consume another multimaster
} else {
look_for_multimasters = false;
// First emit master
if (multimasters.Length == 1) {
configLinesOUTPUT.Add("master: " + multimasters[0]);
master_emitted = true;
}
if (multimasters.Length > 1) {
configLinesOUTPUT.Add("master:");
foreach (string onemultimaster in multimasters) {
configLinesOUTPUT.Add("- " + onemultimaster);
}
master_emitted = true;
}
configLinesOUTPUT.Add(line); // Then copy through whatever is not one multimaster
}
}
}
// input is read
if (!master_emitted) {
// put master after hash master
Regex line_contains_hash_master = new Regex(@"^# master:");
List<string> configLinesOUTPUT_hash_master = new List<string>();
foreach (string output_line in configLinesOUTPUT) {
configLinesOUTPUT_hash_master.Add(output_line);
if(line_contains_hash_master.IsMatch(output_line)) {
if (multimasters.Length == 1) {
configLinesOUTPUT_hash_master.Add("master: " + multimasters[0]);
master_emitted = true;
}
if (multimasters.Length > 1) {
configLinesOUTPUT_hash_master.Add("master:");
foreach (string onemultimaster in multimasters) {
configLinesOUTPUT_hash_master.Add("- " + onemultimaster);
}
master_emitted = true;
}
}
}
configLinesOUTPUT = configLinesOUTPUT_hash_master;
}
if (!master_emitted) {
// put master at end
if (multimasters.Length == 1) {
configLinesOUTPUT.Add("master: " + multimasters[0]);
}
if (multimasters.Length > 1) {
configLinesOUTPUT.Add("master:");
foreach (string onemultimaster in multimasters) {
configLinesOUTPUT.Add("- " + onemultimaster);
}
}
}
if (!id_emitted) {
// put after hash
Regex line_contains_hash_id = new Regex(@"^# id:");
List<string> configLinesOUTPUT_hash_id = new List<string>();
foreach (string output_line in configLinesOUTPUT) {
configLinesOUTPUT_hash_id.Add(output_line);
if (line_contains_hash_id.IsMatch(output_line)) {
configLinesOUTPUT_hash_id.Add("id: " + id);
id_emitted = true;
}
}
configLinesOUTPUT = configLinesOUTPUT_hash_id;
}
if (!id_emitted) {
// put at end
configLinesOUTPUT.Add("id: " + id);
}
session.Log("...writing to " + configfile);
string output = string.Join("\r\n", configLinesOUTPUT.ToArray()) + "\r\n";
File.WriteAllText(configfile, output);
}
private static void read_master_and_id_from_file_IMCAC(Session session, String configfile, ref String ref_master, ref String ref_id) {
/* How to match multimasters *
match `master: `MASTER*:
if MASTER:
master = MASTER
else, a list of masters may follow:
while match `- ` MASTER:
master += MASTER
*/
session.Log("...searching master and id in " + configfile);
bool configExists = File.Exists(configfile);
session.Log("......file exists " + configExists);
if (!configExists) { return; }
string[] configLines = File.ReadAllLines(configfile);
Regex line_key_maybe_value = new Regex(@"^([a-zA-Z_]+):\s*([0-9a-zA-Z_.-]*)\s*$");
Regex line_listvalue = new Regex(@"^\s*-\s*([0-9a-zA-Z_.-]+)\s*$");
bool look_for_keys_otherwise_look_for_multimasters = true;
List<string> multimasters = new List<string>();
foreach (string line in configLines) {
if (look_for_keys_otherwise_look_for_multimasters && line_key_maybe_value.IsMatch(line)) {
Match m = line_key_maybe_value.Match(line);
string key = m.Groups[1].ToString();
string maybe_value = m.Groups[2].ToString();
//session.Log("...ANY KEY " + key + " " + maybe_value);
if (key == "master") {
if (maybe_value.Length > 0) {
ref_master = maybe_value;
session.Log("......master " + ref_master);
} else {
session.Log("...... now searching multimasters");
look_for_keys_otherwise_look_for_multimasters = false;
}
}
if (key == "id" && maybe_value.Length > 0) {
ref_id = maybe_value;
session.Log("......id " + ref_id);
}
} else if (line_listvalue.IsMatch(line)) {
Match m = line_listvalue.Match(line);
multimasters.Add(m.Groups[1].ToString());
} else {
look_for_keys_otherwise_look_for_multimasters = true;
}
}
if (multimasters.Count > 0) {
ref_master = string.Join(",", multimasters.ToArray());
session.Log("......master " + ref_master);
}
}
public static ActionResult kill_python_exe(Session session) {
// because a running process can prevent removal of files
// Get full path and command line from running process
session.Log("...BEGIN kill_python_exe");
using (var wmi_searcher = new ManagementObjectSearcher
("SELECT ProcessID, ExecutablePath, CommandLine FROM Win32_Process WHERE Name = 'python.exe'")) {
foreach (ManagementObject wmi_obj in wmi_searcher.Get()) {
try {
String ProcessID = wmi_obj["ProcessID"].ToString();
Int32 pid = Int32.Parse(ProcessID);
String ExecutablePath = wmi_obj["ExecutablePath"].ToString();
String CommandLine = wmi_obj["CommandLine"].ToString();
if (CommandLine.ToLower().Contains("salt") || ExecutablePath.ToLower().Contains("salt")) {
session.Log("...kill_python_exe " + ExecutablePath + " " + CommandLine);
Process proc11 = Process.GetProcessById(pid);
proc11.Kill();
}
} catch (Exception) {
// ignore wmiresults without these properties
}
}
}
session.Log("...END kill_python_exe");
return ActionResult.Success;
}
public static ActionResult del_NSIS_DECAC(Session session) {
// Leaves the Config
/*
* If NSIS is installed:
* remove salt-minion service,
* remove registry
* remove files, except /salt/conf and /salt/var
*
* The msi cannot use uninst.exe because the service would no longer start.
*/
session.Log("...BEGIN del_NSIS_DECAC");
RegistryKey HKLM = Registry.LocalMachine;
string ARPstring = @"Microsoft\Windows\CurrentVersion\Uninstall\Salt Minion";
RegistryKey ARPreg = cutil.get_registry_SOFTWARE_key(session, ARPstring);
string uninstexe = "";
if (ARPreg != null) uninstexe = ARPreg.GetValue("UninstallString").ToString();
session.Log("from REGISTRY uninstexe = " + uninstexe);
string SOFTWAREstring = @"Salt Project\Salt";
RegistryKey SOFTWAREreg = cutil.get_registry_SOFTWARE_key(session, SOFTWAREstring);
var bin_dir = "";
if (SOFTWAREreg != null) bin_dir = SOFTWAREreg.GetValue("bin_dir").ToString();
session.Log("from REGISTRY bin_dir = " + bin_dir);
if (bin_dir == "") bin_dir = @"C:\salt\bin";
session.Log("bin_dir = " + bin_dir);
session.Log("Going to kill ...");
kill_python_exe(session);
session.Log("Going to stop service salt-minion ...");
cutil.shellout(session, "sc stop salt-minion");
session.Log("Going to delete service salt-minion ...");
cutil.shellout(session, "sc delete salt-minion");
session.Log("Going to delete ARP registry entry ...");
cutil.del_registry_SOFTWARE_key(session, ARPstring);
session.Log("Going to delete SOFTWARE registry entry ...");
cutil.del_registry_SOFTWARE_key(session, SOFTWAREstring);
session.Log("Going to delete bindir ... " + bin_dir);
cutil.del_dir(session, bin_dir);
session.Log("Going to delete uninst.exe ...");
cutil.del_file(session, uninstexe);
var bindirparent = Path.GetDirectoryName(bin_dir);
session.Log(@"Going to delete bindir\..\salt\*.* ... " + bindirparent);
if (Directory.Exists(bindirparent)) {
try { foreach (FileInfo fi in new DirectoryInfo(bindirparent).GetFiles("salt*.*")) { fi.Delete(); } } catch (Exception) {; }
}
session.Log("...END del_NSIS_DECAC");
return ActionResult.Success;
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
static void Main(string[] args) {
Console.WriteLine("DebugMe!");
Session the_session = new Session();
//del_NSIS_DECAC(the_session);
String the_master= "";
String the_id = "bob";
string the_multimasters = "anna1,anna2";
//read_master_and_id_from_file_IMCAC(the_session, @"c:\temp\testme.txt", ref the_master, ref the_id);
write_master_and_id_to_file_DECAC(the_session, @"c:\temp\testme.txt", the_multimasters, the_id);
}
}
}

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("DebugMe")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("DebugMe")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ee0051ac-7845-47b2-8918-4162bc1c527c")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>

View file

@ -0,0 +1,236 @@
## Product attributes
### UpgradeCode
GUID defining the product across versions. E.g. a previous version is uninstalled during upgrade.
In other words: for update (or upgrade), Windows Installer relies on the UpgradeCode attribute of the Product tag.
Keep the same UpgradeCode GUID as long as you want the products to be upgraded by the installer.
### Id
[wixtoolset](https://wixtoolset.org/documentation/manual/v3/xsd/wix/product.html): The product code GUID for the product. Type: AutogenGuid
[MS](https://docs.microsoft.com/en-us/windows/win32/msi/product-codes): The product code is a GUID that is the principal identification of an application or product.
[MS](https://docs.microsoft.com/en-us/windows/win32/msi/productcode): This ID must vary for different versions and languages.
[MS](https://docs.microsoft.com/en-us/windows/win32/msi/changing-the-product-code): The product code must be changed if any of the following are true for the update:
- The name of the .msi file has been changed.
[MS](https://docs.microsoft.com/en-us/windows/win32/msi/major-upgrades):
A major upgrade is a comprehensive update of a product that needs a change of the ProductCode Property.
A typical major upgrade removes a previous version of an application and installs a new version.
A constant Product code GUID is (only) useful for a subsequent mst (transform).
To be safe for a major upgrade, the Id (product code GUI) is dynamic/autogenerated: * (star)
Therefore: we use dynamic/autogenerated: * (star)
## Conditions (for install)
[doc](https://wixtoolset.org/documentation/manual/v3/xsd/wix/condition.html)
[expression-syntax](https://www.firegiant.com/wix/tutorial/com-expression-syntax-miscellanea/expression-syntax)
The XML CDATA Section <![CDATA[ and ]]> is safer.
## Properties
Most important [Naming conventions](https://docs.microsoft.com/en-us/windows/win32/msi/restrictions-on-property-names):
- Public properties may be changed by the user and must be upper-case.
Logic value and checkboxes:
- A msi property is false if and only if it is unset, undefined, missing, the empty string (msi properties are strings).
- A checkbox is empty if and only if the relevant msi property is false.
[OS Properties](http://wixtoolset.org/documentation/manual/v3/howtos/redistributables_and_install_checks/block_install_on_os.html)
- MsiNTProductType: 1=Workstation 2=Domain controller 3=Server
- VersionNT:
- Windows 7=601 [msdn](https://msdn.microsoft.com/library/aa370556.aspx)
- Windows 10=603 [ms](https://support.microsoft.com/en-us/help/3202260/versionnt-value-for-windows-10-and-windows-server-2016)
- PhysicalMemory [ms](https://docs.microsoft.com/en-us/windows/desktop/Msi/physicalmemory)
msi properties, use in custom actions:
- DECAC = "Deferred cusmtom action in C#"
- CADH = "Custom action data helper"
- The CADH helper must mention each msi property or the DECAC function will crash:
- A DECAC that tries to use a msi property not listed in its CADH crashes.
Example:
In the CADH:
master=[MASTER];minion_id=[MINION_ID]
In the DECAC:
session.CustomActionData["master"] THIS IS OK
session.CustomActionData["mister"] THIS WILL CRASH
### Conditional removal of lifetime data
"Lifetime data" means any change that was not installed by the msi (during the life time of the application).
When uninstalling an application, an msi only removes exactly the data it installed, unless explicit actions are taken.
Salt creates life time data which must be removed, some of it during upgrade, all of it (except configuration) during uninstall.
Wix `util:RemoveFolderEx` removes any data transaction safe, but counts an upgrade as an uninstallation.
- for salt/bin/** (mostly *.pyc) this is appropriate.
- for salt/var/** (custom grains and modules) we restrict deletion to "only on uninstall" (`REMOVE ~= "ALL"`).
### Delete minion_id file
Alternatives
https://wixtoolset.org/documentation/manual/v3/xsd/wix/removefile.html
https://stackoverflow.com/questions/7120238/wix-remove-config-file-on-install
## Sequences
An msi is no linear program.
To understand when custom actions will be executed, one must look at the condition within the tag and Before/After:
On custom action conditions:
[Common-MSI-Conditions.pdf](http://resources.flexerasoftware.com/web/pdf/archive/IS-CHS-Common-MSI-Conditions.pdf)
[ms](https://docs.microsoft.com/en-us/windows/win32/msi/property-reference)
On the upgrade custom action condition:
| Property | Comment |
| --- | --- |
| UPGRADINGPRODUCTCODE | does not work
| Installed | the product is installed per-machine or for the current user
| Not Installed | there is no previous version with the same UpgradeCode
| REMOVE ~= "ALL" | Uninstall
[Custom action introduction](https://docs.microsoft.com/en-us/archive/blogs/alexshev/from-msi-to-wix-part-5-custom-actions-introduction)
### Articles
"Installation Phases and In-Script Execution Options for Custom Actions in Windows Installer"
http://www.installsite.org/pages/en/isnews/200108/
## Standard action sequence
[Standard actions reference](https://docs.microsoft.com/en-us/windows/win32/msi/standard-actions-reference)
[Standard actions WiX default sequence](https://www.firegiant.com/wix/tutorial/events-and-actions/queueing-up/)
[coding bee on Standard actions WiX default sequence](https://codingbee.net/wix/wix-the-installation-sequence)
You get error LGHT0204 when After or Before are wrong. Example:
del_NSIS_DECAC is a in-script custom action. It must be sequenced between InstallInitialize and InstallFinalize in the InstallExecuteSequence
Notes on ReadConfig_IMCAC
Note 1:
Problem: INSTALLDIR was not set in ReadConfig_IMCAC
Solution:
ReadConfig_IMCAC must not be called BEFORE FindRelatedProducts, but BEFORE MigrateFeatureStates because
INSTALLDIR in only set in CostFinalize, which comes after FindRelatedProducts
Maybe one could call ReadConfig_IMCAC AFTER FindRelatedProducts
Note 2:
ReadConfig_IMCAC is in both InstallUISequence and InstallExecuteSequence,
but because it is declared Execute='firstSequence', it will not be repeated in InstallExecuteSequence if it has been called in InstallUISequence.
## Don't allow downgrade
http://wixtoolset.org/documentation/manual/v3/howtos/updates/major_upgrade.html
## VC++ for Python
Quote from [PythonWiki](https://wiki.python.org/moin/WindowsCompilers):
Even though Python is an interpreted language, you **may** need to install Windows C++ compilers in some cases.
For example, you will need to use them if you wish to:
- Install a non-pure Python package from sources with Pip (if there is no Wheel package provided).
- Compile a Cython or Pyrex file.
**The msi contains only required VC++ runtimes.**
The Salt-Minion requires the C++ runtime for:
- The x509 module requires M2Crypto
- M2Crypto requiresOpenSSL
- OpenSSL requires "vcredist 2013"/120_CRT
Microsoft provides the Visual C++ compiler.
The runtime come with Visual Studio (in `C:\Program Files (x86)\Common Files\Merge Modules`).
Merge modules (*.msm) are msi 'library' databases that can be included ('merged') into a (single) msi databases.
Which Microsoft Visual C++ compiler is needed where?
| Software | msm | from Visual Studio and in "vcredist" name
|--- |--- |---
| (CPython 2.7) | VC90_CRT | 2008
| M2Crypto, OpenSSL | VC120_CRT | 2013
| (CPython 3.5, 3.6, 3.7, 3.8) | VC140_CRT | 2015
The msi incorporates merge modules following this [how-to](https://wixtoolset.org/documentation/manual/v3/howtos/redistributables_and_install_checks/install_vcredist.html)
## Images
Images:
- Dimensions of images must follow [WiX rules](http://wixtoolset.org/documentation/manual/v3/wixui/wixui_customizations.html)
- WixUIDialogBmp must be transparent
Create Product-imgLeft.png from panel.bmp:
- Open paint3D:
- new image, ..., canvas options: Transparent canvas off, Resize image with canvas NO, Width 493 Height 312
- paste panel.bmp, move to the left, save as
## Note on Create folder
Function win_verify_env() in salt/slt/utils/verify.py sets permissions on each start of the salt-minion services.
The installer must create the folder with the same permissions, so you keep sets of permissions in sync.
The Permission element(s) below replace any present permissions,
except NT AUTHORITY\SYSTEM:(OI)(CI)(F), which seems to be the basis.
Therefore, you don't need to specify User="[WIX_ACCOUNT_LOCALSYSTEM]" GenericAll="yes"
Use icacls to test the result:
C:\>icacls salt
salt BUILTIN\Administrators:(OI)(CI)(F)
NT AUTHORITY\SYSTEM:(OI)(CI)(F)
~~ read ~~
(object inherit)(container inherit)(full access)
C:\>icacls salt\bin\include
salt\bin\include BUILTIN\Administrators:(I)(OI)(CI)(F)
NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
w7h64\Markus:(I)(OI)(CI)(F)
~~ read ~~
(permission inherited from parent container)(object inherit)(container inherit)(full access)
Maybe even the Administrator group full access is "basis", so there is no result of the instruction,
I leave it for clarity, and potential future use.
## On servicePython.wxs
Experimental. Intended to replace nssm (ssm) with the Windows service control.
Maybe, nssm (ssm) cannot be replaced, because it indefineiy starts the salt-minion python exe over and over again,
whereas the Windows method only starts an exe only a limited time and then stops.
Also goto BuildDistFragment.xsl and remove python.exe
<ComponentRef Id="servicePython" />
## Set permissions of the install folder with WixQueryOsWellKnownSID
[doc](http://wixtoolset.org/documentation/manual/v3/customactions/osinfo.html)

View file

@ -0,0 +1,36 @@
2016-11-16 mkr
This regards ISSUE 1
https://github.com/markuskramerIgitt/salt-windows-msi/issues/1
uninstall removes the configuration file
Heat collects files into XML file salt-windows-msi\wix\MinionMSI\dist-amd64.wxs
The entry for conf/minion has a Guid:
<Component Id="cmpF3CE08C037F32A1C76DF93B02A6ACB79" Directory="dirA7CC33A34163812EEAF3B20CD074A564" Guid="{357ECA3A-24C0-49C5-9964-6CF9504168C4}">
<File Id="filF7B75C18646D054B4C42FBFF0826EBA7" KeyPath="yes" Source="$(var.dist)\conf\minion" />
</Component>
Having a Guid means that Wix treats conf/minion as part of the installation
On uninstall, WiX removed all parts of the installation, so also conf/minion.
This is unwanted behaviour.
Approach 1: FAIL
exclude the component
BuildDistFragment.xsl does that.
It filters out ssm.exe, so ssm.exe is not in salt-windows-msi\wix\MinionMSI\dist-amd64.wxs
ssm.exe is added manually in services.wxs.
FAILURE (I think) because then conf would not be installed.
Approach 2:
Remove the GUID of the component.
http://stackoverflow.com/questions/11848780/use-ends-with-in-xslt-v1-0
set attribute to a value while copying:
http://stackoverflow.com/questions/1137078/xslt-do-not-match-certain-attributes/12919373#12919373

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Adapted from http://www.lines-davies.net/blog/?p=12 -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<!--Identity Transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!--key to detect conf/minion file -->
<!-- ends-with WORKAROUND substring(A, length(A) - length(B) + 1) -->
<xsl:key name="conf_minion_key" match="wix:Component['conf\minion' = substring(wix:File/@Source, string-length(wix:File/@Source) - 10)]" use="@Id"/>
<!--Remove the Guid, so conf/minion is left behind on UNINSTALL -->
<xsl:template match="wix:Component[key('conf_minion_key', @Id)]">
<xsl:copy>
<xsl:attribute name="Guid">
<xsl:value-of select="''"/>
</xsl:attribute>
<xsl:apply-templates select="@*[local-name()!='Guid']|node()"/>
</xsl:copy>
</xsl:template>
<!-- This is the XSL madness copied for the case you harvest not ROOTDIR but CONFDIR -->
<!--key to detect minion file -->
<!-- ends-with WORKAROUND substring(A, length(A) - length(B) + 1) -->
<xsl:key name="conf_minion_key2" match="wix:Component['minion' = substring(wix:File/@Source, string-length(wix:File/@Source) - 5)]" use="@Id"/>
<!--Remove the Guid, so minion is left behind on UNINSTALL -->
<xsl:template match="wix:Component[key('conf_minion_key2', @Id)]">
<xsl:copy>
<xsl:attribute name="Guid">
<xsl:value-of select="''"/>
</xsl:attribute>
<xsl:apply-templates select="@*[local-name()!='Guid']|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Adapted from http://www.lines-davies.net/blog/?p=12 -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<!--Identity Transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!-- BEGIN remove component for python.exe from dist-amd64.wxs because it must be in service.wxs -->
<!--
<xsl:key name="excludepython" match="wix:Component[contains(wix:File/@Source, 'python.exe')]" use="@Id"/>
<xsl:template match="wix:Component[key('excludepython', @Id)]"/>
<xsl:template match="wix:ComponentRef[key('excludepython', @Id)]"/>
-->
<!-- END remove component for python.exe from dist-amd64.wxs because it must be in service.wxs -->
<!-- BEGIN remove component for ssm.exe from dist-amd64.wxs because it must be in service.wxs -->
<!--key to detect ssmexe-->
<xsl:key name="ssmexe" match="wix:Component[contains(wix:File/@Source, '\ssm.exe')]" use="@Id"/>
<!--Match and ignore ssmexe -->
<xsl:template match="wix:Component[key('ssmexe', @Id)]"/>
<xsl:template match="wix:ComponentRef[key('ssmexe', @Id)]"/>
<!-- END remove component for ssm.exe from dist-amd64.wxs because it must be in service.wxs -->
<!--key to detect conf/minion file -->
<!-- ends-with ~ substring (A, string-length(A) - string-length(B) + 1) -->
<xsl:key name="conf_minion_key" match="wix:Component['conf\minion' = substring(wix:File/@Source, string-length(wix:File/@Source) - 10)]" use="@Id"/>
<!--void Component Guid, so conf/minion is not removed on UNINSTALL -->
<xsl:template match="wix:Component[key('conf_minion_key', @Id)]">
<xsl:copy>
<xsl:attribute name="Guid">
<xsl:value-of select="''"/>
</xsl:attribute>
<xsl:apply-templates select="@*[local-name()!='Guid']|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

529
pkg/windows/msi/Product.wxs Normal file
View file

@ -0,0 +1,529 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
>> - Ends With
<< - Starts With - Requires CDATA because < is a key character in xml
CADH - Custom Action Data Helper - Sends variables from the client to the server
One way only (send only)
Runs as Administrator
DECAC - Deferred Custom Action - It is run by the msiserver service, local system
Runs as LOCAL_SYSTEM
Sandboxed
IMCAC - Immediate Custom Action - It's immediate
Runs as Administrator
-->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Product
Manufacturer = "$(var.MANUFACTURER)"
Name = "$(var.PRODUCT) $(var.DisplayVersion)"
Version = "$(var.InternalVersion)"
UpgradeCode = "FC6FB3A2-65DE-41A9-AD91-D10A402BD641"
Id = "*"
Language = "1033">
<!-- Install per Machine -->
<Package
InstallScope = "perMachine"
InstallerVersion = "500"
Compressed = "yes"
InstallPrivileges = "elevated"
/>
<!-- Abort installation if conditions are met -->
<?if $(var.WIN64)="no" ?>
<Condition Message="The 32bit variant must be installed on 32bit Windows.">not VersionNT64</Condition>
<?endif?>
<Condition Message="[AbortReason]">Installed or (not AbortReason)</Condition>
<Condition Message="Installation requires Windows 7/Server 2012. Found MsiNTProductType [MsiNTProductType], VersionNT [VersionNT]">
Installed
OR (MsiNTProductType = 1) AND (VersionNT >= 601)
OR (MsiNTProductType = 2) AND (VersionNT >= 602)
OR (MsiNTProductType = 3) AND (VersionNT >= 602)
</Condition>
<Condition Message="Installation requires 125 MB RAM. Found [PhysicalMemory] MB">Installed OR (PhysicalMemory > 125)</Condition>
<!--
ReadConfig_IMCAC must run immediately as Admin, the msi cannot elevate
before deferred, so the user must be Admin. We need to run with elevated
privileges in order to read the minion config, if it exists, and get the
currently configured master and minion id.
-->
<Condition Message="Searching for configuration requires Administrator privileges. Please open from an elevated command prompt.">Privileged</Condition>
<Condition Message="CONFIG_TYPE must not be &quot;[CONFIG_TYPE]&quot;. Please use &quot;Existing&quot;, &quot;Custom&quot; or &quot;Default&quot;.">
Installed
OR (CONFIG_TYPE = "Existing")
OR (CONFIG_TYPE = "Custom")
OR (CONFIG_TYPE = "Default")
</Condition>
<!-- Properties default values. For Properties unset by default see README.md -->
<Property Id="CONFIG_TYPE" Value="Default" />
<Property Id="START_MINION" Value="1" />
<Property Id="ROOTDRIVE" Value="C:\" /> <!-- Prevent msi to choose the drive with most free space -->
<Property Id="ARPPRODUCTICON" Value="icon.ico" />
<Property Id="MSIRESTARTMANAGERCONTROL" Value="Disable" />
<Property Id="MSIUSEREALADMINDETECTION" Value="1" />
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
<!-- Allow command line alias for Property -->
<SetProperty Id="INSTALLDIR" Value="[INSTALLFOLDER]" Before="LaunchConditions">INSTALLFOLDER</SetProperty>
<!-- MINION_CONFIG implies REMOVE_CONFIG -->
<SetProperty Id="REMOVE_CONFIG" Value="1" Before="LaunchConditions">MINION_CONFIG</SetProperty>
<!-- Search for old config minion file -->
<Property Id="OLD_CONF_EXISTS">
<DirectorySearch Id="conf_old" Path="C:\salt\conf">
<FileSearch Name="minion" />
</DirectorySearch>
</Property>
<!-- Search for new config minion file -->
<Property Id="NEW_CONF_EXISTS">
<DirectorySearch Id="conf_new" Path="C:\ProgramData\Salt Project\Salt\conf">
<FileSearch Name="minion" />
</DirectorySearch>
</Property>
<!-- Search registry for previous msi and Nullsoft install dirs -->
<Property Id="REGISTRY_ROOTDIR">
<!-- New layout is C:\ProgramData\Salt Project\Salt -->
<?if $(var.WIN64)=yes ?>
<RegistrySearch Root="HKLM" Key="SOFTWARE\$(var.MANUFACTURER)\$(var.PRODUCTDIR)" Name="root_dir" Win64="yes" Type="raw" Id="p1" />
<?endif ?>
<RegistrySearch Root="HKLM" Key="SOFTWARE\$(var.MANUFACTURER)\$(var.PRODUCTDIR)" Name="root_dir" Win64="no" Type="raw" Id="p2" />
</Property>
<!-- Search registry for option to remove config uninstall, set in previous install -->
<Property Id="REMOVE_CONFIG">
<?if $(var.WIN64)=yes ?>
<RegistrySearch Root="HKLM" Key="SOFTWARE\$(var.MANUFACTURER)\$(var.PRODUCTDIR)" Name="REMOVE_CONFIG" Win64="yes" Type="raw" Id="p3" />
<?endif ?>
<RegistrySearch Root="HKLM" Key="SOFTWARE\$(var.MANUFACTURER)\$(var.PRODUCTDIR)" Name="REMOVE_CONFIG" Win64="no" Type="raw" Id="p4" />
</Property>
<!-- Write registry (declare components) and delete on uninstall -->
<Component Id="register_dirs" Directory="TARGETDIR">
<RegistryKey Root="HKLM" Key="SOFTWARE\$(var.MANUFACTURER)\$(var.PRODUCTDIR)">
<RegistryValue Name="install_dir" Value="[INSTALLDIR]" Type="string" />
<RegistryValue Name="root_dir" Value="[ROOTDIR]" Type="string" />
</RegistryKey>
</Component>
<Component Id="register_remove_config" Directory="TARGETDIR">
<Condition>REMOVE_CONFIG</Condition>
<RegistryKey Root="HKLM" Key="SOFTWARE\$(var.MANUFACTURER)\$(var.PRODUCTDIR)" ForceDeleteOnUninstall="yes" >
<RegistryValue Name="REMOVE_CONFIG" Value="1" Type="string" />
</RegistryKey>
</Component>
<!-- Search registry for NSIS install -->
<Property Id="NSIS_UNINSTALLSTRING">
<RegistrySearch Root="HKLM" Key="SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Salt Minion" Name="UninstallString" Type="raw" Win64="no" Id="n1" />
<RegistrySearch Root="HKLM" Key="SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Salt Minion" Name="UninstallString" Type="raw" Win64="yes" Id="n2" />
</Property>
<!-- Detect NSIS install -->
<SetProperty Id="nsis_install_found" Before="LaunchConditions" Value="1">NSIS_UNINSTALLSTRING >> "uninst.exe"</SetProperty>
<!--
Workaround.
We stop the salt-minion service before validating the installation.
Part of validation is checking the log file. If the log file is locked, the check can fail and the install can fail.
Something has a lock on the minion log file, either salt-minion or
ssm.exe
Message is:
Another application has exclusive access to the file \salt\var\log\salt\minion
Please shut down the application
-->
<CustomAction Id="stopSalt" Script="vbscript">
On error resume next
Set objShell = CreateObject("WScript.Shell")
objShell.Run "net stop salt-minion", 0, true
</CustomAction>
<!-- This is the import statement for the Custom Actions:
IMCAC, DECAC, etc...
-->
<Binary Id="MinionConfigExt" SourceFile="CustomAction01\CustomAction01.CA.dll" />
<!-- The GUI Sequence (ask for input from the user) -->
<!--
You should not apply changes, only retrieve config.
Normally runs non-privileged, but reading salt config requires admin privilege.
https://docs.microsoft.com/en-us/windows/win32/msi/suggested-installuisequence
If this sequence is left out, this is a "silent install".
-->
<InstallUISequence>
<Custom Action="ReadConfig_IMCAC" Before="LaunchConditions">NOT Installed</Custom>
<LaunchConditions After="AppSearch" /> <!-- Benefit unclear. -->
</InstallUISequence>
<!-- The Install Sequence (aka server side) -->
<!--
Run with localsystem privilege.
https://docs.microsoft.com/en-us/windows/win32/msi/suggested-installexecutesequence
If the UI sequence took place, some (non-UI) actions are repeated (e.g. CostInitialize).
Actions before MigrateFeatureStates:
Custom action is placed before the transaction starts, coined "immediate".
You should not modifiy the system.
Actions after InstallFiles:
Custom action is placed after the transaction started, coined "deferred" and run in a sandbox.
Deferred custom actions (DECAC) need custom action helper (CADH) to access msi properties
You may modify the system.
-->
<InstallExecuteSequence>
<!--
On install and uninstall:
stopSalt to release log file, installValidate requires access to all
files, including the log file
-->
<Custom Action="stopSalt" Before="InstallValidate" >1</Custom>
<!-- ReadConfig_IMCAC must be called before CostInitialize so features can depend on properties set-->
<Custom Action="ReadConfig_IMCAC" Before="CostInitialize" >NOT Installed</Custom>
<Custom Action="del_NSIS_DECAC" After="InstallInitialize" >nsis_install_found</Custom>
<!-- If CLEAN_INSTALL, on install or upgrade: delete config and cache -->
<Custom Action="DeleteConfig2_CADH"
Before="DeleteConfig2_DECAC" >CLEAN_INSTALL and ((NOT Installed) or WIX_UPGRADE_DETECTED)</Custom>
<Custom Action="DeleteConfig2_DECAC" After="InstallInitialize" >CLEAN_INSTALL and ((NOT Installed) or WIX_UPGRADE_DETECTED)</Custom>
<Custom Action="MoveInsecureConfig_CADH"
Before="MoveInsecureConfig_DECAC" >(NOT Installed) and INSECURE_CONFIG_FOUND</Custom>
<Custom Action="MoveInsecureConfig_DECAC" Before="CreateFolders" >(NOT Installed) and INSECURE_CONFIG_FOUND</Custom>
<Custom Action="BackupConfig_DECAC" Before="CreateFolders" >(NOT Installed) and (not INSECURE_CONFIG_FOUND) and (not MINION_CONFIG) and ((CONFIG_TYPE = "Custom") or (CONFIG_TYPE = "Default"))</Custom>
<Custom Action="MoveConfig_DECAC" Before="CreateFolders" >(NOT Installed) and MOVE_CONF</Custom>
<Custom Action="WriteConfig_CADH" Before="WriteConfig_DECAC" >NOT Installed</Custom>
<Custom Action="WriteConfig_DECAC" After="WriteIniValues" >NOT Installed</Custom>
<!-- Optionally start the service -->
<StartServices Sequence="5900">START_MINION</StartServices>
<!-- On uninstall or upgrade: stop salt python.exe processes that would lock dll's -->
<Custom Action="kill_python_exe" After="StopServices" >(REMOVE ~= "ALL") or WIX_UPGRADE_DETECTED</Custom>
<!-- On uninstall (not upgrade): delete config and cache -->
<Custom Action="DeleteConfig_CADH"
Before="DeleteConfig_DECAC" >REMOVE ~= "ALL"</Custom>
<Custom Action="DeleteConfig_DECAC" After="RemoveFolders" >REMOVE ~= "ALL"</Custom>
</InstallExecuteSequence>
<!-- Declare Immediate (*_IMCAC) and Deferred (*_DECAC) Custom Actions. -->
<!-- These are the entry points into the DLL -->
<CustomAction Id="ReadConfig_IMCAC" BinaryKey="MinionConfigExt" DllEntry="ReadConfig_IMCAC" Execute="firstSequence" />
<CustomAction Id="del_NSIS_DECAC" BinaryKey="MinionConfigExt" DllEntry="del_NSIS_DECAC" Execute="deferred" Return="check" Impersonate="no" />
<CustomAction Id="MoveInsecureConfig_DECAC" BinaryKey="MinionConfigExt" DllEntry="MoveInsecureConfig_DECAC" Execute="deferred" Return="check" Impersonate="no" />
<CustomAction Id="MoveConfig_DECAC" BinaryKey="MinionConfigExt" DllEntry="MoveConfig_DECAC" Execute="deferred" Return="check" Impersonate="no" />
<CustomAction Id="WriteConfig_DECAC" BinaryKey="MinionConfigExt" DllEntry="WriteConfig_DECAC" Execute="deferred" Return="check" Impersonate="no" />
<CustomAction Id="DeleteConfig_DECAC" BinaryKey="MinionConfigExt" DllEntry="DeleteConfig_DECAC" Execute="deferred" Return="check" Impersonate="no" />
<CustomAction Id="DeleteConfig2_DECAC" BinaryKey="MinionConfigExt" DllEntry="DeleteConfig_DECAC" Execute="deferred" Return="check" Impersonate="no" />
<CustomAction Id="BackupConfig_DECAC" BinaryKey="MinionConfigExt" DllEntry="BackupConfig_DECAC" Execute="deferred" Return="check" Impersonate="no" />
<CustomAction Id="kill_python_exe" BinaryKey="MinionConfigExt" DllEntry="kill_python_exe" Execute="deferred" Return="check" Impersonate="no" />
<!-- Custom Action Data Helper for deferred custom actions -->
<!-- master and id must be named like in YAML configuration -->
<!-- Send all this stuff down to the sandbox -->
<CustomAction Id="WriteConfig_CADH" Property="WriteConfig_DECAC" Value="master=[MASTER];id=[MINION_ID];MOVE_CONF=[MOVE_CONF];sourcedir=[SOURCEDIR];INSTALLDIR=[INSTALLDIR];ROOTDIR=[ROOTDIR];CONFDIR=[CONFDIR];config_type=[CONFIG_TYPE];MINION_CONFIG=[MINION_CONFIG];custom_config=[CUSTOM_CONFIG];" />
<CustomAction Id="DeleteConfig_CADH" Property="DeleteConfig_DECAC" Value="CLEAN_INSTALL=[CLEAN_INSTALL];REMOVE_CONFIG=[REMOVE_CONFIG];INSTALLDIR=[INSTALLDIR];ROOTDIR=[ROOTDIR];" />
<CustomAction Id="DeleteConfig2_CADH" Property="DeleteConfig2_DECAC" Value="CLEAN_INSTALL=[CLEAN_INSTALL];REMOVE_CONFIG=[REMOVE_CONFIG];INSTALLDIR=[INSTALLDIR];ROOTDIR=[ROOTDIR];" />
<CustomAction Id="MoveInsecureConfig_CADH" Property="MoveInsecureConfig_DECAC" Value="INSECURE_CONFIG_FOUND=[INSECURE_CONFIG_FOUND];" />
<!-- Prevent downgrade -->
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<!-- Install VC++ runtime -->
<DirectoryRef Id="TARGETDIR">
<!-- Visual C++ runtimes depend on the target platform -->
<?if $(var.WIN64)=yes ?>
<Merge Id="MSM_VC120_CRT" SourceFile="$(var.WEBCACHE_DIR)\Microsoft_VC120_CRT_x64.msm" DiskId="1" Language="0" />
<Merge Id="MSM_VC140_CRT" SourceFile="$(var.WEBCACHE_DIR)\Microsoft_VC140_CRT_x64.msm" DiskId="1" Language="0" />
<?else ?>
<Merge Id="MSM_VC120_CRT" SourceFile="$(var.WEBCACHE_DIR)\Microsoft_VC120_CRT_x86.msm" DiskId="1" Language="0" />
<Merge Id="MSM_VC140_CRT" SourceFile="$(var.WEBCACHE_DIR)\Microsoft_VC140_CRT_x86.msm" DiskId="1" Language="0" />
<?endif ?>
</DirectoryRef>
<!-- Add INSTALLDIR to the system Path -->
<DirectoryRef Id="TARGETDIR">
<Component Id="INSTALLDIR_System_Path" Guid="A9F54641-91F8-4AFB-B812-9409E6EA0192">
<Environment Id="Env_PATH" Name="PATH" Value="[INSTALLDIR]" Permanent="no" Part="last" Action="set" System="yes" />
</Component>
</DirectoryRef>
<!-- Assemble fixed and conditional features -->
<!-- Conditional features need attention for install and uninstall -->
<!-- Leaving registry keys would mean the product is still installed -->
<Feature Id="ProductFeature" Title="Minion" Level="1">
<ComponentGroupRef Id="ProductComponents" />
<Feature Id="VC120" Title="VC++ 2013" AllowAdvertise="no" Display="hidden"><MergeRef Id="MSM_VC120_CRT" /></Feature>
<Feature Id="VC140" Title="VC++ 2015" AllowAdvertise="no" Display="hidden"><MergeRef Id="MSM_VC140_CRT" /></Feature>
</Feature>
<!-- Get the config file template from the msi store only if no config is present -->
<Feature Id='GetConfigTemplate' Level='0'>
<ComponentGroupRef Id="DiscoveredConfigFiles" />
<Condition Level="1">GET_CONFIG_TEMPLATE_FROM_MSI_STORE or (REMOVE ~= "ALL")</Condition>
</Feature>
<ComponentGroup Id="ProductComponents" Directory="INSTALLDIR">
<ComponentGroupRef Id="DiscoveredBinaryFiles" />
<ComponentGroupRef Id="service" />
<ComponentRef Id="INSTALLDIR_Permissions" />
<ComponentRef Id="ROOTDIR_Permissions" />
<ComponentRef Id="CreateMinionDDir" />
<ComponentRef Id="CreatePKIMinionDir" />
<ComponentRef Id="CreateRunDir" />
<ComponentRef Id="CreateLogSaltDir" />
<ComponentRef Id="CreateProcDir" />
<ComponentRef Id="CreateGrainsDir" />
<ComponentRef Id="INSTALLDIR_System_Path" />
<ComponentRef Id="register_dirs" />
<ComponentRef Id="register_remove_config" />
</ComponentGroup>
<!-- Icons -->
<Icon Id="icon.ico" SourceFile="pkg_resources\Product-icon.ico" />
<WixVariable Id="WixUIBannerBmp" Value="pkg_resources\Product-imgTop.jpg" />
<WixVariable Id="WixUIDialogBmp" Value="pkg_resources\Product-imgLeft.png" />
<WixVariable Id="WixUILicenseRtf" Value="pkg_resources\LICENSE.rtf" />
<!-- GUI
Built-in dialogs description ... https://wixtoolset.org//documentation/manual/v3/wixui/dialog_reference/wixui_dialogs.html
Dialogs appearing (* == built-in):
WelcomeDlg*
LicenseAgreementDlg*
HostsDlg
DirectoryDlg = InstallDirDlg*
VerifyReadyDlg*
-->
<UI>
<TextStyle Id="WixUI_Font_Warning" FaceName="Tahoma" Size="8" Red="200" Bold="yes" />
<UIRef Id="WixUI_Mondo" />
<Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="HostsDlg" Order="3">LicenseAccepted = "1"</Publish>
<Publish Dialog="HostsDlg" Control="Back" Event="NewDialog" Value="LicenseAgreementDlg">1</Publish>
<Publish Dialog="HostsDlg" Control="Next" Event="NewDialog" Value="DirectoryDlg" >1</Publish>
<Publish Dialog="HostsDlg" Control="Cancel" Event="SpawnDialog" Value="CancelDlg" >1</Publish>
<Publish Dialog="DirectoryDlg" Control="Back" Event="NewDialog" Value="HostsDlg" >1</Publish>
<Publish Dialog="DirectoryDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" >1</Publish>
<Publish Dialog="DirectoryDlg" Control="Cancel" Event="SpawnDialog" Value="CancelDlg" >1</Publish>
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="DirectoryDlg" >1</Publish>
<Dialog Id="HostsDlg" Width="370" Height="270" Title="[ProductName] Setup">
<Control Id="Title" Type="Text" X="15" Y="6" Width="300" Height="15" Transparent="yes" NoPrefix="yes" Text="{\WixUI_Font_Title}Minion configuration" />
<Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes" Text="Please verify master and minion." />
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="!(loc.InstallDirDlgBannerBitmap)" />
<Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="MasterLabel" Type="Text" X="20" Y="55" Width="280" Height="15" Transparent="yes" NoPrefix="yes" Text="Master (hostnames or IPv4, commma separated):" />
<Control Id="MasterId" Type="Edit" X="30" Y="70" Width="190" Height="15" Property="MASTER" />
<Control Id="MinionLabel" Type="Text" X="20" Y="85" Width="280" Height="15" Transparent="yes" NoPrefix="yes" Text="Minion:" />
<Control Id="MinionId" Type="Edit" X="30" Y="100" Width="190" Height="15" Property="MINION_ID" />
<Control Id="cbt_type" Type="Text" X="20" Y="125" Width="45" Height="15" Transparent="yes" NoPrefix="yes" Text="Config type:">
<Condition Action="show">not (MINION_CONFIG or INSECURE_CONFIG_FOUND or (not CONFIG_FOUND))</Condition>
<Condition Action="hide"> MINION_CONFIG or INSECURE_CONFIG_FOUND or (not CONFIG_FOUND)</Condition>
</Control>
<Control Id="cbo_type" Type="ComboBox" X="75" Y="125" Width="60" Height="15" Property="CONFIG_TYPE" >
<ComboBox Property="CONFIG_TYPE">
<ListItem Value="Existing" />
<ListItem Value="Custom" />
<ListItem Value="Default" />
</ComboBox>
<Condition Action="show">not (MINION_CONFIG or INSECURE_CONFIG_FOUND or (not CONFIG_FOUND))</Condition>
<Condition Action="hide"> MINION_CONFIG or INSECURE_CONFIG_FOUND or (not CONFIG_FOUND)</Condition>
</Control>
<Control Id="StartService" Type="CheckBox" X="25" Y="150" Width="280" Height="15" Property="START_MINION" CheckBoxValue="1" Text="&amp;Start salt-minion service immediately" />
<Control Id="HideInARP" Type="CheckBox" X="25" Y="165" Width="280" Height="15" Property="ARPSYSTEMCOMPONENT" CheckBoxValue="1" Text="&amp;Hide in 'Programs and Features'" />
<Control Id="conf_txt" Type="Text" X="36" Y="182" Width="130" Height="15" Text="Remove configuration and cache:" />
<Control Id="clean_inst" Type="CheckBox" X="166" Y="180" Width="70" Height="15" Property="CLEAN_INSTALL" CheckBoxValue="1" Text="B&amp;efore upgrade" />
<Control Id="remove_conf" Type="CheckBox" X="244" Y="180" Width="60" Height="15" Property="REMOVE_CONFIG" CheckBoxValue="1" Text="&amp;On uninstall" />
<Control Id="move_conf" Type="CheckBox" X="25" Y="195" Width="280" Height="15" Property="MOVE_CONF" CheckBoxValue="1" Text="&amp;Move configuration from &quot;C:\salt&quot; to &quot;C:\ProgramData\Salt Project&quot;">
<Condition Action="show"> OLD_CONF_EXISTS</Condition>
<Condition Action="hide">not (OLD_CONF_EXISTS)</Condition>
</Control>
<Control Id="Insecure1" Type="Text" X="20" Y="125" Width="320" Height="30" Transparent="yes" NoPrefix="yes" Text="{\WixUI_Font_Warning}Insecure config found at C:\salt\conf. If you continue, the config directory will be renamed and the default config will be used.">
<Condition Action="show"> INSECURE_CONFIG_FOUND and (not MINION_CONFIG) </Condition>
<Condition Action="hide">not (INSECURE_CONFIG_FOUND and (not MINION_CONFIG))</Condition>
</Control>
<Control Id="Insecure2" Type="Text" X="20" Y="125" Width="320" Height="30" Transparent="yes" NoPrefix="yes" Text="{\WixUI_Font_Warning}Insecure config found at C:\salt\conf. If you continue, the config directory will be renamed and the MINION_CONFIG property will be used.">
<Condition Action="show"> INSECURE_CONFIG_FOUND and MINION_CONFIG </Condition>
<Condition Action="hide">not (INSECURE_CONFIG_FOUND and MINION_CONFIG)</Condition>
</Control>
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)" />
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="!(loc.WixUINext)" />
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)"/>
</Dialog>
<Dialog Id="DirectoryDlg" Width="370" Height="270" Title="[ProductName] Setup">
<Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes" Text="{\WixUI_Font_Title}Installation directory" />
<Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes" Text="Please specify the installation directory." />
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="!(loc.InstallDirDlgBannerBitmap)" />
<Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="i1" Type="Text" X="20" Y="55" Width="280" Height="15" Transparent="yes" NoPrefix="yes" Text="Install program into directory:" />
<Control Id="i2" Type="PathEdit" X="30" Y="70" Width="250" Height="15" Property="INSTALLDIR" />
<Control Id="i3" Type="PushButton" X="280" Y="70" Width="40" Height="15" Text="Browse" >
<Publish Property="_BrowseProperty" Value="INSTALLDIR" Order="1">1</Publish>
<Publish Event="SpawnDialog" Value="BrowseDlg" Order="2">1</Publish>
</Control>
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)" />
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="!(loc.WixUINext)" />
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)" />
</Dialog>
</UI>
<!-- Default directory layout, Admin could modify by setting all affected directory id's -->
<Directory Id="TARGETDIR" Name="SourceDir"> <!-- Fixed expression. Location of the msi file -->
<Directory Id="$(var.PROGRAMFILES)"> <!-- C:\Program Files or C:\Program Files (x86) -->
<Directory Id="installparent" Name="$(var.MANUFACTURER)">
<Directory Id="INSTALLDIR" Name="$(var.PRODUCTDIR)" />
</Directory>
</Directory>
<!-- Default layout for the ROOTDIR, this is always in ProgramData -->
<Directory Id="CommonAppDataFolder"> <!-- C:\ProgramData -->
<Directory Id="rootparent" Name="$(var.MANUFACTURER)">
<Directory Id="ROOTDIR" Name="$(var.PRODUCTDIR)">
<Directory Id="CONFDIR" Name="conf">
<Directory Id="MINIONDDIR" Name="minion.d" />
<Directory Id="PKIDIR" Name="pki">
<Directory Id="PKIMINIONDIR" Name="minion" />
</Directory>
</Directory>
<Directory Id="VARDIR" Name="var">
<Directory Id="RUNDIR" Name="run" />
<Directory Id="LOGDIR" Name="log">
<Directory Id="LOGSALTDIR" Name="salt" />
</Directory>
<Directory Id="CACHEDIR" Name="cache">
<Directory Id="CACHESALTDIR" Name="salt">
<Directory Id="CACHESALTMINIONDIR" Name="minion">
<Directory Id="PROCDIR" Name="proc" />
<Directory Id="EXTMODSDIR" Name="extmods">
<Directory Id="GRAINSDIR" Name="grains" />
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
<!-- Set ROOTDIR to C:\salt if OLD_CONF_EXISTS and not MOVE_CONF (only before install sequence) -->
<SetDirectory Id="ROOTDIR" Value="C:\salt" Sequence="execute">OLD_CONF_EXISTS and (not MOVE_CONF)</SetDirectory>
<SetDirectory Id="CONFDIR" Value="C:\salt\conf" Sequence="execute">OLD_CONF_EXISTS and (not MOVE_CONF)</SetDirectory>
<!-- Set ownership to 'Localized qualified name of the Administrators group' -->
<PropertyRef Id="WIX_ACCOUNT_ADMINISTRATORS" />
<Component Id="INSTALLDIR_Permissions" Directory="INSTALLDIR" Guid="B30E119F-0286-4453-8AB7-C6E916FA2843">
<CreateFolder>
<Permission User="[WIX_ACCOUNT_ADMINISTRATORS]" GenericAll="yes" TakeOwnership="yes" />
</CreateFolder>
</Component>
<Component Id="ROOTDIR_Permissions" Directory="ROOTDIR" Guid="84554438-6807-4d92-b602-7fce831b01a3">
<CreateFolder>
<Permission User="[WIX_ACCOUNT_ADMINISTRATORS]" GenericAll="yes" TakeOwnership="yes" />
</CreateFolder>
</Component>
<Component Id="CreateMinionDDir" Directory="MINIONDDIR" Guid="59E64BE0-284C-4482-987C-416F4BD20A4C">
<CreateFolder />
</Component>
<Component Id="CreatePKIMinionDir" Directory="PKIMINIONDIR" Guid="F907613B-3669-4AB2-9630-A8ECDD50F157">
<CreateFolder />
</Component>
<Component Id="CreateRunDir" Directory="RUNDIR" Guid="BB3727B5-4C46-4622-BF08-F69E5004D817">
<CreateFolder />
</Component>
<Component Id="CreateLogSaltDir" Directory="LOGSALTDIR" Guid="BE111495-EE66-4BD0-9111-1373C2F78AE1">
<CreateFolder />
</Component>
<Component Id="CreateProcDir" Directory="PROCDIR" Guid="3B92D253-189F-4100-A21D-4FD7C45F5CD9">
<CreateFolder />
</Component>
<Component Id="CreateGrainsDir" Directory="GRAINSDIR" Guid="2F08218A-45C6-4E51-970C-DCB2530D36C5">
<CreateFolder />
</Component>
<!-- Install Windows service (nssm and without)-->
<?if 1=1 ?>
<ComponentGroup Id="service">
<Component Id="cmp906378FA53882935FD2EC0CC58D32FAC" Directory="INSTALLDIR" Guid="{E27F3682-194D-4CC2-9F9B-F3E1D53ADCDB}">
<File Id="ssm.exe" KeyPath="yes" Source="$(var.DISCOVER_INSTALLDIR)\ssm.exe" />
<ServiceInstall
Account="LocalSystem" ErrorControl="normal" Start="auto" Type="ownProcess" Vital="yes"
Name="salt-minion"
Description="Salt Minion from saltproject.io"
DisplayName="salt-minion"
Id="si1">
<util:ServiceConfig
FirstFailureActionType="none"
SecondFailureActionType="none"
ThirdFailureActionType="none" />
</ServiceInstall>
<ServiceControl Name="salt-minion" Remove="uninstall" Stop="both" Start="install" Wait="yes" Id="sc1">
<ServiceArgument />
</ServiceControl>
<CreateFolder />
<util:EventSource Log="Application" Name="ssm" EventMessageFile="[#ssm.exe]" />
<RegistryKey Root="HKLM" Key="System\CurrentControlSet\Services\salt-minion">
<RegistryKey Key="Parameters">
<RegistryValue Type="expandable" Name="AppDirectory" Value="[INSTALLDIR]" />
<RegistryValue Type="expandable" Name="Application" Value="[INSTALLDIR]salt-minion.exe" />
<RegistryValue Type="expandable" Name="AppParameters" Value='-c "[ROOTDIR]conf" -l quiet' />
<RegistryValue Type="integer" Name="AppStopMethodConsole" Value="24000" />
<RegistryValue Type="integer" Name="AppStopMethodWindow" Value="2000" />
<RegistryValue Type="integer" Name="AppRestartDelay" Value="60000" />
<RegistryKey Key="AppExit">
<RegistryValue Type="string" Value="Restart" />
</RegistryKey>
</RegistryKey>
</RegistryKey>
</Component>
</ComponentGroup>
<?else ?>
<!-- For the far future, in which nssm.exe is no longer used -->
<ComponentGroup Id="service">
<Component Id="servicec1" Directory="INSTALLDIR" Guid="51713960-fbe7-4e87-9472-66e3c18f76cd">
<File Source="$(var.DISCOVER_INSTALLDIR)\salt-minion.exe" KeyPath="yes" />
<ServiceInstall Name="salt-minion" DisplayName="Salt Minion" Description="Salt Minion from saltproject.io"
Arguments="-c [ROOTDIR]conf -l quiet"
Account="LocalSystem" ErrorControl="normal" Start="auto" Type="ownProcess" Vital="yes" >
<util:ServiceConfig
FirstFailureActionType="none"
SecondFailureActionType="none"
ThirdFailureActionType="none" />
</ServiceInstall>
<ServiceControl Name="salt-minion" Start="install" Stop="both" Remove="uninstall" Wait="yes" Id="ServiceControl">
<ServiceArgument /> <!-- http://stackoverflow.com/questions/10621082/wix-serviceinstall-arguments -->
</ServiceControl>
</Component>
</ComponentGroup>
<?endif ?>
<!-- Do not create cab files -->
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
</Product>
</Wix>

View file

@ -0,0 +1,85 @@
# How to build the msi
## Build client requirements
The build client is where the msi installer is built.
You need
- 64bit Windows 10
- Git repositories `salt`, `salt-windows-nsis` and `salt-windows-msi`
- .Net 3.5 SDK (for WiX)<sup>*</sup>
- [Wix 3](http://wixtoolset.org/releases/)<sup>**</sup>
- [Build tools 2015](https://www.microsoft.com/en-US/download/confirmation.aspx?id=48159)<sup>**</sup>
- Microsoft_VC140_CRT_x64.msm from Visual Studio 2015<sup>**</sup>
- Microsoft_VC140_CRT_x86.msm from Visual Studio 2015<sup>**</sup>
- Microsoft_VC120_CRT_x64.msm from Visual Studio 2013<sup>**</sup>
- Microsoft_VC120_CRT_x86.msm from Visual Studio 2013<sup>**</sup>
Notes:
- <sup>*</sup> `build.cmd` will open `optionalfeatures` if necessary.
- <sup>**</sup> `build.cmd` will download them to `.\_cache.dir` and install if necessary.
### Step 1: build the Nullsoft (NSIS) exe installer or use the mockup
- Build the Nullsoft (NSIS) exe installer
- Or execute `test-copy_mock_files_to_salt_repo.cmd` for only testing configuration
### Step 2: build the msi installer
Execute
build.cmd
### Remark on transaction safety
- Wix is transaction safe: either the product is installed or the prior state is restored/rolled back.
- C# is not.
### Directory structure
- Product.wxs: main file.
- (EXPERIMENTAL) salt-minion Windows Service
- requires [saltminionservice](https://github.com/saltstack/salt/blob/167cdb344732a6b85e6421115dd21956b71ba25a/salt/utils/saltminionservice.py) or [winservice](https://github.com/saltstack/salt/blob/3fb24929c6ebc3bfbe2a06554367f8b7ea980f5e/salt/utils/winservice.py) [Removed](https://github.com/saltstack/salt/commit/8c01aacd9b4d6be2e8cf991e3309e2a378737ea0)
- CustomAction01/: custom actions in C#
- *-discovered-files.wxs: TEMPORARY FILE
### Naming conventions
- **Immediate** custom actions serve initialization (before the install transaction starts) and must not change the system.
- **Deferred** custom action may change the system but run in a "sandbox".
Postfix | Example | Meaning
-------- | ---------------------------------- | -------
`_IMCAC` | `ReadConfig_IMCAC` | Immediate custom action written in C#
`_DECAC` | `WriteConfig_DECAC` | Deferred custom action written in C#
`_CADH` | `WriteConfig_CADH` | Custom action data helper (only for deferred custom action)
"Custom action data helper" send properties to the deferreed actions in the sandbox.
### Other Notes
msi conditions for install, uninstall, upgrade:
- https://stackoverflow.com/a/17608049
Install sequences documentation:
- [standard-actions-reference](https://docs.microsoft.com/en-us/windows/win32/msi/standard-actions-reference)
- [suggested-installuisequence](https://docs.microsoft.com/en-us/windows/win32/msi/suggested-installuisequence)
- [suggested-installexecutesequence](https://docs.microsoft.com/en-us/windows/win32/msi/suggested-installexecutesequence)
- [other docs](https://www.advancedinstaller.com/user-guide/standard-actions.html)
The Windows installer restricts the maximum values of the [ProductVersion property](https://docs.microsoft.com/en-us/windows/win32/msi/productversion):
- major.minor.build
- `255.255.65535`
Therefore we generate an "internal version":
- Salt 3002.1 becomes `30.02.1`
[Which Python version uses which MS VC CRT version](https://wiki.python.org/moin/WindowsCompilers)
- Python 2.7 = VC CRT 9.0 = VS 2008
- Python 3.6 = VC CRT 14.0 = VS 2017

105
pkg/windows/msi/README.md Normal file
View file

@ -0,0 +1,105 @@
# Salt Minion msi installer
The installer offers properties for unattended/silent installations.
Example: install silently, set the master, don't start the service:
> msiexec /i *.msi MASTER=salt2 START_MINION=""
Example: uninstall and remove configuration
> MsiExec.exe /X *.msi REMOVE_CONFIG=1
## Notes
- The installer requires a privileged user
- Properties must be upper case
- Values of properties are case sensitve
- Values must be quoted when they contain whitespace, or to unset a property, as in `START_MINION=""`
- Creates a verbose log file, by default `%TEMP%\MSIxxxxx.LOG`, where xxxxx is random. The name of the log can be specified with `msiexec /log example.log`
- extends the system `PATH` environment variable
## Properties
Property | Default value | Comment
---------------------- | ----------------------- | ------
`MASTER` | `salt` | The master (name or IP). Separate multiple masters by comma.
`MASTER_KEY` | | The master public key. See below.
`MINION_ID` | Hostname | The minion id.
`MINION_CONFIG` | | Content to be written to the `minion` config file. See below.
`START_MINION` | `1` | Set to `""` to prevent the start of the `salt-minion` service.
`MOVE_CONF` | | Set to `1` to move configuration from `C:\salt` to `%ProgramData%`.
`REMOVE_CONFIG` | | Set to `1` to remove configuration on uninstall. Implied by `MINION_CONFIG`.
`CLEAN_INSTALL` | | Set to `1` to remove configuration and cache before install or upgrade.
`CONFIG_TYPE` | `Existing` | Set to `Custom` or `Default` for scenarios below.
`CUSTOM_CONFIG` | | Name of a custom config file in the same path as the installer or full path. Requires `CONFIG_TYPE=Custom`. __ONLY FROM COMMANDLINE__
`INSTALLDIR` | Windows default | Where to install binaries.
`ROOTDIR` | `C:\ProgramData\Salt Project\Salt` | Where to install configuration.
`ARPSYSTEMCOMPONENT` | | Set to `1` to hide "Salt Minion" in "Programs and Features".
Master and id are read from file `conf\minion`
You can set a master with `MASTER`.
You can set a master public key with `MASTER_KEY`, after you converted it into one line like so:
- Remove the first and the last line (`-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----`).
- Remove linebreaks.
### Property `MINION_CONFIG`
If `MINION_CONFIG` is set:
- Its content is written to configuraton file `conf\minion`, with `^` replaced by line breaks
- All prior configuration is deleted:
- all `minion.d\*.conf` files
- the `minion_id` file
- Implies `REMOVE_CONFIG=1`: uninstall will remove all configuration.
Example `MINION_CONFIG="master: Anna^id: Bob"` results in:
master: Anna
id: Bob
### Property `CONFIG_TYPE`
There are 3 scenarios the installer tries to account for:
1. existing-config
2. custom-config
3. default-config
Existing
This setting makes no changes to the existing config and just upgrades/downgrades salt.
Makes for easy upgrades. Just run the installer with a silent option.
If there is no existing config, then the default is used and `master` and `minion id` are applied if passed.
Custom
This setting will lay down a custom config passed via the command line.
Since we want to make sure the custom config is applied correctly, we'll need to back up any existing config.
1. `minion` config renamed to `minion-<timestamp>.bak`
2. `minion_id` file renamed to `minion_id-<timestamp>.bak`
3. `minion.d` directory renamed to `minion.d-<timestamp>.bak`
Then the custom config is laid down by the installer... and `master` and `minion id` should be applied to the custom config if passed.
Default
This setting will reset config to be the default config contained in the pkg.
Therefore, all existing config files should be backed up
1. `minion` config renamed to `minion-<timestamp>.bak`
2. `minion_id` file renamed to `minion_id-<timestamp>.bak`
3. `minion.d` directory renamed to `minion.d-<timestamp>.bak`
Then the default config file is laid down by the installer... settings for `master` and `minion id` should be applied to the default config if passed
### Previous installation in C:\salt and how to install into C:\salt
A previous installation or configuration in `C:\salt` causes an upgrade into `C:\salt`, unless you set `MOVE_CONF=1`.
Set the two properties `INSTALLDIR=c:\salt ROOTDIR=c:\salt` to install binaries and configuration into `C:\salt`.
## Client requirements
- Windows 7 (for workstations), Server 2012 (for domain controllers), or higher.

View file

@ -0,0 +1,360 @@
<#
.SYNOPSIS
This script builds the MSI installer
.DESCRIPTION
This script builds the MSI installer from the contents of the buildenv directory
.EXAMPLE
build_pkg.ps1
.EXAMPLE
build_pkg.ps1 -Version 3005
#>
param(
[Parameter(Mandatory=$false)]
[Alias("v")]
# The version of Salt to be built. If this is not passed, the script will
# attempt to get it from the git describe command on the Salt source
# repo
[String] $Version
)
#-------------------------------------------------------------------------------
# Script Preferences
#-------------------------------------------------------------------------------
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
#-------------------------------------------------------------------------------
# Script Functions
#-------------------------------------------------------------------------------
function Write-Result($result, $ForegroundColor="Green") {
$position = 80 - $result.Length - [System.Console]::CursorLeft
Write-Host -ForegroundColor $ForegroundColor ("{0,$position}$result" -f "")
}
function VerifyOrDownload ($local_file, $URL, $SHA256) {
#### Verify or download file
$filename = Split-Path $local_file -leaf
if ( Test-Path -Path $local_file ) {
Write-Host "Verifying hash for $filename`: " -NoNewline
if ( (Get-FileHash $local_file).Hash -eq $SHA256 ) {
Write-Result "Verified" -ForegroundColor Green
return
} else {
Write-Result "Failed Hash" -ForegroundColor Red
Remove-Item -Path $local_file -Force
}
}
Write-Host "Downloading $filename`: " -NoNewline
Invoke-WebRequest -Uri "$URL" -OutFile "$local_file"
if ( Test-Path -Path $local_file ) {
Write-Result "Success" -ForegroundColor Green
} else {
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}
#-------------------------------------------------------------------------------
# Script Variables
#-------------------------------------------------------------------------------
$WEBCACHE_DIR = "$env:TEMP\msi_build_cache_dir"
$DEPS_URL = "http://repo.saltproject.io/windows/dependencies"
$PROJECT_DIR = $(git rev-parse --show-toplevel)
$BUILD_DIR = "$PROJECT_DIR\pkg\windows\build"
$BUILDENV_DIR = "$PROJECT_DIR\pkg\windows\buildenv"
$SCRIPTS_DIR = "$BUILDENV_DIR\Scripts"
$PYTHON_BIN = "$SCRIPTS_DIR\python.exe"
$BUILD_ARCH = $(. $PYTHON_BIN -c "import platform; print(platform.architecture()[0])")
$SCRIPT_DIR = (Get-ChildItem "$($myInvocation.MyCommand.Definition)").DirectoryName
$RUNTIME_DIR = [System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory()
$CSC_BIN = "$RUNTIME_DIR\csc.exe"
if ( $BUILD_ARCH -eq "64bit" ) {
$BUILD_ARCH = "AMD64"
} else {
$BUILD_ARCH = "x86"
}
# MSBuild needed to compile C#
if ( [System.IntPtr]::Size -eq 8 ) {
$MSBUILD = "C:\Program Files (x86)\MSBuild\14.0"
} else {
$MSBUILD = "C:\Program Files\MSBuild\14.0"
}
#-------------------------------------------------------------------------------
# Verify Salt and Version
#-------------------------------------------------------------------------------
if ( [String]::IsNullOrEmpty($Version) ) {
$Version = $( git describe ).Trim("v")
if ( [String]::IsNullOrEmpty($Version) ) {
Write-Host "Failed to get version from $PROJECT_DIR"
exit 1
}
}
#-------------------------------------------------------------------------------
# Script Begin
#-------------------------------------------------------------------------------
Write-Host $("=" * 80)
Write-Host "Build MSI Installer for Salt" -ForegroundColor Cyan
Write-Host "- Architecture: $BUILD_ARCH"
Write-Host "- Salt Version: $Version"
Write-Host $("-" * 80)
#-------------------------------------------------------------------------------
# Ensure cache dir exists
#-------------------------------------------------------------------------------
if ( ! (Test-Path -Path $WEBCACHE_DIR) ) {
Write-Host "Creating cache directory: " -NoNewline
New-Item -ItemType directory -Path $WEBCACHE_DIR | Out-Null
if ( Test-Path -Path $WEBCACHE_DIR ) {
Write-Result "Success" -ForegroundColor Green
} else {
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}
#-------------------------------------------------------------------------------
# Caching VC++ Runtimes
#-------------------------------------------------------------------------------
$RUNTIMES = @(
("Microsoft_VC120_CRT_x64.msm", "64", "15FD10A495287505184B8913DF8D6A9CA461F44F78BC74115A0C14A5EDD1C9A7"),
("Microsoft_VC120_CRT_x86.msm", "32", "26340B393F52888B908AC3E67B935A80D390E1728A31FF38EBCEC01117EB2579"),
("Microsoft_VC140_CRT_x64.msm", "64", "E1344D5943FB2BBB7A56470ED0B7E2B9B212CD9210D3CC6FA82BC3DA8F11EDA8"),
("Microsoft_VC140_CRT_x86.msm", "32", "0D36CFE6E9ABD7F530DBAA4A83841CDBEF9B2ADCB625614AF18208FDCD6B92A4")
)
$RUNTIMES | ForEach-Object {
$name, $arch, $hash = $_
VerifyOrDownload "$WEBCACHE_DIR\$name" "$DEPS_URL/$arch/$name" "$hash"
}
#-------------------------------------------------------------------------------
# Converting to MSI Version
#-------------------------------------------------------------------------------
Write-Host "Getting internal version: " -NoNewline
[regex]$tagRE = '(?:[^\d]+)?(?<major>[\d]{1,4})(?:\.(?<minor>[\d]{1,2}))?(?:\.(?<bugfix>[\d]{0,2}))?'
$tagREM = $tagRE.Match($Version)
$major = $tagREM.groups["major"].ToString()
$minor = $tagREM.groups["minor"]
$bugfix = $tagREM.groups["bugfix"]
if ([string]::IsNullOrEmpty($minor)) {$minor = 0}
if ([string]::IsNullOrEmpty($bugfix)) {$bugfix = 0}
# Assumption: major is a number
$major1 = $major.substring(0, 2)
$major2 = $major.substring(2)
$INTERNAL_VERSION = "$major1.$major2.$minor"
Write-Result $INTERNAL_VERSION -ForegroundColor Green
#-------------------------------------------------------------------------------
# Setting Product Variables
#-------------------------------------------------------------------------------
$MANUFACTURER = "Salt Project"
$PRODUCT = "Salt Minion"
$PRODUCTFILE = "Salt-Minion-$Version"
$PRODUCTDIR = "Salt"
$DISCOVER_INSTALLDIR = "$BUILDENV_DIR", "$BUILDENV_DIR"
$DISCOVER_CONFDIR = Get-Item "$BUILDENV_DIR\configs"
# MSI related arrays for 64 and 32 bit values, selected by BUILD_ARCH
if ($BUILD_ARCH -eq "AMD64") {$i = 0} else {$i = 1}
$WIN64 = "yes", "no" # Used in wxs
$ARCHITECTURE = "x64", "x86" # WiX dictionary values
$ARCH_AKA = "AMD64", "x86" # For filename
$PROGRAMFILES = "ProgramFiles64Folder", "ProgramFilesFolder" # msi dictionary values
function CheckExitCode() { # Exit on failure
if ($LastExitCode -ne 0) {
Write-Result "Failed" -ForegroundColor Red
if (Test-Path build.tmp -PathType Leaf) {
Get-Content build.tmp
Remove-Item build.tmp
}
exit(1)
}
Write-Result "Success" -ForegroundColor Green
if (Test-Path build.tmp -PathType Leaf) {
Remove-Item build.tmp
}
}
#-------------------------------------------------------------------------------
# Compiling .cs to .dll
#-------------------------------------------------------------------------------
Write-Host "Compiling *.cs to *.dll: " -NoNewline
# Compiler options are exactly those of a wix msbuild project.
# https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options
& "$CSC_BIN" /nologo `
/noconfig /nostdlib+ /errorreport:prompt /warn:4 /define:TRACE /highentropyva- `
/debug:pdbonly /filealign:512 /optimize+ /target:library /utf8output `
/reference:"$($ENV:WIX)SDK\Microsoft.Deployment.WindowsInstaller.dll" `
/reference:"$($ENV:WIX)bin\wix.dll" `
/reference:"C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.dll" `
/reference:"C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll" `
/reference:"C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll" `
/reference:"C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.ServiceProcess.dll" `
/reference:"C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Management.dll" `
/nowarn:"1701,1702" `
/out:"$SCRIPT_DIR\CustomAction01\CustomAction01.dll" `
"$SCRIPT_DIR\CustomAction01\CustomAction01.cs" `
"$SCRIPT_DIR\CustomAction01\CustomAction01Util.cs" `
"$SCRIPT_DIR\CustomAction01\Properties\AssemblyInfo.cs"
CheckExitCode
#-------------------------------------------------------------------------------
# Packaging Sandbox DLLs
#-------------------------------------------------------------------------------
Write-Host "Packaging *.dll's to *.CA.dll: " -NoNewline
# MakeSfxCA creates a self-extracting managed MSI CA DLL because
# The custom action DLL will run in a sandbox and needs all DLLs inside. This adds 700 kB.
# Because MakeSfxCA cannot check if Wix will reference a non existing procedure, you must double check yourself.
# Usage: MakeSfxCA <outputca.dll> SfxCA.dll <inputca.dll> [support files ...]
& "$($ENV:WIX)sdk\MakeSfxCA.exe" `
"$SCRIPT_DIR\CustomAction01\CustomAction01.CA.dll" `
"$($ENV:WIX)sdk\x86\SfxCA.dll" `
"$SCRIPT_DIR\CustomAction01\CustomAction01.dll" `
"$($ENV:WIX)SDK\Microsoft.Deployment.WindowsInstaller.dll" `
"$($ENV:WIX)bin\wix.dll" `
"$($ENV:WIX)bin\Microsoft.Deployment.Resources.dll" `
"$SCRIPT_DIR\CustomAction01\CustomAction.config" > build.tmp
CheckExitCode
# move conf folder up one dir because it must not be discovered twice and xslt is difficult
Write-Host "Remove configs from discovery: " -NoNewline
Move-Item -Path "$DISCOVER_CONFDIR" `
-Destination "$($DISCOVER_CONFDIR.Parent.Parent.FullName)\temporarily_moved_conf_folder"
CheckExitCode
Write-Host "Discovering install files: " -NoNewline
# https://wixtoolset.org/documentation/manual/v3/overview/heat.html
# -cg <ComponentGroupName> Component group name (cannot contain spaces e.g -cg MyComponentGroup).
# -sfrag Suppress generation of fragments for directories and components.
# -var WiX variable for SourceDir
# -gg Generate guids now. All components are given a guid when heat is run.
# -sfrag Suppress generation of fragments for directories and components.
# -sreg Suppress registry harvesting.
# -srd Suppress harvesting the root directory as an element.
# -ke Keep empty directories.
# -dr <DirectoryName> Directory reference to root directories (cannot contains spaces e.g. -dr MyAppDirRef).
# -t <xsl> Transform harvested output with XSL file.
# Selectively delete Guid ,so files remain on uninstall.
& "$($ENV:WIX)bin\heat" dir "$($DISCOVER_INSTALLDIR[$i])" `
-out "$SCRIPT_DIR\Product-discovered-files-$($ARCHITECTURE[$i]).wxs" `
-cg DiscoveredBinaryFiles `
-var var.DISCOVER_INSTALLDIR `
-dr INSTALLDIR `
-t "$SCRIPT_DIR\Product-discover-files.xsl" `
-nologo -indent 1 -gg -sfrag -sreg -srd -ke -template fragment
CheckExitCode
# Move the configs back
Write-Host "Restore configs for installation: " -NoNewline
Move-Item -Path "$($DISCOVER_CONFDIR.Parent.Parent.FullName)\temporarily_moved_conf_folder" `
-Destination "$DISCOVER_CONFDIR"
CheckExitCode
# TODO: Config shall remain, so delete all Guid
Write-Host "Discovering config files: " -NoNewline
& "$($ENV:WIX)bin\heat" dir "$DISCOVER_CONFDIR" `
-out "$SCRIPT_DIR\Product-discovered-files-config.wxs" `
-cg DiscoveredConfigFiles `
-var var.DISCOVER_CONFDIR `
-dr CONFDIR `
-t "$SCRIPT_DIR\Product-discover-files-config.xsl" `
-nologo -indent 1 -gg -sfrag -sreg -srd -ke -template fragment
CheckExitCode
Write-Host "Compiling *.wxs to $($ARCHITECTURE[$i]) *.wixobj: " -NoNewline
# Options see "%wix%bin\candle"
Push-Location $SCRIPT_DIR
& "$($ENV:WIX)bin\candle.exe" -nologo -sw1150 `
-arch $ARCHITECTURE[$i] `
-dWIN64="$($WIN64[$i])" `
-dPROGRAMFILES="$($PROGRAMFILES[$i])" `
-dMANUFACTURER="$MANUFACTURER" `
-dPRODUCT="$PRODUCT" `
-dPRODUCTDIR="$PRODUCTDIR" `
-dDisplayVersion="$Version" `
-dInternalVersion="$INTERNAL_VERSION" `
-dDISCOVER_INSTALLDIR="$($DISCOVER_INSTALLDIR[$i])" `
-dWEBCACHE_DIR="$WEBCACHE_DIR" `
-dDISCOVER_CONFDIR="$DISCOVER_CONFDIR" `
-ext "$($ENV:WIX)bin\WixUtilExtension.dll" `
-ext "$($ENV:WIX)bin\WixUIExtension.dll" `
-ext "$($ENV:WIX)bin\WixNetFxExtension.dll" `
"$SCRIPT_DIR\Product.wxs" `
"$SCRIPT_DIR\Product-discovered-files-$($ARCHITECTURE[$i]).wxs" `
"$SCRIPT_DIR\Product-discovered-files-config.wxs" > build.tmp
CheckExitCode
Pop-Location
Write-Host "Linking $PRODUCT-$INTERNAL_VERSION-$($ARCH_AKA[$i]).msi: " -NoNewline
# Options https://wixtoolset.org/documentation/manual/v3/overview/light.html
# Supress LGHT1076 ICE82 warnings caused by the VC++ Runtime merge modules
# https://sourceforge.net/p/wix/mailman/message/22945366/
$installer_name = "$PRODUCTFILE-Py3-$($ARCH_AKA[$i]).msi"
& "$($ENV:WIX)bin\light" `
-nologo -spdb -sw1076 -sice:ICE03 -cultures:en-us `
-out "$SCRIPT_DIR\$installer_name" `
-dDISCOVER_INSTALLDIR="$($DISCOVER_INSTALLDIR[$i])" `
-dDISCOVER_CONFDIR="$DISCOVER_CONFDIR" `
-ext "$($ENV:WIX)bin\WixUtilExtension.dll" `
-ext "$($ENV:WIX)bin\WixUIExtension.dll" `
-ext "$($ENV:WIX)bin\WixNetFxExtension.dll" `
"$SCRIPT_DIR\Product.wixobj" `
"$SCRIPT_DIR\Product-discovered-files-$($ARCHITECTURE[$i]).wixobj" `
"$SCRIPT_DIR\Product-discovered-files-config.wixobj"
CheckExitCode
Remove-Item *.wixobj
#-------------------------------------------------------------------------------
# Move installer to build directory
#-------------------------------------------------------------------------------
if ( ! (Test-Path -Path "$BUILD_DIR") ) {
New-Item -Path "$BUILD_DIR" -ItemType Directory | Out-Null
}
if ( Test-Path -Path "$BUILD_DIR\$installer_name" ) {
Write-Host "Backing up existing installer: " -NoNewline
$new_name = "$installer_name.$( Get-Date -UFormat %s ).bak"
Move-Item -Path "$BUILD_DIR\$installer_name" `
-Destination "$BUILD_DIR\$new_name"
if ( Test-Path -Path "$BUILD_DIR\$new_name" ) {
Write-Result "Success" -ForegroundColor Green
} else {
Write-Result "Failed" -ForegroundColor Red
exit 1
}
}
Write-Host "Moving the Installer: " -NoNewline
Move-Item -Path "$SCRIPT_DIR\$installer_name" -Destination "$BUILD_DIR"
if ( Test-Path -Path "$BUILD_DIR\$installer_name" ) {
Write-Result "Success" -ForegroundColor Green
} else {
Write-Result "Failed" -ForegroundColor Red
exit 1
}
#-------------------------------------------------------------------------------
# Script Complete
#-------------------------------------------------------------------------------
Write-Host $("-" * 80)
Write-Host "Build MSI Installer for Salt Complete" -ForegroundColor Cyan
Write-Host $("=" * 80)

View file

@ -0,0 +1,5 @@
@echo off
del *.wixobj
del CustomAction01\*.pdb
del CustomAction01\*.dll
del Product-discovered-files-*.wxs

View file

@ -0,0 +1 @@
msiexec /i %* /l*v %1-install.log

Binary file not shown.

View file

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View file

@ -0,0 +1,4 @@
@ echo off
pushd tests\config_tests
call clean
popd

View file

@ -0,0 +1,3 @@
rem If you want to test config-tests, do this before building
xcopy /s /y _mock_salt_pkg_windows\buildenv ..\salt\pkg\windows\buildenv\
xcopy /s /y _mock_salt_pkg_windows\build ..\salt\pkg\windows\build\

3
pkg/windows/msi/test.cmd Normal file
View file

@ -0,0 +1,3 @@
pushd tests\config_tests
PowerShell -ExecutionPolicy RemoteSigned -File test.ps1
popd

View file

@ -0,0 +1,22 @@
@echo off
IF exist "c:\salt\conf" (
echo "c:\salt"
dir /b "c:\salt"
echo "c:\salt\conf"
dir /b /s "c:\salt\conf" )
IF exist "C:\ProgramData\Salt Project\salt\conf" (
echo "C:\ProgramData\Salt Project\salt\conf"
dir /b "C:\ProgramData\Salt Project\salt\conf" )
IF exist "C:\Program Files (x86)\Salt Project\salt" (
echo "C:\Program Files (x86)\Salt Project\salt"
dir /b "C:\Program Files (x86)\Salt Project\salt" )
IF exist "C:\Program Files\Salt Project\salt" (
echo "C:\Program Files\Salt Project\salt"
dir /b "C:\Program Files\Salt Project\salt" )
Reg Query "HKLM\SOFTWARE\Salt Project\salt"
sc query salt-minion

View file

@ -0,0 +1,8 @@
# This is the template from the msi store line 1/6
#master: salt
# This is the template from the msi store line 2/6
#id:
# This is the template from the msi store line 3/6
# This is the template from the msi store line 4/6
# This is the template from the msi store line 5/6
# This is the template from the msi store line 6/6

View file

@ -0,0 +1,4 @@
*.bat
*.log
*.output

View file

@ -0,0 +1,22 @@
### Run `../../test-copy_mock_files_to_salt_repo.cmd`
- Use test configuration
### Edit a `*.test` file
- Contains the msi-properties, of which master and minion_id simulate user input via the GUI
- May contain the `dormant` keyword, which means the configuration remains (dormant) after uninstall
- Without the `dormant` keyword, no configuaration must exist after uninstall
### Optionally edit a `*.minion_id` file
- Contains prior configuration by conf/minion_id file
- Omit this file if there is no prior conf/minion_id configuration
### Optionally edit a `*.input` file
- Contains prior configuration by conf/minion file
- Omit this file if there is no prior conf/minion configuration
### Edit a `*.expected` file
- Contains the expected configuration (after install). Any difference will result in failure.
- If the test fails, the resulting configuration will be stored as *.output
### Run `test.cmd`
- Will generate and run *-install.bat and *-uninstall.bat files for each *.test file

View file

@ -0,0 +1,7 @@
@ echo off
del *.bat
del *.install.log
del *.output
del *.uninstall.log
del *.un~

View file

@ -0,0 +1,5 @@
# User custom config line 1/3
master: custom.master
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,5 @@
# User custom config line 1/3
master: custom.master
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,2 @@
properties START_MINION="" CONFIG_TYPE=Custom CUSTOM_CONFIG=tests\config_tests\custom_config.conf
dormant

View file

@ -0,0 +1,5 @@
# User custom config line 1/3
#master: custom.master
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,6 @@
# User custom config line 1/3
#master: custom.master
master: cli.master
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,2 @@
properties START_MINION="" CONFIG_TYPE=Custom CUSTOM_CONFIG=tests\config_tests\custom_config_commented_master_set_master.conf MASTER=cli.master
dormant

View file

@ -0,0 +1,5 @@
# User custom config line 1/3
master: custom.master
# User custom config line 2/3
#id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,6 @@
# User custom config line 1/3
master: custom.master
# User custom config line 2/3
#id: custom.minion
id: cli.minion
# User custom config line 3/3

View file

@ -0,0 +1,2 @@
properties START_MINION="" CONFIG_TYPE=Custom CUSTOM_CONFIG=tests\config_tests\custom_config_commented_minion_set_minion.conf MINION_ID=cli.minion
dormant

View file

@ -0,0 +1,7 @@
# User custom config line 1/3
#master:
#- custom.master1
#- custom.master2
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,8 @@
# User custom config line 1/3
#master:
#- custom.master1
#- custom.master2
master: cli.master
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,2 @@
properties START_MINION="" CONFIG_TYPE=Custom CUSTOM_CONFIG=tests\config_tests\custom_config_commented_multi_master_set_master.conf MASTER=cli.master
dormant

View file

@ -0,0 +1,5 @@
# User custom config line 1/3
master: custom.master
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,5 @@
# User custom config line 1/3
master: cli.master
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,2 @@
properties START_MINION="" CONFIG_TYPE=Custom CUSTOM_CONFIG=tests\config_tests\custom_config_master_set_master.conf MASTER=cli.master
dormant

View file

@ -0,0 +1,5 @@
# User custom config line 1/3
master: custom.master
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,7 @@
# User custom config line 1/3
master:
- cli.master1
- cli.master2
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,2 @@
properties START_MINION="" CONFIG_TYPE=Custom CUSTOM_CONFIG=tests\config_tests\custom_config_master_set_multi_master.conf MASTER=cli.master1,cli.master2
dormant

View file

@ -0,0 +1,5 @@
# User custom config line 1/3
master: custom.master
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,5 @@
# User custom config line 1/3
master: custom.master
# User custom config line 2/3
id: cli.minion
# User custom config line 3/3

View file

@ -0,0 +1,2 @@
properties START_MINION="" CONFIG_TYPE=Custom CUSTOM_CONFIG=tests\config_tests\custom_config_minion_set_minion.conf MINION_ID=cli.minion
dormant

View file

@ -0,0 +1,7 @@
# User custom config line 1/3
master:
- custom.master1
- custom.master2
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,5 @@
# User custom config line 1/3
master: cli.master
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,2 @@
properties START_MINION="" CONFIG_TYPE=Custom CUSTOM_CONFIG=tests\config_tests\custom_config_multi_master_set_master.conf MASTER=cli.master
dormant

View file

@ -0,0 +1,7 @@
# User custom config line 1/3
master:
- custom.master1
- custom.master2
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,7 @@
# User custom config line 1/3
master:
- cli.master1
- cli.master2
# User custom config line 2/3
id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,2 @@
properties START_MINION="" CONFIG_TYPE=Custom CUSTOM_CONFIG=tests\config_tests\custom_config_multi_master_set_multi_master.conf MASTER=cli.master1,cli.master2
dormant

View file

@ -0,0 +1,5 @@
# User custom config line 1/3
#master: custom.master
# User custom config line 2/3
#id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,7 @@
# User custom config line 1/3
#master: custom.master
master: cli.master
# User custom config line 2/3
#id: custom.minion
id: cli.minion
# User custom config line 3/3

View file

@ -0,0 +1,2 @@
properties START_MINION="" CONFIG_TYPE=Custom CUSTOM_CONFIG=tests\config_tests\custom_config_set_minion_master.conf MASTER=cli.master MINION_ID=cli.minion
dormant

View file

@ -0,0 +1,5 @@
# User custom config line 1/3
#master: custom.master
# User custom config line 2/3
#id: custom.minion
# User custom config line 3/3

View file

@ -0,0 +1,9 @@
# User custom config line 1/3
#master: custom.master
master:
- cli.master1
- cli.master2
# User custom config line 2/3
#id: custom.minion
id: cli.minion
# User custom config line 3/3

View file

@ -0,0 +1,2 @@
properties START_MINION="" CONFIG_TYPE=Custom CUSTOM_CONFIG=tests\config_tests\custom_config_set_minion_multi_master.conf MASTER=cli.master1,cli.master2 MINION_ID=cli.minion
dormant

View file

@ -0,0 +1,9 @@
# This is the template from the msi store line 1/6
#master: salt
master: cli.master
# This is the template from the msi store line 2/6
#id:
# This is the template from the msi store line 3/6
# This is the template from the msi store line 4/6
# This is the template from the msi store line 5/6
# This is the template from the msi store line 6/6

View file

@ -0,0 +1,2 @@
properties CONFIG_TYPE=Default START_MINION="" MASTER=cli.master
dormant

View file

@ -0,0 +1,10 @@
# This is the template from the msi store line 1/6
#master: salt
master: cli.master.oldrootdir
# This is the template from the msi store line 2/6
#id:
id: cli.minion.oldrootdir
# This is the template from the msi store line 3/6
# This is the template from the msi store line 4/6
# This is the template from the msi store line 5/6
# This is the template from the msi store line 6/6

View file

@ -0,0 +1,2 @@
properties START_MINION="" MASTER=cli.master.oldrootdir MINION_ID=cli.minion.oldrootdir INSTALLDIR=c:\salt ROOTDIR=c:\salt
dormant

View file

@ -0,0 +1,9 @@
# This is the template from the msi store line 1/6
#master: salt
# This is the template from the msi store line 2/6
#id:
id: cli.minion
# This is the template from the msi store line 3/6
# This is the template from the msi store line 4/6
# This is the template from the msi store line 5/6
# This is the template from the msi store line 6/6

View file

@ -0,0 +1,2 @@
properties CONFIG_TYPE=Default START_MINION="" MINION_ID=cli.minion
dormant

View file

@ -0,0 +1,2 @@
master: Anna
id: Bob

View file

@ -0,0 +1 @@
properties START_MINION="" MINION_CONFIG="master: Anna^id: Bob"

View file

@ -0,0 +1,11 @@
# This is the template from the msi store line 1/6
#master: salt
master:
- cli.master1
- cli.master2
# This is the template from the msi store line 2/6
#id:
# This is the template from the msi store line 3/6
# This is the template from the msi store line 4/6
# This is the template from the msi store line 5/6
# This is the template from the msi store line 6/6

View file

@ -0,0 +1,2 @@
properties CONFIG_TYPE=Default START_MINION="" MASTER=cli.master1,cli.master2
dormant

View file

@ -0,0 +1,11 @@
# This is the template from the msi store line 1/6
#master: salt
master:
- cli.master1
- cli.master2
# This is the template from the msi store line 2/6
#id:
# This is the template from the msi store line 3/6
# This is the template from the msi store line 4/6
# This is the template from the msi store line 5/6
# This is the template from the msi store line 6/6

Some files were not shown because too many files have changed in this diff Show more