TisaneOnprem.psm1

#Region '.\Public\Check-ServiceCpu.ps1' 0
function Check-ServiceCpu {
    param (
        [Parameter(Mandatory=$true)][string]$service
    )
    $NUMBER_OF_CORES = 1
    $procId = (Get-CimInstance Win32_Service | ?{$_.Name -like $service} | SELECT ProcessId).ProcessId
    $procPath = (Get-Counter "\Process(*)\ID Process" -ErrorAction SilentlyContinue).CounterSamples | Where-Object {$_.CookedValue -eq $procId} | Select-Object -ExpandProperty Path
    $procCpuUsage = [Math]::Round(((Get-Counter ($procPath -replace "\\id process$","\% Processor Time")).CounterSamples.CookedValue) / $NUMBER_OF_CORES)
    return $procCpuUsage
}

#EndRegion '.\Public\Check-ServiceCpu.ps1' 12
#Region '.\Public\Check-ServiceStatus.ps1' 0
function Check-ServiceStatus {
    param (
        [Parameter(Mandatory=$true)][string]$hostIp,
        [Parameter(Mandatory=$true)][int]$timeout
    )
    $uri = $hostIp+"parse"
    $response = Invoke-WebRequest -Uri $uri -Method POST -Headers $headers -ContentType 'application/json' -Body '{"language": "en", "content": "this is a test sentence to test the testing of tisane", "settings": {"parses":true,"debug":true}}' -TimeoutSec $timeout -ErrorAction SilentlyContinue
    if ($response.StatusCode -eq 200) {
        return $true
    } else {
        return $false
    }
}
# Check-IfServiceDown "http://94.237.73.126:80/"
#EndRegion '.\Public\Check-ServiceStatus.ps1' 15
#Region '.\Public\Download-TisaneFromFTP.ps1' 0
function Download-TisaneFromFTP{
  [CmdletBinding()]
Param(
     [Parameter(Mandatory = $true, valueFromPipeline=$true, HelpMessage="FTP user: ")][String] $user,
     [Parameter(Mandatory = $true, HelpMessage="FTP password: ")][String] $password,
     [Parameter(Mandatory = $true, HelpMessage="FTP host: ")][String] $ftp
   )

$DOWNLOAD_FOLDER = "C:\Users\Administrator\Downloads\Tisane"
$TISANE_ROOT = "C:\Tisane"

$webclient = New-Object System.Net.WebClient 

if(-not(Test-Path($DOWNLOAD_FOLDER))) {
    # file with path $path doesn't exist
  mkdir $DOWNLOAD_FOLDER
}


$webclient.Credentials = New-Object System.Net.NetworkCredential("$user@$ftp", $password)

$uri = New-Object System.Uri("ftp://ftp.$ftp/tisane_db.zip")
Write-Host "Downloading Tisane data to $DOWNLOAD_FOLDER..." -ForegroundColor Green
$webclient.DownloadFile($uri, "$DOWNLOAD_FOLDER\tisane_db.zip")
Write-Host "Downloading Tisane Web Service to $DOWNLOAD_FOLDER..." -ForegroundColor Green
$uri = New-Object System.Uri("ftp://ftp.$ftp/tisane_ws.zip")
$webclient.DownloadFile($uri, "$DOWNLOAD_FOLDER\tisane_ws.zip")

if(Test-Path($TISANE_ROOT)) {
  #mkdir $TISANE_ROOT
  #Remove-Item -Path "$TISANE_ROOT\" -Recurse
  # dir $TISANE_ROOT -Recurse | Where-Object { $_.FullName -imatch '^C:\\tisane\\([^\\]+)\\.+[.]sst' } | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
}

#Add-Type -AssemblyName System.IO.Compression.FileSystem
#Add-Type -Assembly "System.IO.Compression.Filesystem"

Add-Type -Assembly "System.Io.Compression.FileSystem"

[System.IO.Compression.ZipFile]::ExtractToDirectory("$DOWNLOAD_FOLDER\tisane_db.zip", "$DOWNLOAD_FOLDER\db")
}
#EndRegion '.\Public\Download-TisaneFromFTP.ps1' 42
#Region '.\Public\Install-TisaneFromFTP.ps1' 0
function Install-TisaneFromFTP{
  [CmdletBinding()]
Param(
     [Parameter(Mandatory = $true, valueFromPipeline=$true, HelpMessage="FTP user: ")][String] $user,
     [Parameter(Mandatory = $true, HelpMessage="FTP password: ")][String] $password,
     [Parameter(Mandatory = $true, HelpMessage="FTP host: ")][String] $ftp,
     [Parameter(Mandatory = $false, HelpMessage="Install .NET? ")][boolean] $dotnet,
     [Parameter(Mandatory = $true, HelpMessage="Instance count: ")][int] $instances,
     [Parameter(Mandatory = $false, HelpMessage="Windows user: ")][String] $winuser
   )

if (-not($winuser)) {
  $winuser = 'Administrator'
}
$DOWNLOAD_FOLDER = "C:\Users\$winuser\Downloads\Tisane"
$dotNETInstallerUrl = "https://go.microsoft.com/fwlink/?linkid=2088631"
$TISANE_ROOT = "C:\Tisane"


$webclient = New-Object System.Net.WebClient 

if(-not(Test-Path($DOWNLOAD_FOLDER))) {
    # file with path $path doesn't exist
  mkdir $DOWNLOAD_FOLDER
}

if ($dotnet) {
  Write-Host "Downloading .NET installer to $DOWNLOAD_FOLDER..." -ForegroundColor Green
  $webclient.DownloadFile($dotNETInstallerUrl, "$DOWNLOAD_FOLDER\dotnetinstaller.exe")
  Write-Host "Installing .NET..." -ForegroundColor Green
  Start-Process -FilePath $DOWNLOAD_FOLDER\dotnetinstaller.exe -ArgumentList "/q","/norestart" -Wait
}



$webclient.Credentials = New-Object System.Net.NetworkCredential("$user@tisane.ai", $password)

$uri = New-Object System.Uri("ftp://$ftp/tisane_db.zip")
Write-Host "Downloading Tisane data to $DOWNLOAD_FOLDER..." -ForegroundColor Green
$webclient.DownloadFile($uri, "$DOWNLOAD_FOLDER\tisane_db.zip")
Write-Host "Downloading Tisane Web Service to $DOWNLOAD_FOLDER..." -ForegroundColor Green
$uri = New-Object System.Uri("ftp://$ftp/tisane_ws.zip")
$webclient.DownloadFile($uri, "$DOWNLOAD_FOLDER\tisane_ws.zip")
$uri = New-Object System.Uri("ftp://$ftp/Tisane.Runtime.Service.exe.config")
$webclient.DownloadFile($uri, "$DOWNLOAD_FOLDER\Tisane.Runtime.Service.exe.config")


if(Test-Path($TISANE_ROOT)) {
  #mkdir $TISANE_ROOT
  #Remove-Item -Path $TISANE_ROOT -Recurse
}

#Add-Type -AssemblyName System.IO.Compression.FileSystem

#[System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath)
Write-Host "Extracting Tisane data to $TISANE_ROOT..." -ForegroundColor Green
# WE HAVE TO USE Ionic Zip because the standard compression libraries crash with very large archives
[System.Reflection.Assembly]::LoadFrom("$DOWNLOAD_FOLDER\Ionic.Zip.dll")
$zipFile = New-Object Ionic.Zip.ZipFile("$DOWNLOAD_FOLDER\tisane_db.zip")

#Expand-Archive -LiteralPath "$DOWNLOAD_FOLDER\tisane_db.zip" -DestinationPath $TISANE_ROOT
$zipFile.ExtractAll($TISANE_ROOT, 1)

$portNumber = 81
For ($i=1; $i -le $instances; $i++) {
  $formattedI = $i | % tostring 00
  $portNumber = 79 + $i
  Write-Host "Creating instance under $TISANE_ROOT\instance$formattedI" -ForegroundColor Green
  Expand-Archive -LiteralPath "$DOWNLOAD_FOLDER\tisane_ws.zip" -DestinationPath "$TISANE_ROOT\instance$formattedI" -Force
  $webclient.DownloadFile($uri, "$TISANE_ROOT\instance$formattedI\Tisane.Runtime.Service.exe.config")
  ((Get-Content -path "$TISANE_ROOT\instance$formattedI\Tisane.Runtime.Service.exe.config" -Raw) -replace 'localhost:80',"localhost:$portNumber") | Set-Content -Path "$TISANE_ROOT\instance$formattedI\Tisane.Runtime.Service.exe.config"
  Start-Process -FilePath "$TISANE_ROOT\instance$formattedI\Tisane.Runtime.Service.exe" -ArgumentList "-i" -Wait
}
# now open firewall ports
netsh advfirewall firewall add rule name='Tisane ports' dir=in protocol=tcp localport="80-$portNumber" action=allow profile=any

# create a quarterly update task
SchTasks /Create /sc monthly /mo 3 /TN "Tisane update" /TR "$DOWNLOAD_FOLDER\UpdateTisaneFromFTP.ps1 -user $user -password $password -ftp $ftp" /ST 23:00

# start the services
Start-Service "Tisane Runtime *"
}
#EndRegion '.\Public\Install-TisaneFromFTP.ps1' 83
#Region '.\Public\Monitor-Tisane.ps1' 0
function Monitor-Tisane {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, HelpMessage="Service names to monitor: ")][string[]]$services,
        [Parameter(Mandatory=$false, HelpMessage="Timeout for monitor (Default 120s)")][int]$interval = 120,
        [Parameter(Mandatory=$false, HelpMessage="Threshold for CPU consuption (Default 100)")][int]$thresholdCpu = 100,
        [Parameter(Mandatory=$false, HelpMessage="Timeout for the healthcheck: (Default 2s)")][int]$timeout = 2,
        [Parameter(Mandatory=$false, HelpMessage="Number of health checks to fail consecutively before restart : (Default 1)")][int]$failureBeforeRestart = 1
    )
    Write-Host "The script allows you to get email notification, Please run Setup-Email to configure the credentials"
    $hostName = [System.Net.Dns]::GetHostName()
    $Results = @{}
    $originalServiceNames = @{}
    $servicesOriginal = $services
    Write-Host $services
    $services = $servicesOriginal -replace '\[','?' -replace '\]','?'

    foreach ($service in $services) {
        $oneService = Get-CimInstance Win32_Service | ?{$_.Name -like $service} | SELECT Name , PathName
        if ([string]::IsNullOrEmpty($oneService)){
            Write-Host "$service not found" -ForegroundColor RED
            Write-Host "Abortting !!"
            return 1
        }
        $serviceName = $oneService.Name
        $configFile = $oneService.PathName -replace "\.exe", ".exe.config" -replace '\"', ''
        if (Test-Path $configFile) {
                $config = [xml](Get-Content $configFile)
                $AddNode = Select-Xml -Xml $config -XPath "//services/service/host/baseAddresses"
                $Value = $AddNode.Node.add.baseAddress
                $Results[$service] = $Value
                $originalServiceNames[$service]=$serviceName
        }else{
            Write-Host "Config file for $service not found" -ForegroundColor Red
            Write-Host "Abortting !!"
            return 1
        }
    }
    $hostIpS = $Results.Values
    $services = $Results.Keys
    # $originalNames = $originalServiceNames.Values
    Write-Host "Starting monitor for the following services"
    # Write-Host $originalServiceNames.Values -ForegroundColor Green
    # Write-Host "Corresponding endpoints: "
    # Write-Host $hostIpS -ForegroundColor Green
    $combinedList = for ($i = 0; $i -lt $hostIpS.Count; $i++) {
        [PSCustomObject]@{
            Services = $services[$i]
            Endpoints = $Results[$services[$i]]
        }
    }
    $combinedList | Format-Table -AutoSize
    $restarting =@{}
    $failureCount = @{}
    while ($true) {
        foreach ($service in $services) {
            $hostIp = $Results[$service]
            if ($restarting.ContainsKey($service)) {
                Write-Host "$service is being restarted"
                if (Check-ServiceStatus -hostIp $hostIp -timeout $timeout) {

                    Write-Host "Service on $service has restared"
                    $restarting.Remove($service)
                }
                continue
            }
            $cpuUsage = Check-ServiceCpu $service
            if ((Check-ServiceStatus -hostIp $hostIp -timeout $timeout) -and ( $cpuUsage -le $thresholdCpu)) {
                Write-Host "$service is running, consuming $cpuUsage% cpu"
                $failureCount[$service] = 0
            } else {
                $failureCount[$service]++
                $oneServiceFaliurCount = $failureCount[$service]
                Write-Host "$service failed $oneServiceFaliurCount health checks consecutively"
                if ($oneServiceFaliurCount -ge $failureBeforeRestart){
                    $restarting[$service]=$hostIp
                    Stop-Service -Name $service
                    Start-Service -Name $service
                    Write-Host "sending mail"
                    $date = Get-Date
                    $subject = "Tisane admin alert [$hostName]"
                    $instanceName = $originalServiceNames[$service]
                    $body = "[$instanceName] running on [$hostName] was restarted on [$date] after health check failed"
                    Send-Email -body $body -subject -$subject
                }
            }
        }
        # Sleep for 120 seconds
        Start-Sleep -Seconds $interval
    }
}
#EndRegion '.\Public\Monitor-Tisane.ps1' 92
#Region '.\Public\Reset-EmailSettings.ps1' 0
function Reset-EmailSettings {
    Save-LampSetting -settingName 'emailTo' -settingValue $null
    Save-LampSetting -settingName 'smtpServer' -settingValue $null
    Save-LampSetting -settingName 'smtpPort' -settingValue '587'
    Save-LampSetting -settingName 'emailUsername' -settingValue $null
    Save-LampSetting -settingName 'emailPassword' -settingValue $null
}
#EndRegion '.\Public\Reset-EmailSettings.ps1' 8
#Region '.\Public\Send-Email.ps1' 0
function Send-Email() {

    Param(
    
        [Parameter(Mandatory=$false, HelpMessage="Body: ")][String] $body,
        [Parameter(Mandatory=$false, HelpMessage="Subject: " )][String] $subject
    )
    $To = Get-LampSetting -settingName 'emailTo' -defaultValue ''
    $To = $To.Split(",")
    $smtpServer = Get-LampSetting -settingName 'smtpServer' -defaultValue 'mail.tisane.ai'
    $smtpPort = Get-LampSetting -settingName 'smtpPort' -defaultValue '587'
    $emailUsername = Get-LampSetting -settingName 'emailUsername' -defaultValue ''
    $encryptedEmailPassword = Get-LampSetting -settingName 'emailPassword' -defaultValue ''
    $secureEmailPassword = ConvertTo-SecureString -String $encryptedEmailPassword
    $emailCredential = New-Object System.Management.Automation.PSCredential($emailUsername, $secureEmailPassword)
    Send-MailMessage -To $To -From $emailUsername -Subject $subject -Body $body -Credential $emailCredential -SmtpServer $SmtpServer -Port $SmtpPort
    Write-Host "Email sent"
}
#EndRegion '.\Public\Send-Email.ps1' 19
#Region '.\Public\Setup-Email.ps1' 0
function Setup-Email {
    Write-Host "Comma-delimited email addresses to send alerts to:"
    $To = Read-Host
    $To = $To.Split(",")
    Save-LampSetting -settingName 'emailTo' -settingValue $To
    $smtpServer = Read-Host "SMTP server"
    $smtpPort = Read-Host "SMTP port (default: 587)"
    Save-LampSetting -settingName 'smtpServer' -settingValue $smtpServer
    Save-LampSetting -settingName 'smtpPort' -settingValue $smtpPort
    if ([string]::IsNullOrEmpty($smtpServer)) {
        $smtpServer = Get-LampSetting -settingName 'smtpServer' -defaultValue ''   
    }
    if ([string]::IsNullOrEmpty($smtpPort)) {
        $smtpPort = Get-LampSetting -settingName 'smtpPort' -defaultValue '587'
    }
    
    $emailUsername = Get-LampSetting -settingName 'emailUsername' -defaultValue ''
    $encryptedEmailPassword = Get-LampSetting -settingName 'emailPassword' -defaultValue ''
    if ([string]::IsNullOrEmpty($emailUsername)){
        # Write-Host "in if"
        # $credential = Get-Credential -Message "Sender email account credentials"
        $credential = $host.ui.PromptForCredential("Sender email account credentials", "Sender email account credentials", "", "")
        $emailUsername = $credential.UserName
        $secureEmailPassword = $credential.Password
        $encryptedEmailPassword = ConvertFrom-SecureString -SecureString $secureEmailPassword
        Save-LampSetting -settingName 'emailUsername' -settingValue $emailUsername
        Save-LampSetting -settingName 'emailPassword' -settingValue $encryptedEmailPassword
    }else{
        # Write-Host "in else"
        $secureEmailPassword = ConvertTo-SecureString -String $encryptedEmailPassword
    }
    # $emailPassword = [System.Net.NetworkCredential]::new("", $secureEmailPassword).Password
    $emailCredential = New-Object System.Management.Automation.PSCredential($emailUsername, $secureEmailPassword)
    $date = Get-Date
    $subject = "Tisane admin test email"
    $body = "This is a test email sent by Monitor-Tisane script [ $date ]"
    Write-Host "Email settings : "
    Write-Host "To: $To"
    Write-Host "From: $emailUsername"
    Write-Host "smtpServer: $smtpServer"
    Write-Host "smtpPort: $smtpPort"
    try {
        Send-MailMessage -To $To -From $emailUsername -Subject $subject -Body $body -Credential $emailCredential -SmtpServer $smtpServer -Port $smtpPort
    }
    catch {
        Write-Host "An error occurred while sending the email:" -ForegroundColor Red
        Write-Host "Error message: $($_.Exception.Message)" -ForegroundColor Red
        Write-Host "Please reset the settings using Reset-EmailSettings, and run the Setup-Email again." -ForegroundColor Red
        return
    }
    
    # Send-MailMessage -To $To -From $emailUsername -Subject $subject -Body $body -Credential $emailCredential -SmtpServer $smtpServer -Port $smtpPort
    Write-Host "Test Email sent" -ForegroundColor Green
    Write-Host "Please check your inbox, if you did not receive the email, please reset the settings using Reset-EmailSettings, and run the Setup-Email again."
}
#EndRegion '.\Public\Setup-Email.ps1' 56
#Region '.\Public\Update-TisaneFromFTP.ps1' 0
function Update-TisaneFromFTP{
  [CmdletBinding()]
Param(
     [Parameter(Mandatory = $true, valueFromPipeline=$true, HelpMessage="FTP user: ")][String] $user,
     [Parameter(Mandatory = $true, HelpMessage="FTP password: ")][String] $password,
     [Parameter(Mandatory = $true, HelpMessage="FTP host: ")][String] $ftp,
     [Parameter(Mandatory = $false, HelpMessage="Copy only, no need to reregister the service: ")][Boolean] $copyOnly
   )

$DOWNLOAD_FOLDER = "C:\Users\Administrator\Downloads\Tisane"
$TISANE_ROOT = "C:\Tisane"

$webclient = New-Object System.Net.WebClient 

if(-not(Test-Path($DOWNLOAD_FOLDER))) {
    # file with path $path doesn't exist
  mkdir $DOWNLOAD_FOLDER
}


$webclient.Credentials = New-Object System.Net.NetworkCredential("$user@$ftp", $password)

$uri = New-Object System.Uri("ftp://ftp.$ftp/tisane_db.zip")
Write-Host "Downloading Tisane data to $DOWNLOAD_FOLDER..." -ForegroundColor Green
$webclient.DownloadFile($uri, "$DOWNLOAD_FOLDER\tisane_db.zip")
Stop-Service "Tisane Runtime *"
Write-Host "Downloading Tisane Web Service to $DOWNLOAD_FOLDER..." -ForegroundColor Green
$uri = New-Object System.Uri("ftp://ftp.$ftp/tisane_ws.zip")
$webclient.DownloadFile($uri, "$DOWNLOAD_FOLDER\tisane_ws.zip")

if(Test-Path($TISANE_ROOT)) {
  #mkdir $TISANE_ROOT
  #Remove-Item -Path "$TISANE_ROOT\" -Recurse
  # dir $TISANE_ROOT -Recurse | Where-Object { $_.FullName -imatch '^C:\\tisane\\([^\\]+)\\.+[.]sst' } | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
}

#Add-Type -AssemblyName System.IO.Compression.FileSystem
#Add-Type -Assembly "System.IO.Compression.Filesystem"


# WE HAVE TO USE Ionic Zip because the standard compression libraries crash with very large archives
[System.Reflection.Assembly]::LoadFrom("$DOWNLOAD_FOLDER\Ionic.Zip.dll")
$zipFile = New-Object Ionic.Zip.ZipFile("$DOWNLOAD_FOLDER\tisane_db.zip")

#[System.IO.Compression.ZipFile]::ExtractToDirectory("$DOWNLOAD_FOLDER\tisane_db.zip", $TISANE_ROOT, $true)
Write-Host "Extracting Tisane data to $TISANE_ROOT..." -ForegroundColor Green
#Expand-Archive -LiteralPath "$DOWNLOAD_FOLDER\tisane_db.zip" -DestinationPath $TISANE_ROOT -Force
(Get-Service "Tisane *").WaitForStatus('Stopped')
$zipFile.ExtractAll($TISANE_ROOT, 1)

# dir $TISANE_ROOT -Recurse | Where-Object { $_.FullName -imatch '^C:\\tisane\\([^\\]+)\\LOG[.]old.+' } | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue


$services = Get-Service "Tisane Runtime *"
$instances = $services.length


For ($i=1; $i -le $instances; $i++) {
  $formattedI = $i | % tostring 00
  Write-Host "Updating instance under $TISANE_ROOT\instance$formattedI" -ForegroundColor Green
  Expand-Archive -LiteralPath "$DOWNLOAD_FOLDER\tisane_ws.zip" -DestinationPath "$TISANE_ROOT\instance$formattedI" -Force
  if (-not $copyOnly) {
    Start-Process -FilePath "$TISANE_ROOT\instance$formattedI\Tisane.Runtime.Service.exe" -ArgumentList "-r" -Wait
  }
}

Start-Service "Tisane Runtime *"
}
#EndRegion '.\Public\Update-TisaneFromFTP.ps1' 69
#Region '.\Public\Update-TisaneFromLocalZips.ps1' 0
function Update-TisaneFromLocalZips{
Stop-Service "Tisane Runtime *"

$DOWNLOAD_FOLDER = "C:\Users\Administrator\Downloads\Tisane"
$TISANE_ROOT = "C:\Tisane"

#Add-Type -AssemblyName System.IO.Compression.FileSystem
#Add-Type -Assembly "System.IO.Compression.Filesystem"


# WE HAVE TO USE Ionic Zip because the standard compression libraries crash with very large archives
[System.Reflection.Assembly]::LoadFrom("$DOWNLOAD_FOLDER\Ionic.Zip.dll")
$zipFile = New-Object Ionic.Zip.ZipFile("$DOWNLOAD_FOLDER\tisane_db.zip")


#[System.IO.Compression.ZipFile]::ExtractToDirectory("$DOWNLOAD_FOLDER\tisane_db.zip", $TISANE_ROOT, $true)
Write-Host "Extracting Tisane data to $TISANE_ROOT..." -ForegroundColor Green
#Expand-Archive -LiteralPath "$DOWNLOAD_FOLDER\tisane_db.zip" -DestinationPath $TISANE_ROOT -Force
$zipFile.ExtractAll($TISANE_ROOT, 1)

# dir $TISANE_ROOT -Recurse | Where-Object { $_.FullName -imatch '^C:\\tisane\\([^\\]+)\\LOG[.]old.+' } | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue


$services = Get-Service "Tisane Runtime *"
$instances = $services.length


For ($i=1; $i -le $instances; $i++) {
  $formattedI = $i | % tostring 00
  Write-Host "Updating instance under $TISANE_ROOT\instance$formattedI" -ForegroundColor Green
  Expand-Archive -LiteralPath "$DOWNLOAD_FOLDER\tisane_ws.zip" -DestinationPath "$TISANE_ROOT\instance$formattedI" -Force
  Start-Process -FilePath "$TISANE_ROOT\instance$formattedI\Tisane.Runtime.Service.exe" -ArgumentList "-r" -Wait
}

Start-Service "Tisane Runtime *"
}
#EndRegion '.\Public\Update-TisaneFromLocalZips.ps1' 37