# Do not use Requires -RunAsAdministrator
# Do not use requires -Version 5.1 # For [<TYPE>] syntax
# They are not logged by Start-Transcript

# Use Common parameters
# https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_commonparameters
[CmdletBinding()]
param()

Set-StrictMode -Version 3

[cultureinfo]::CurrentUICulture = "en-US"

# Used to compute SHA256
# Contains script path when running .ps1 directly
# Contains script content when running .exe
$self = $MyInvocation.MyCommand.Definition

$LogFile = Join-Path -Path $env:TEMP -childPath "install-minion.log"
$LogPrefix = "installMinion:"
$config = @{}
$installedMinion = @{}

function main()
{
    setupLogging

    $selfHash = sha256 $self
    $fileVersion = ( Get-Item -Path ( getSelfPath ) ).VersionInfo.FileVersion

    log "------------------------------------------"
    if ( $fileVersion )
    {
        log "Version: $fileVersion"
    }
    log "SHA256: $selfHash"

    checkEnvironment
    Set-ExecutionPolicy Bypass -Scope Process -Force

    $config = getConfig
    $installedMinion = getInstalledMinion

    if ( $installedMinion["version"] )
    {
        log "------------------------------------------"
        log "Check if installed minion is up-to-date..."
        if ( $config["version"].toString().startsWith($installedMinion["version"]) )
        {
            throw [InstallMinionNotForMeException]::new(
                "The version '$($installedMinion['version'])' is already installed"
            )
        }
        elseif ( $config["version"] -lt $installedMinion["version"] )
        {
            throw [InstallMinionConfigException]::new(
                "The version '$($installedMinion['version'])' can't be downgraded to older version '$($config['version'])'"
            )
        }
        else
        {
            log "The version '$($installedMinion['version'])' must be upgraded to new version '$($config['version'])'"
        }

        unInstallMinion $installedMinion
    }
    elseif ( $installedMinion["service"] )
    {
        $serviceName = $installedMinion["service"].getValue("DisplayName")
        log "The service '$serviceName' exists without installer => remove!"

        # Try to uninstall properly with uninst.exe
        # uninst.exe may be in same directory as ssm.exe or in parent directory
        # See: https://github.com/saltstack/salt/issues/67949
        $servicePath = ( $installedMinion["service"].getValue("ImagePath") ) -replace "[`"']",""
        foreach ( $uninstPath in ( Split-Path -parent $servicePath ), ( ( Split-Path -parent $servicePath ) | Split-Path -parent ) )
        {
            $uninst = Join-Path -Path $uninstPath -childPath "uninst.exe"
            verbose "Lookup 'uninst.exe' in '$uninstPath'"
            if ( Test-Path $uninst ) {
                verbose "Uninstaller '$uninst' found"
                $installedMinion["UninstallString"] = $uninst
            }
        }

        $config_dir = Join-Path -path $env:ProgramData -childPath "Salt Project\Salt\conf"
        if ( Test-Path $config_dir )
        {
            verbose "Salt configuration dir '$config_dir' found"
            $installedMinion["config_dir"] = $config_dir
        }
        unInstallMinion $installedMinion
    }

    downloadInstaller $config
    $newMinion = installMinion $config

    if ( $installedMinion.containsKey("version") -and $installedMinion["version"] -lt "3004.0" -and $newMinion["version"] -ge "3004.0" ) {
        moveMinionDirs $newMinion $installedMinion
    }

    configMinion $newMinion
}

