Initialize.ps1

#Requires -Version 3

function Initialize-TelligentInstanceManager {
[CmdletBinding(SupportsShouldProcess=$true)]
param(
    [Parameter(HelpMessage="The path where Telligent Instances Manager will store instances and builds.")]
    [string]$InstallDirectory,
    [string]$DatabaseServerInstance,
    [Parameter(Mandatory=$false, HelpMessage="The path where Tomcat is installed. Used to add Tomcat contexts used for Solr multi core setup.")]
    [ValidateScript({$_ -and (Test-TomcatPath $_) })]
    [string]$TomcatDirectory,
    [switch]$SkipSearch,
    [switch]$Force
)

#Banner
Write-Telligent
Write-Host "Telligent Instance Manager"
Write-Host

#Test Prerequisites
$initialErrorCount = $Error.Count
Write-Progress "Telligent Instance Manager Setup" "Checking Prerequisites" -CurrentOperation "This may take a few moments"
if(!$InstallDirectory){
    $InstallDirectory = $env:TelligentInstanceManager
    if(!(Test-Path $InstallDirectory)) {
        Write-Error 'Could not auto-detect Install location. Please run the command with the -InstallDirectory paramater to specify this location manually'
    }
}

if(!$DatabaseServerInstance) {
    if([string]::IsNullOrEmpty($env:TelligentDatabaseServerInstance)) { $DatabaseServerInstance = "(local)" } else { $DatabaseServerInstance= $env:TelligentDatabaseServerInstance }
}

if (!$TomcatDirectory) {
    $TomcatDirectory = Get-TomcatLocation
    if (!$TomcatDirectory) {
        Write-Error 'Could not auto-detect Tomcat location. Please run the command with the -TomcatDirectory paramater to specify this location manually'
    }
}
else {
    if (!(Test-TomcatPath $TomcatDirectory)) {
        Write-Error "'$TomcatDirectory' does not contain a valid Tomcat instance"
    }
}

Test-Prerequisites $installDirectory
if ($Error.Count -ne $initialErrorCount) {
    throw 'Prerequisites not met (see previous errors for more details)'
}

#Make Required Folders
@('Licenses', 'TelligentPackages', 'Communities', 'Solr') |
    % { Join-Path $InstallDirectory $_} |
    ? {!(Test-Path $_)} |
    % {new-item $_ -ItemType Directory | Out-Null}

if(!$SkipSearch) {
    Install-Solr -InstallDirectory $InstallDirectory -TomcatDirectory $TomcatDirectory -Force:$Force
}

Initalize-Environment $InstallDirectory $DatabaseServerInstance

Write-Progress 'Telligent Instance Manager Setup' -Completed

#Provide hints for finishing installation
$LicensePath = Join-Path $InstallDirectory Licenses 
if (!(Get-ChildItem $LicensePath -ErrorAction SilentlyContinue)){
    Write-Warning "No Licenses available for installation at '$LicensePath' "
    Write-Warning "Add license files to this directory with filenames of the format 'Community8.xml', 'Community9.xml' etc."
}

$packagesPath = Join-Path $InstallDirectory TelligentPackages
if (!(Get-ChildItem $LicensePath -ErrorAction SilentlyContinue)){
    Write-Warning "No packages are available for Installation at '$packagesPath'."
}

Write-Host
Write-Host 'Telligent Instance Manager installation complete' -ForegroundColor Green
Write-Host @"
For more details on how to use the Telligent Instance Manager, use the Get-Help cmdlet to learn more about the following comamnds
* Get-TelligentInstance
* Install-TelligentInstance
* Remove-TelligentInstance
 
"@


}

function Test-Prerequisites
{
    param(
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Path $_ -PathType Container -IsValid})]
        [string]$InstallDirectory
    )
    #Check Admin
    $currentPrincipal = [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
    if (!($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))) {
           Write-Error "Installation requires Administrative credentials"
    }

    #Check for required modules
    @('webadministration','sqlps') |
        ? { !(Get-Module $_ -ListAvailable) } |
        % { Write-Error "Required Module '$_' is not available" }
}

