mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Add MSI Support
This commit is contained in:
parent
689882d2d3
commit
76731ff26f
139 changed files with 4288 additions and 326 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
3
pkg/windows/install_wix.cmd
Normal file
3
pkg/windows/install_wix.cmd
Normal 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
106
pkg/windows/install_wix.ps1
Normal 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)
|
|
@ -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
|
||||
}
|
|
@ -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"}
|
||||
}
|
32
pkg/windows/msi/CustomAction01/CustomAction.config
Normal file
32
pkg/windows/msi/CustomAction01/CustomAction.config
Normal 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>
|
779
pkg/windows/msi/CustomAction01/CustomAction01.cs
Normal file
779
pkg/windows/msi/CustomAction01/CustomAction01.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
68
pkg/windows/msi/CustomAction01/CustomAction01.md
Normal file
68
pkg/windows/msi/CustomAction01/CustomAction01.md
Normal 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();
|
||||
}
|
||||
}
|
225
pkg/windows/msi/CustomAction01/CustomAction01Util.cs
Normal file
225
pkg/windows/msi/CustomAction01/CustomAction01Util.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
38
pkg/windows/msi/CustomAction01/Properties/AssemblyInfo.cs
Normal file
38
pkg/windows/msi/CustomAction01/Properties/AssemblyInfo.cs
Normal 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")]
|
3
pkg/windows/msi/CustomAction01/debug_dotnetframework_csharp/.gitignore
vendored
Normal file
3
pkg/windows/msi/CustomAction01/debug_dotnetframework_csharp/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.vs/
|
||||
bin/
|
||||
obj/
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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")]
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
|
236
pkg/windows/msi/Product-README.md
Normal file
236
pkg/windows/msi/Product-README.md
Normal 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)
|
||||
|
36
pkg/windows/msi/Product-discover-files-README.md
Normal file
36
pkg/windows/msi/Product-discover-files-README.md
Normal 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
|
||||
|
||||
|
48
pkg/windows/msi/Product-discover-files-config.xsl
Normal file
48
pkg/windows/msi/Product-discover-files-config.xsl
Normal 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>
|
49
pkg/windows/msi/Product-discover-files.xsl
Normal file
49
pkg/windows/msi/Product-discover-files.xsl
Normal 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
529
pkg/windows/msi/Product.wxs
Normal 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 "[CONFIG_TYPE]". Please use "Existing", "Custom" or "Default".">
|
||||
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="&Start salt-minion service immediately" />
|
||||
<Control Id="HideInARP" Type="CheckBox" X="25" Y="165" Width="280" Height="15" Property="ARPSYSTEMCOMPONENT" CheckBoxValue="1" Text="&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&efore upgrade" />
|
||||
<Control Id="remove_conf" Type="CheckBox" X="244" Y="180" Width="60" Height="15" Property="REMOVE_CONFIG" CheckBoxValue="1" Text="&On uninstall" />
|
||||
<Control Id="move_conf" Type="CheckBox" X="25" Y="195" Width="280" Height="15" Property="MOVE_CONF" CheckBoxValue="1" Text="&Move configuration from "C:\salt" to "C:\ProgramData\Salt Project"">
|
||||
<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>
|
85
pkg/windows/msi/README-how-to-build.md
Normal file
85
pkg/windows/msi/README-how-to-build.md
Normal 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
105
pkg/windows/msi/README.md
Normal 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.
|
360
pkg/windows/msi/build_pkg.ps1
Normal file
360
pkg/windows/msi/build_pkg.ps1
Normal 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)
|
5
pkg/windows/msi/clean.cmd
Normal file
5
pkg/windows/msi/clean.cmd
Normal file
|
@ -0,0 +1,5 @@
|
|||
@echo off
|
||||
del *.wixobj
|
||||
del CustomAction01\*.pdb
|
||||
del CustomAction01\*.dll
|
||||
del Product-discovered-files-*.wxs
|
1
pkg/windows/msi/install.cmd
Normal file
1
pkg/windows/msi/install.cmd
Normal file
|
@ -0,0 +1 @@
|
|||
msiexec /i %* /l*v %1-install.log
|
BIN
pkg/windows/msi/pkg_resources/LICENSE.rtf
Normal file
BIN
pkg/windows/msi/pkg_resources/LICENSE.rtf
Normal file
Binary file not shown.
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
BIN
pkg/windows/msi/pkg_resources/Product-imgLeft.png
Normal file
BIN
pkg/windows/msi/pkg_resources/Product-imgLeft.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
pkg/windows/msi/pkg_resources/Product-imgTop.jpg
Normal file
BIN
pkg/windows/msi/pkg_resources/Product-imgTop.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
4
pkg/windows/msi/test-clean.cmd
Normal file
4
pkg/windows/msi/test-clean.cmd
Normal file
|
@ -0,0 +1,4 @@
|
|||
@ echo off
|
||||
pushd tests\config_tests
|
||||
call clean
|
||||
popd
|
3
pkg/windows/msi/test-copy_mock_files_to_salt_repo.cmd
Normal file
3
pkg/windows/msi/test-copy_mock_files_to_salt_repo.cmd
Normal 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
3
pkg/windows/msi/test.cmd
Normal file
|
@ -0,0 +1,3 @@
|
|||
pushd tests\config_tests
|
||||
PowerShell -ExecutionPolicy RemoteSigned -File test.ps1
|
||||
popd
|
22
pkg/windows/msi/test_dirs_reg_service.cmd
Normal file
22
pkg/windows/msi/test_dirs_reg_service.cmd
Normal 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
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
::
|
0
pkg/windows/msi/tests/_mock_salt_pkg_windows/buildenv/var/cache/salt/minion/proc/empty.txt
vendored
Normal file
0
pkg/windows/msi/tests/_mock_salt_pkg_windows/buildenv/var/cache/salt/minion/proc/empty.txt
vendored
Normal file
4
pkg/windows/msi/tests/config_tests/.gitignore
vendored
Normal file
4
pkg/windows/msi/tests/config_tests/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
*.bat
|
||||
*.log
|
||||
*.output
|
||||
|
22
pkg/windows/msi/tests/config_tests/README.md
Normal file
22
pkg/windows/msi/tests/config_tests/README.md
Normal 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
|
7
pkg/windows/msi/tests/config_tests/clean.cmd
Normal file
7
pkg/windows/msi/tests/config_tests/clean.cmd
Normal file
|
@ -0,0 +1,7 @@
|
|||
@ echo off
|
||||
del *.bat
|
||||
del *.install.log
|
||||
del *.output
|
||||
del *.uninstall.log
|
||||
del *.un~
|
||||
|
5
pkg/windows/msi/tests/config_tests/custom_config.conf
Normal file
5
pkg/windows/msi/tests/config_tests/custom_config.conf
Normal 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
|
|
@ -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
|
2
pkg/windows/msi/tests/config_tests/custom_config.test
Normal file
2
pkg/windows/msi/tests/config_tests/custom_config.test
Normal file
|
@ -0,0 +1,2 @@
|
|||
properties START_MINION="" CONFIG_TYPE=Custom CUSTOM_CONFIG=tests\config_tests\custom_config.conf
|
||||
dormant
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
properties CONFIG_TYPE=Default START_MINION="" MASTER=cli.master
|
||||
dormant
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
properties START_MINION="" MASTER=cli.master.oldrootdir MINION_ID=cli.minion.oldrootdir INSTALLDIR=c:\salt ROOTDIR=c:\salt
|
||||
dormant
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
properties CONFIG_TYPE=Default START_MINION="" MINION_ID=cli.minion
|
||||
dormant
|
|
@ -0,0 +1,2 @@
|
|||
master: Anna
|
||||
id: Bob
|
|
@ -0,0 +1 @@
|
|||
properties START_MINION="" MINION_CONFIG="master: Anna^id: Bob"
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
properties CONFIG_TYPE=Default START_MINION="" MASTER=cli.master1,cli.master2
|
||||
dormant
|
|
@ -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
Loading…
Add table
Reference in a new issue