function checkEnvironment()
{
    log "------------------------------------------"
    log "Check environment compatibility..."

    $major = $PSVersionTable.PSVersion.Major
    log "ps version : $Major"
    if ( $Major -le 4 )
    {
        throw [InstallMinionEnvironmentException]::new(
            "You must use PowerShell 4.0 or above instead of '$Major'"
        )
    }

    $build = $PSVersionTable.PSVersion.Build
    log "OS Build : $build"

    #############################################################################
    # .Net Framework
    #############################################################################
    $release = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Release
    log ".NetFramework release = $release"
    $NetFramework  = "$(
        switch ($release) {
            ({ $_ -ge 533320 }) { '4.8.1'; break }
            ({ $_ -ge 528040 }) { '4.8'; break }
            ({ $_ -ge 461808 }) { '4.7.2'; break }
            ({ $_ -ge 461308 }) { '4.7.1'; break }
            ({ $_ -ge 460798 }) { '4.7'; break }
            ({ $_ -ge 394802 }) { '4.6.2'; break }
            ({ $_ -ge 394254 }) { '4.6.1'; break }
            ({ $_ -ge 393295 }) { '4.6'; break }
            ({ $_ -ge 379893 }) { '4.5.2'; break }
            ({ $_ -ge 378675 }) { '4.5.1'; break }
            ({ $_ -ge 378389 }) { '4.5'; break }
            default { '4.5+ not installed.' }
        }
    )"

    log "NetFramework = $NetFramework"

    #############################################################################
    # OS
    #############################################################################
    # 1 you are on a workstation OS.
    # 2 you're on a domain controller.
    # 3 you're on a server that is not a domain controller.
    log "Check if os type is workstation..."
    $osInfo = Get-WmiObject -Class Win32_OperatingSystem
    $productType = $osInfo.ProductType
    log "ProductType = $productType"
    if ( $productType -ne 1 )
    {
        throw [InstallMinionNotForMeException]::new(
            "Computer is not a workstation, abort!"
        )
    }

    #############################################################################
    # Elevated privileged is required
    #############################################################################
    log "Check if session is elevated..."
    $isAdmin=( [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if ( -Not $isAdmin )
    {
        throw [InstallMinionEnvironmentException]::new(
            "You are not 'Administrator' or 'Elevated' session"
        )
    }
    else
    {
        log "You are in 'Elevated' session, OK"
    }

    #############################################################################
    # DNS query must find `salt`
    #############################################################################
    log "Check 'salt' dns resolution..."
    $i=1
    $testConnection = Test-Connection -ComputerName salt -Count 1 -ErrorAction SilentlyContinue
    while( $i -le 3 -and -Not $testConnection )
    {
        $i++
        log "DNS lookup of 'salt' does not work (try $i/3)"
        Start-Sleep -s 2
        $testConnection = Test-Connection -ComputerName salt -Count 1 -ErrorAction SilentlyContinue
    }
    if ( $testConnection )
    {
        log "DNS lookup of 'salt' is working"
    }
    else
    {
        throw [InstallMinionException]::new(
            "DNS lookup of 'salt' does not work. Check your DNS configuration or DNS record."
        )
    }

    $ip = $testConnection.IPV4Address
    $ipSaltMaster = $ip.IPAddressToString
    if ( -Not $ipSaltMaster )
    {
        throw [InstallMinionException]::new(
            "DNS lookup of 'salt' has incorrect IP address"
        )
    }
    else
    {
        log "ipSaltMaster = $ipSaltMaster"
        $config["saltMasterIP"] = $ipSaltMaster
    }

    #############################################################################
    # Prepare Invoke httprequest for debug
    #############################################################################
    Set-UseUnsafeHeaderParsing -Enable
}

function getConfig()
{
    log "------------------------------------------"
    log "Load installMinion configuration..."
    $saltMinionConfFile = Join-Path -Path $env:TEMP -childPath "installMinion.conf"
    $saltMinionConfUrl  = "http://$($config['saltMasterIP'])/joineole/installMinion.conf"
    doDownload -url $saltMinionConfUrl -file $saltMinionConfFile

    # Be carefull if firewall/proxy return something else...
    $content = Get-Content $saltMinionConfFile -Raw
    $idx = $content.ToLower().indexOf("guardian")
    if ( $idx -gt 0 )
    {
        throw [DownloadProxyError]::new(@"
Bad reply from server.
Check your firewall configuration.
"@)
    }

    # Initialize with current config
    $context = $config
    switch -regex -file $saltMinionConfFile {
        "^#.*" {
            #Write-Host "commentaire"
        }
        "^([^=]+)=(.+?)$" {
            $name, $value = $matches[1..2]
            $context[$name] = $value
            log "config $name = $value"
        }
    }

    $VerbosePreference = if ( $context["verbose"] -eq "1" ) { "Continue" } else { $VerbosePreference }
    $DebugPreference = if ( $context["debug"] -eq "1" ) { "Continue" } else { $DebugPreference }
    setDebug

    #############################################################################
    # Build installer file name and URL
    #############################################################################
    if ( [System.Environment]::Is64BitOperatingSystem )
    {
        $arch = "AMD64"
        if ( -Not $context["salt-version-amd64"] )
        {
            throw [InstallMinionConfigException]::new(
                "Configuration variable 'salt-version-amd64' does not exist."
            )
        }
        # salt-version-amd64 must be mangled to became a `[version]`
        [version]$context["version"] = $context["salt-version-amd64"] -replace '-.*',''
        [string]$context["installer_version"] = $context["salt-version-amd64"] -replace '-Py.*',''
    }
    else
    {
        $arch = "x86"
        if ( -Not $context["salt-version-x86"] )
        {
            throw [InstallMinionConfigException]::new(
                "Configuration variable 'salt-version-x86' does not exist."
            )
        }
        # salt-version-x86 must be mangled to became a `[version]`
        [version]$context["version"] = $context["salt-version-x86"] -replace '-.*',''
        [string]$context["installer_version"] = $context["salt-version-x86"] -replace '-Py.*',''
    }

    log "Wanted Salt version: $($context['version'])"
    $context["setupName"] = "Salt-Minion-$($context['installer_version'])-Py3-$arch-Setup.exe"
    $context["setupUrl"]  = "http://$($context['saltMasterIP'])/joineole/saltstack/$($context['SetupName'])"
    $context["setupFile"] = Join-Path -Path $env:TEMP -childPath $context["SetupName"]

    verbose "New minion configuration"
    verbose ( $context | Out-String )

    return $context
}

function getInstalledMinion()
{
    log "------------------------------------------"
    log "Check installed minion..."

    $context = @{}
    $installerSalt = getInstaller "Salt Minion"

    if ( $installerSalt )
    {
        verbose "Installed minion installer"
        verbose ( regDump $installerSalt | Out-String )

        $context["installer"]        = $installerSalt
        [version]$context["version"] = $installerSalt.GetValue("DisplayVersion") -replace '-.*',''
        $context["UninstallString"]  = $installerSalt.GetValue("UninstallString")

        if ( $context["version"] -lt "3004.0" )
        {
            $context["install_dir"] = $context["UninstallString"].Substring(0, $context["UninstallString"].lastIndexOf("\"))
            $context["root_dir"]    = $context["install_dir"]
        }
        else
        {
            $context["install_dir"] = expandPath ( regGetItemProperty -path "HKLM:Software\Salt Project\Salt" -value "install_dir" )
            $context["root_dir"]    = expandPath ( regGetItemProperty -path "HKLM:Software\Salt Project\Salt" -value "root_dir" )
        }

        if ( Test-Path ( Join-Path -Path $context["install_dir"] -childPath "salt-call.exe" ) )
        {
            $context["salt_call"] = Join-Path -Path $context["install_dir"] -childPath "salt-call.exe"
        }
        elseif ( Test-Path ( Join-Path -Path $context["install_dir"] -childPath "salt-call.bat" ) )
        {
            $context["salt_call"] = Join-Path -Path $context["install_dir"] -childPath "salt-call.bat"
        }

        $context["config_dir"]  = Join-Path -Path $context["root_dir"] -childPath "conf"
        $context["var_dir"]     = Join-Path -Path $context["root_dir"] -childPath "var"
    }
    else
    {
        log "No minion package installed"
    }

    $serviceSalt = getService "salt-minion"
    if ( $serviceSalt )
    {
        verbose "Installed minion service"
        verbose ( regDump $serviceSalt | Out-String )
        $context["service"] = $serviceSalt
    }
    else
    {
        log "No minion service found"
    }

    if ( $context.ContainsKey("installer") )
    {
        log "Installed minion version: $($context['installer'].getValue('DisplayVersion'))"
    }

    if ( $context.Count -ne 0 )
    {
        verbose "Installed minion configuration"
        verbose ( $context | Out-String )
    }

    return $context
}

function getInstaller( [string] $name )
{
    debug "getInstaller for $name"
    return regGetItemProperty -path "HKLM:Software\Microsoft\Windows\CurrentVersion\Uninstall\$name"
}

function getService( [string] $name )
{
    debug "getService for $name"
    return regGetItemProperty -path "HKLM:System\CurrentControlSet\services\$name"
}

function unInstallMinion( [hashtable] $minion )
{
    log "------------------------------------------"
    log "Uninstall minion '$($minion['version'])' with '$($minion['UninstallString'])'..."

    try
    {
        $minion_file = Join-Path -Path $minion["config_dir"] -childPath "minion"
        $minion_d = Join-Path -Path $minion["config_dir"] -childPath "minion.d"

        $serviceName = $minion["service"].getValue("DisplayName") -replace "[`"']",""
        $servicePath = $minion["service"].getValue("ImagePath") -replace "[`"']",""

        # Spawn uninstaller and wait for termination
        Start-Process -FilePath $minion["UninstallString"] -ArgumentList "/S" -Wait

        log "Uninstall done"

        if ( Test-Path $servicePath )
        {
            log "Remove orphan service '$serviceName'"
            sc.exe delete $serviceName
        }

        log "Clean minion configuration"

        log "Remove '$minion_file' file"
        Remove-Item -path $minion_file -ErrorAction SilentlyContinue

        log "Remove '$minion_d' directory"
        Remove-Item  -recurse -path $minion_d -ErrorAction SilentlyContinue
    }
    catch
    {
        throw [InstallMinionUninstallException]::new(
            "Error during uninstall: $($PSItem.ToString())",
            $PSItem.Exception
        )
    }
}

function moveMinionDirs( [hashtable] $newMinion, [hashtable] $oldMinion )
{
    log "------------------------------------------"
    log "Move '$($oldMinion['version'])' directories..."

    try
    {
        if ( -Not ( Test-Path $newMinion["root_dir"] ) )
        {
            log "Create new installation root directory '$($newMinion['root_dir'])'"
            verbose ( md $newMinion["root_dir"] | Out-String )
        }

        log "Move '$($oldMinion['config_dir'])' to '$($newMinion['config_dir'])'"
        if ( Test-Path $newMinion["config_dir"] )
        {
            verbose "Remove '$($newMinion['config_dir'])'"
            Remove-Item -Recurse -Force -Path $newMinion["config_dir"]
        }
        Move-Item -Force -Path $oldMinion["config_dir"] -Destination $newMinion["config_dir"]

        log "Move '$($oldMinion['var_dir'])' to '$($newMinion['var_dir'])'"
        if ( Test-Path $newMinion["var_dir"] )
        {
            verbose "Remove '$($newMinion['var_dir'])'"
            Remove-Item -Recurse -Force -Path $newMinion["var_dir"]
        }
        Move-Item -Force -Path $oldMinion["var_dir"] -Destination $newMinion["var_dir"]

        if ( -Not ( Test-Path "$($oldMinion['root_dir'])\*" ) )
        {
            log "Clean old empty directory '$($oldMinion['root_dir'])'"
            Remove-Item $oldMinion["root_dir"]
        }
    }
    catch
    {
        throw [InstallMinionUninstallException]::new(
            "Can't move old directories: $($PSItem.ToString())"
        )
    }
}

function downloadInstaller( [hashtable] $minion )
{
    log "------------------------------------------"
    log "Download salt installer..."
    if ( Test-Path $minion["setupFile"] )
    {
        log "File '$($minion['setupFile'])' already downloaded"
    }
    else
    {
        doDownload -url $minion["setupUrl"] -file $minion["setupFile"]
        Unblock-File -Path $minion["setupFile"]
    }
}

function installMinion( [hashtable] $minion )
{
    log "------------------------------------------"
    log "Install minion with '$($minion['setupFile'])'..."

    Start-Process -FilePath $minion["setupFile"] -ArgumentList "/S /start-minion=0" -Wait

    $newMinion = getInstalledMinion

    if ( -Not $newMinion )
    {
        throw [InstallMinionInstallException]::new(
            "Critical error: salt-minion service does not exist after installation."
        )
    }

    log "Service 'salt-minion' exists"

    if ( Test-Path "$($newMinion['salt_call'])" )
    {
        log "salt-call path '$($newMinion['salt_call'])'."
    }
    else
    {
        throw [InstallMinionInstallException]::new(
            "'$($newMinion['salt_call'])' does not exist."
        )
    }

    return $newMinion
}

function configMinion( [hashtable] $minion )
{
    log "------------------------------------------"
    log "Configure salt-minion..."
    $grainsConfPath = Join-Path -Path $minion["config_dir"] -childPath "grains"

    if ( -Not ( Test-Path $grainsConfPath ) )
    {
        log "Add roles 'ad/member', 'veyon/master' and 'veyon/client'"
        & "$($minion['salt_call'])" --local grains.append roles '["ad/member", "veyon/master", "veyon/client"]'
    }
    else
    {
        log "salt-minion is already configured."
    }

    log "Lookup 'roles' grain"
    & "$($minion['salt_call'])" --local grains.get roles | Write-Host

    log "Read '$grainsConfPath'"
    log ( Get-Content "$grainsConfPath" | Out-String )

    $serviceName = $minion["service"].getValue("DisplayName")
    log "Set service to automatic startup"
    Set-Service -StartupType Automatic $serviceName
    Start-Service $serviceName

    log "salt-minion configured"
}

function doDownload( [string] $url, [string] $file )
{
    debug "Download '$url' to '$file'"

    if ( Test-Path $file ) {
        Remove-Item -Path $file
    }

    try
    {
        $global:ProgressPreference = 'SilentlyContinue'
        [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
        $wc = New-Object Net.WebClient
        $wc.UseDefaultCredentials = $true
        $wc.Proxy = $null
        #$wc.Proxy.Credentials = $wc.Credentials
        $wc.Encoding = [System.Text.Encoding]::UTF8
        $wc.CachePolicy = New-Object Net.Cache.HttpRequestCachePolicy([System.Net.Cache.HttpRequestCacheLevel]::NoCacheNoStore)

        log "Download WebClient $url"
        $wc.DownloadFile($url, $file)
    }
    catch
    {
        $_ | Out-Host # Output the thread pipeline error
        $StatusCode = $_.Exception.Response.StatusCode.value__
        log "StatusCode = $StatusCode"
        log "ERROR: download WebClient '$url'. Stop!"
    }

    if ( -Not ( Test-Path $file ) )
    {
        # Win 7, 8, 2012 ... <= Win 10 !
        try
        {
            # avec DisableKeepAlive !
            log "Invoke-WebRequest $url"
            $r = Invoke-WebRequest -Uri $url -OutFile $file -Method Get -DisableKeepAlive -UseDefaultCredentials -useBasicParsing
            $StatusCode = $Response.StatusCode
            log "StatusCode = $StatusCode"
        }
        catch
        {
            $_ | Out-Host # Output the thread pipeline error
            $StatusCode = $_.Exception.Response.StatusCode.value__
            log "StatusCode = $StatusCode"
            log "ERROR: download Invoke-WebRequest '$url'. Stop!"
        }
    }

    if ( -Not ( Test-Path $file ) )
    {
        throw [InstallMinionDownloadException]::new("Unable to download '$url'. Stop!")
    }

    log "'$file' downloaded"
}

function regGetItemProperty( [string] $path, [string] $value )
{
    debug "regGetItemProperty lookup -path '$path' -value '$value'"

    if ( [System.Environment]::Is64BitProcess )
    {
        $regObject = regGetItemProperty64($path)
        if ( $regObject -eq $null )
        {
            debug "regGetItemProperty: no 64 bits native '$path', lookup in 32 bits hive"
            $regObject = regGetItemProperty32($path)
        }
    }
    elseif ( [System.Environment]::Is64BitOperatingSystem )
    {
        $regObject = regGetItemProperty64($path)
        if ( $regObject -eq $null )
        {
            debug "regGetItemProperty: no WOW6432 bits '$path', lookup in 64 bits hive"
            $regObject = regGetItemProperty32($path)
        }
    }
    else {
        debug "regGetItemProperty: 32 bits process on 32 bits system"
        regGetItemProperty32($path)
    }

    if ( $regObject -and $value )
    {
        $ret = $regObject.getValue($value, $null, 1)
        debug "regGetItemProperty: '$path' '$value' = '$ret'"
        return $ret
    }
    else
    {
        return $regObject
    }
}

function regGetItemProperty32( [string] $path )
{
    $noHivePath = $path -replace '^HKLM:\\?',''
    $HKLM32 = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry32)
    return $HKLM32.OpenSubKey($noHivePath)
}

function regGetItemProperty64( [string] $path )
{
    $noHivePath = $path -replace '^HKLM:\\?',''
    $HKLM64 = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry64)
    return $HKLM64.OpenSubKey($noHivePath)
}

function regDump( $regObject )
{
    $object = @{}
    if ( $regObject -ne $null )
    {
        foreach ( $valueName in $regObject.getValueNames() )
        {
            $object[$valueName] = $regObject.getValue($valueName)
        }
    }

    return $object
}

function expandPath( [string] $path )
{
    $expandedPath = $path
    foreach ( $program_files in "ProgramFiles", "ProgramFiles(x86)", "ProgramW6432" )
    {
        if ( Test-Path ( $path -replace "%ProgramFiles%",[Environment]::GetEnvironmentVariable($program_files) ) )
        {
            $expandedPath = $expandedPath -replace "%ProgramFiles%",[Environment]::GetEnvironmentVariable($program_files)
        }
    }
    return $expandedPath -replace  "%ProgramData%",${env:ProgramData}
}

function getSelfPath()
{
    if ( $PSCommandPath -ne "" ) {
        $psScriptPath = $PSCommandPath
    } else {
        # This enables the script to be compiles and get the directory of it.
        $psScriptPath = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName
    }
    return $psScriptPath
}

function sha256( $string )
{
    if ( Test-Path($string) )
    {
        $hash = Get-FileHash -Algorithm SHA256 -Path $string
        return $hash.Hash
    }
    else
    {
        $hasher = [System.Security.Cryptography.HashAlgorithm]::Create('sha256')
        $hashBytes = $hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($string + "`n" -replace "`r"))
        return [BitConverter]::ToString($hashBytes) -replace '-',''
    }
}

function setupLogging()
{
    setDebug
    Start-Transcript -Path $LogFile
}

function setDebug()
{
    if ( $DebugPreference -ne "SilentlyContinue" )
    {
        Set-PSDebug -Trace 1
    }
}

# tips: https://stackoverflow.com/a/35260673
function Set-UseUnsafeHeaderParsing
{
    param(
        [Parameter(Mandatory,ParameterSetName="Enable")]
        [switch]$Enable,

        [Parameter(Mandatory,ParameterSetName="Disable")]
        [switch]$Disable
    )

    $ShouldEnable = $PSCmdlet.ParameterSetName -eq "Enable"

    $netAssembly = [Reflection.Assembly]::GetAssembly([System.Net.Configuration.SettingsSection])

    if ( $netAssembly )
    {
        $bindingFlags = [Reflection.BindingFlags] "Static,GetProperty,NonPublic"
        $settingsType = $netAssembly.GetType("System.Net.Configuration.SettingsSectionInternal")

        $instance = $settingsType.InvokeMember("Section", $bindingFlags, $null, $null, @())

        if ( $instance )
        {
            $bindingFlags = "NonPublic","Instance"
            $useUnsafeHeaderParsingField = $settingsType.GetField("useUnsafeHeaderParsing", $bindingFlags)

            if ( $useUnsafeHeaderParsingField )
            {
                $useUnsafeHeaderParsingField.SetValue($instance, $ShouldEnable)
            }
        }
    }
}

function log {
    Param([string] $message)
    Write-Host "$logPrefix $message" @Args
}

function verbose {
    Param([string] $message)
    Write-Verbose -Message "$logPrefix $message" @Args
}

function warning {
    Param([string] $message)
    Write-Warning -Message "$logPrefix $message" @Args
}

function debug {
    Param([string] $message)
    Write-Debug -Message "$logPrefix $message" @Args
}

class InstallMinionException : Exception
{
    InstallMinionException() : base() {}
    InstallMinionException([string] $message) : base($message) {}
    InstallMinionException([string] $message, [Exception] $inner) : base($message, $inner) {}
}

class InstallMinionNotForMeException : InstallMinionException
{
    InstallMinionNotForMeException() : base() {}
    InstallMinionNotForMeException([string] $message) : base($message) {}
    InstallMinionNotForMeException([string] $message, [Exception] $inner) : base($message, $inner) {}
}

class InstallMinionEnvironmentException : InstallMinionException
{
    InstallMinionEnvironmentException() : base() {}
    InstallMinionEnvironmentException([string] $message) : base($message) {}
    InstallMinionEnvironmentException([string] $message, [Exception] $inner) : base($message, $inner) {}
}

class InstallMinionConfigException : InstallMinionException
{
    InstallMinionConfigException() : base() {}
    InstallMinionConfigException([string] $message) : base($message) {}
    InstallMinionConfigException([string] $message, [Exception] $inner) : base($message, $inner) {}
}

class InstallMinionUninstallException : InstallMinionException
{
    InstallMinionUninstallException() : base() {}
    InstallMinionUninstallException([string] $message) : base($message) {}
    InstallMinionUninstallException([string] $message, [Exception] $inner) : base($message, $inner) {}
}

class InstallMinionDownloadException : InstallMinionException
{
    InstallMinionDownloadException() : base() {}
    InstallMinionDownloadException([string] $message) : base($message) {}
    InstallMinionDownloadException([string] $message, [Exception] $inner) : base($message, $inner) {}
}

class InstallMinionDownloadProxyException : InstallMinionDownloadException
{
    InstallMinionDownloadProxyException() : base() {}
    InstallMinionDownloadProxyException([string] $message) : base($message) {}
    InstallMinionDownloadProxyException([string] $message, [Exception] $inner) : base($message, $inner) {}
}

class InstallMinionInstallException : InstallMinionException
{
    InstallMinionInstallException() : base() {}
    InstallMinionInstallException([string] $message) : base($message) {}
    InstallMinionInstallException([string] $message, [Exception] $inner) : base($message, $inner) {}
}


####
#### Execute main
####
try
{
    Push-Location
    Set-Location $env:TEMP
    main
}
catch [InstallMinionNotForMeException]
{
    log $PSItem.ToString()
    exit 0
}
catch
{
    log $PSItem.ToString()
    exit -1
}
finally {
    Pop-Location
    Set-PSDebug -Off

    Stop-Transcript -ErrorAction SilentlyContinue
}