function Get-TomcatLocation {
    @(
        "${env:ProgramFiles}\Apache Software Foundation\Tomcat 9.0"
        "${env:ProgramFiles(x86)}\Apache Software Foundation\Tomcat 9.0"
        "${env:ProgramFiles}\Apache Software Foundation\Tomcat 8.5"
        "${env:ProgramFiles(x86)}\Apache Software Foundation\Tomcat 8.5"
        "${env:ProgramFiles}\Apache Software Foundation\Tomcat 8.0"
        "${env:ProgramFiles(x86)}\Apache Software Foundation\Tomcat 8.0"
        "${env:ProgramFiles}\Apache Software Foundation\Tomcat 7.0"
        "${env:ProgramFiles(x86)}\Apache Software Foundation\Tomcat 7.0"
        "${env:ProgramFiles}\Apache Software Foundation\Tomcat 6.0"
        "${env:ProgramFiles(x86)}\Apache Software Foundation\Tomcat 6.0"
    ) |
        ? { Test-TomcatPath $_ } |
        select -First 1
}

function Test-TomcatPath {
    param(
        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, ValueFromPipeline=$true)]
        [string]$TomcatPath
    )
    process {
        (Test-Path $TomcatPath -PathType Container) `
            -and (Join-Path $TomcatPath conf\server.xml | Test-Path -PathType Leaf)
    }
}

function Install-Solr {
    param(
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Path $_ -PathType Container -IsValid})]
        [string]$InstallDirectory,
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-TomcatPath $_ })]
        [string]$TomcatDirectory,
        [switch]$Force
    )

    $tomcatContext = @"
<Context docBase="{0}" debug="0" crossContext="true" >
   <Environment name="solr/home" type="java.lang.String" value="{1}" override="true" />
</Context>
"@

    $legacySolrXml = @"
<?xml version='1.0' encoding='UTF-8'?>
<solr sharedLib="lib" persistent="true">
    <cores adminPath="/admin/cores" />
</solr>
"@

    $modernSolrXml = @"
<?xml version="1.0" encoding="UTF-8" ?>
<solr>
  <solrcloud>
    <str name="host">$`{host:}</str>
    <int name="hostPort">$`{jetty.port:8983}</int>
    <str name="hostContext">$`{hostContext:solr}</str>
    <int name="zkClientTimeout">$`{zkClientTimeout:15000}</int>
    <bool name="genericCoreNodeNames">$`{genericCoreNodeNames:true}</bool>
  </solrcloud>
 
  <shardHandlerFactory name="shardHandlerFactory"
    class="HttpShardHandlerFactory">
    <int name="socketTimeout">$`{socketTimeout:0}</int>
    <int name="connTimeout">$`{connTimeout:0}</int>
  </shardHandlerFactory>
</solr>
"@

    
    $solrBase = Join-Path $InstallDirectory Solr
    $tomcatContextDirectory = Join-Path $TomcatDirectory conf\Catalina\localhost

    if(!(Test-Path $tomcatContextDirectory)) {
        Write-Host "Creating $tomcatContextDirectory"
        New-Item $tomcatContextDirectory -ItemType Directory | Out-Null
    }
    
    @('3-6', '4-5-1', '4-10-3') |% {
        $solrHome = Join-Path $solrBase $_
        $contextPath = Join-Path $tomcatContextDirectory "${_}.xml" 

        $war = Join-Path $solrBase "solr_${_}.war"
        if(!(Test-Path $war) -or $Force) {
            Write-Progress 'Telligent Instance Manager Setup' "Downloading $war"
            $warUri = "https://github.com/RichMercer/TIM-Search/blob/master/solr_${_}.war?raw=true"
            Invoke-WebRequest -Uri $warUri -OutFile $war 
        }        
        
        if ((Test-Path $contextPath) -and !$Force) {
            #Don't treat existing context file as an error - most likely means it's already set up
            Write-Verbose "Not seting up Multi Core Solr $_ Instance - manually ensure this is set up. Context already exists at '$contextPath'"
        }
        elseif ((Test-Path $solrHome) -and !$Force) {
            Write-Warning "Not seting up Multi Core Solr $_ Instance - manually ensure this is set up. SolrHome already exists at '$solrHome'"
        }        
        else {
            Write-Host "Installing Solr $_"
            if(!(Test-Path $solrHome)) {
                New-Item $solrHome -ItemType Directory | Out-Null
            }

            #Create Solr.xml to enable multicore
            $solrXml = if($_ -in '3-6') { $legacySolrXml } else { $modernSolrXml}
            $solrXmlPath = Join-Path $solrHome solr.xml 
            $solrXml | out-file $solrXmlPath -Encoding utf8

            #Create context file
            $tomcatContext -f $war, $solrHome | out-file $contextPath
        }
    }

    Write-Progress 'Telligent Instance Manager Setup' 'Stopping Tocmat'
    Stop-Service tomcat*

    @('jcl-over-slf4j-1.6.6.jar', 'jul-to-slf4j-1.6.6.jar', 'log4j-1.2.16.jar', 'slf4j-api-1.6.6.jar', 'slf4j-log4j12-1.6.6.jar') |% {
        Write-Progress 'Telligent Instance Manager Setup' 'Downloading Tomcat dependencies'
        $tomcatLib= Join-Path "$TomcatDirectory" 'lib'        
        $filePath =  Join-Path $tomcatLib $_      
        if(!(Test-Path $filePath)) {
            Invoke-WebRequest -Uri "https://github.com/RichMercer/TIM-Search/blob/master/_tomcatlib/${_}?raw=true" -OutFile $filePath
        }
    }

    Write-Progress 'Telligent Instance Manager Setup' 'Starting Tocmat' -PercentComplete 50
    Start-Service tomcat* -ErrorAction Continue

    # Install new Solr 6+ Version. Add to the array for each version required to be downloaded and installed.
    @('6-3-0', '7-6-0') |% {
        $FilePath = Join-Path $solrBase "$($_).zip"
        if(!(Test-Path (Join-Path $solrBase $_))) {
            Invoke-WebRequest -Uri "https://github.com/RichMercer/TIM-Search/blob/master/$($_).zip?raw=true" -OutFile $FilePath
    
            Expand-Archive $FilePath $SolrBase
            Remove-Item $FilePath

            $PortNumber = "8$($_.Replace('-', ''))"
            $InstallScript = Join-Path $SolrBase "$($_)/bin/ServiceInstall.ps1"
            &$InstallScript -ServiceName "TIM-Search-$($_)" -DisplayName "TIM Search $($_)" -Port $PortNumber
        }
    }
}

function Initalize-Environment
{
    param(
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Path $_ -PathType Container -IsValid})]
        [string]$InstallDirectory,
        [Parameter(Mandatory=$true)]
        [string]$DatabaseServerInstance
    )
    #Set Package Location
    [Environment]::SetEnvironmentVariable('TelligentInstanceManager', $InstallDirectory)
    [Environment]::SetEnvironmentVariable('TelligentInstanceManager', $InstallDirectory, 'Machine')

    #Set DB Instance Name, if supplied
    [Environment]::SetEnvironmentVariable('TelligentDatabaseServerInstance', $DatabaseServerInstance)
    [Environment]::SetEnvironmentVariable('TelligentDatabaseServerInstance', $DatabaseServerInstance, 'Machine')
}

function Write-Telligent
{
    $hostColor = $host.UI.RawUI.BackgroundColor
    if($hostColor -eq [ConsoleColor]::Black -or $hostColor -match "Dark") {
        $textColour = [ConsoleColor]::White
    }
    else {
        $textColour  = [ConsoleColor]::Black
    }
    function Write-LogoPart
    {
        [CmdletBinding()]
        param(
            [Parameter(Mandatory=$true)]
            [ValidateNotNullOrEmpty()]
            [string]$part1,
            [Parameter(Mandatory=$true)]
            [ValidateNotNullOrEmpty()]
            [string]$part2,
            [Parameter(Mandatory=$true)]
            [ValidateNotNullOrEmpty()]
            [string]$part3,
            [Parameter(Mandatory=$true)]
            [ValidateNotNullOrEmpty()]
            [string]$part4
        )
        Write-Host ' ' -NoNewLine
        Write-Host $part1 -ForegroundColor Blue -NoNewline
        Write-Host $part2 -ForegroundColor DarkCyan -NoNewline
        Write-Host $part3 -ForegroundColor Cyan -NoNewline
        Write-Host $part4 
    }


    Write-LogoPart "__ " "__ " "__ " " _ _ _ _ _ "
    Write-LogoPart "\ \" "\ \" "\ \ " "| |_ ___| | (_) ___ ___ ___ | |_ "
    Write-LogoPart " \ \" "\ \" "\ \" "| __/ _ \ | | |/ _ \/ _ \ / _ \| __| "
    Write-LogoPart " / /" "/ /" "/ /" "| |_| __/ | | | (_| | _/| | | | |_ "
    Write-LogoPart "/_/" "/_/" "/_/ " " \__\___|_|_|_|\__, |\___|_| |_|\__|"
    Write-LogoPart " " " " " " " |___/ "
    Write-Host
}

function Expand-Zip {
    <#
    .Synopsis
        Extracts files or directories from a zip folder
    .Parameter Path
        The path to the Zip folder to extract
    .Parameter Destination
        The location to extract the files to
    .Parameter ZipDirectory
        The directory within the zip folder to extract. If not specified, extracts the whole zip file
    .Parameter ZipFileName
        The name of a specific file within ZipDirectory to extract
    .Example
        Expand-Zip c:\sample.zip c:\files\
         
        Description
        -----------
        This command extracts the entire contents of c:\sample.zip to c:\files\
    .Example
        Expand-Zip c:\sample.zip c:\sample\web\ -ZipDirectory web
         
        Description
        -----------
        This command extracts the contents of the web directory of c:\sample.zip to c:\sample\web
    .Example
        Expand-Zip c:\sample.zip c:\test\ -ZipDirectory documentation -zipFileName sample.txt
         
        Description
        -----------
        This command extracts the sample.txt file from the web directory of c:\sample.zip to c:\sample\sample.txt
    #>

    [CmdletBinding()]
    param(
        [parameter(Mandatory=$true, Position=0)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({Test-Zip $_ })]
        [string]$Path,
        [parameter(Mandatory=$true, Position=1)]
        [ValidateNotNullOrEmpty()]
        [string]$Destination,
        [ValidateScript({!$_ -or (Test-Path $_ -PathType Container -IsValid)})]
        [string]$ZipDirectory,
        [ValidateScript({!$_ -or (Test-Path $_ -PathType Leaf -IsValid)})]
        [string]$ZipFileName
    )

    $prefix = ''
    if($ZipDirectory){
        $prefix = ($ZipDirectory).Replace('\','/').Trim('/') + '/'
    }
    if (!(test-path $Destination -PathType Container)) {
        New-item $Destination -Type Directory | out-null
    }

    #Convert path requried to ensure
    $absoluteDestination = (Resolve-Path $Destination).ProviderPath
    $zipAbsolutePath = (Resolve-Path $Path).ProviderPath

    $zipPackage = [IO.Compression.ZipFile]::OpenRead($zipAbsolutePath)
    try {
        $entries = $zipPackage.Entries
        if ($ZipFileName){
            $entries = $entries |
                ? {$_.FullName.Replace('\','/') -eq "${prefix}${ZipFileName}"} |
                select -First 1
        }
        else {
            #Filter out directories
            $entries = $zipPackage.Entries |? Name
            if ($ZipDirectory) {
                #Filter out items not under requested directory
                $entries = $entries |? { $_.FullName.Replace('\','/').StartsWith($prefix, "OrdinalIgnoreCase")}
            }
        }

        $totalFileSize = ($entries |% length | Measure-Object -sum).Sum
        $processedFileSize = 0
        $entries |% {
            $destination = join-path $absoluteDestination $_.FullName.Substring($prefix.Length)
            <#
            Write-Progress 'Extracting Zip' `
                -CurrentOperation $_.FullName `
                -PercentComplete ($processedFileSize / $totalFileSize * 100)
            #>

                      
            $itemDir = split-path $Destination -Parent
            if (!(Test-Path $itemDir -PathType Container)) {
                New-item $itemDir -Type Directory | out-null
            }
            [IO.Compression.ZipFileExtensions]::ExtractToFile($_, $Destination, $true)

            $processedFileSize += $_.Length
        }
        #Write-Progress 'Extracting-Zip' -Completed
        
    }
    finally {
        $zipPackage.Dispose()
    }
}