ps/Modules/Alkami.PowerShell.SDK/Public/Install-SDKRelease.ps1
2023-05-30 22:51:22 -07:00

348 lines
17 KiB
PowerShell

function Install-SDKRelease {
<#
.SYNOPSIS
Install a release of SDK with some set of feature packages, where they exist
#>
[CmdletBinding(DefaultParameterSetName = 'VersionAndFeatures')]
param (
[Parameter(ParameterSetName = 'VersionAndFeatures')]
[ArgumentCompleter({
$manifestFolder = (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath "Manifests")
if (!(Test-Path -Path $manifestFolder)) {
$manifestFolder = (Join-Path -Path $PSScriptRoot -ChildPath "Manifests")
}
$files = Get-ChildItem -Path (Join-Path $manifestFolder "*.json")
$fileNames = $files | Foreach-Object { return ([System.IO.Path]::GetFileNameWithoutExtension($_) -split '\.')[0..1] -join '.' } | Sort-Object | Get-Unique
return $filenames | Foreach-Object { $_ }
})][string]$Version = ((Get-SavedInstallVersions) -split "`n")[0],
[Parameter(ParameterSetName = 'VersionAndFeatures')]
[ArgumentCompleter({
$manifestFolder = (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath "Manifests")
if (!(Test-Path -Path $manifestFolder)) {
$manifestFolder = (Join-Path -Path $PSScriptRoot -ChildPath "Manifests")
}
$files = Get-ChildItem -Path (Join-Path $manifestFolder "*.json")
$fileNames = $files | Foreach-Object { return ([System.IO.Path]::GetFileNameWithoutExtension($_) -split '\.')[2] } | Sort-Object | Get-Unique
return $filenames | Foreach-Object { $_ }
})][string[]]$Features = ((Get-SavedInstallVersions) -split "`n")[1],
[Parameter(ParameterSetName = 'Latest')]
[switch]$Latest
)
$logLead = Get-LogLeadName
$tiers = 0..4
if ($null -ne (Get-Command -Name Set-EnvironmentVariable -ErrorAction Ignore)) {
Set-AlkamiConfiguration -Configuration StartServicesOnInstall -Off
}
$transcriptFilename = "Alkami.SDK.InstallRelease.$(Get-Date -Format "yyyyMMddhhmm").txt"
$transcriptPath = Join-Path -Path (Get-OrbLogsPath) -ChildPath $transcriptFilename
Start-Transcript -Path $transcriptPath
# Ensure hosts file entries exist for later
Add-OrbHostEntries
# Install/Upgrade Alkami.SDKRelease.Manifests package
$chocoSplat = @(
"upgrade"
"Alkami.SDKRelease.Manifests"
"-i"
"-y"
"--no-progress"
"--limit-output"
)
Invoke-CallOperatorWithPathAndParameters -Path Choco -Arguments $chocoSplat *>&1 | Out-Default -Transcript | Out-Null
try {
$allPackages, $removePackages = Get-FeatureSets
Write-Verbose (ConvertTo-Json $allPackages)
$packagesToUpgrade, $newPackagesToInstall, $packagesToRemove = Get-LocalPackages -Packages $allPackages
Write-Host "$logLead : Found [$($packagesToUpgrade.Count)] existing packages to upgrade"
foreach ($packageToUpgrade in $packagesToUpgrade) {
"Upgrade $($packageToUpgrade.Tier) -> $($packageToUpgrade.id) $($packageToUpgrade.Version)" | Out-Default -Transcript | Out-Null
}
Write-Host "$logLead : Found [$($newPackagesToInstall.Count)] new packages to install"
foreach ($packageToInstall in $newPackagesToInstall) {
"Install $($packageToInstall.Tier) -> $($packageToInstall.id) $($packageToInstall)." | Out-Default -Transcript | Out-Null
}
if ($packagesToRemove.Count -gt 0) {
Write-Host "$logLead : Found [$($packagesToRemove.Count)] existing packages to remove"
foreach ($package in $packagesToRemove) {
"Remove $package" | Out-Default -Transcript | Out-Null
}
} else {
Write-Host "No packages marked for deletion"
}
$packagesToInstall = @{}
foreach ($tier in $tiers) {
#Write-host "$logLead : Tier $tier Installs"
$installs = @()
foreach ($package in $packagesToUpgrade) {
if ($package.Tier -eq $tier) {
$installs += $package
}
}
foreach ($package in $newPackagesToInstall) {
if ($package.Tier -eq $tier) {
$installs += $package
}
}
$packagesToInstall[$tier] = $installs
}
Write-Host "$logLead : Setting Alkami services recovery to take no action"
Set-SDKServiceRecovery -ServiceName 'All' -Action 'TakeNoAction'
Write-Host "$logLead : Stopping IIS and only services to be upgraded"
# Only stop services we are going to be installing so we do as little interruption as possible
# Only has to be packages that are being upgraded, the ones being installed don't affect anything
foreach ($package in $packagesToUpgrade) {
$serviceInfo = (Get-ServiceInfoByCIMFragment -Fragment $package.Id)
if ($null -eq $serviceInfo) {
# This isn't a service, so we don't care
continue
}
$serviceName = $serviceInfo.Name
if (![string]::IsNullOrWhiteSpace($serviceName)) {
#Stop-AlkamiService -ServiceName $serviceName
#Write-Host "Package Id: " $package.Id
#Write-Host "ServiceName: " $serviceName
$service = Get-Service -Name $serviceName | Select-Object -First 1
$processes = @(Get-ProcessFromService $service)
if ($processes.Count -ne 0) {
$process = ($processes | Select-Object -First 1)
"Stopping Process: $($process.Name) | $($process.id)" | Out-Default -Transcript | Out-Null
#Stop-ProcessIfFound $process.Name
Stop-Process $process.Id -force | Out-Null
} else {
"No running processes found for $($service.Name)" | Out-Default -Transcript | Out-Null
}
}
if ($null -eq $package.ComponentType) {
# because of legacy installers, remove this package's service registration to allow for downgrade experiences
Invoke-SCExe @('delete', $serviceName)
}
}
if ($packagesToRemove.Count -gt 0) {
# remove packages we are uninstalling
foreach ($package in $packagesToRemove) {
Write-Verbose "Package to remove: $package"
$serviceName = (Get-ServiceInfoByCIMFragment -Fragment $package).Name
if (![string]::IsNullOrWhiteSpace($serviceName)) {
Stop-AlkamiService -ServiceName $serviceName
}
}
}
Stop-IISOnly
#Stop Radium if installed
$serviceName = Get-ServiceInfoByCIMFragment -QueryFragment (Join-Path (Get-OrbPath) 'Radium')
if (-not [string]::IsNullOrWhiteSpace($serviceName.Name)) {
Stop-AlkamiService -ServiceName $serviceName.Name
}
#Stop Nag if installed
$serviceName = Get-ServiceInfoByCIMFragment -QueryFragment (Join-Path (Get-OrbPath) 'Nag')
if (-not [string]::IsNullOrWhiteSpace($serviceName.Name)) {
Stop-AlkamiService -ServiceName $serviceName.Name
}
# We are installing ORB, so we are always going to start it back up
$startIIS = $true
if ($packagesToRemove.Count -gt 0) {
Write-Host "$logLead : Removing packages now"
# remove packages we are uninstalling
foreach ($package in $packagesToRemove) {
"Completely removed package [$package]" | Out-Default -Transcript | Out-Null
choco uninstall $package -n -f -i
}
}
$chocoPath = Get-ChocolateyInstallPath
Write-Host "$logLead : Installing packages now"
"Dumping `$newPackagesToInstall to Transcript" | Out-Default -Transcript | Out-Null
(ConvertTo-Json $newPackagesToInstall) | Out-Default -Transcript | Out-Null
"Dumping `$packagesToUpgrade to Transcript" | Out-Default -Transcript | Out-Null
(ConvertTo-Json $packagesToUpgrade) | Out-Default -Transcript | Out-Null
"Dumping `$packagesToRemove to Transcript" | Out-Default -Transcript | Out-Null
(ConvertTo-Json $packagesToRemove) | Out-Default -Transcript | Out-Null
"Dumping `$tiers to Transcript" | Out-Default -Transcript | Out-Null
(ConvertTo-Json $tiers) | Out-Default -Transcript | Out-Null
foreach ($tier in $tiers) {
if ($tier -eq 1) {
Write-Host "Force removing AlkamiModules that just got installed"
Get-Module Alkami* | Remove-Module -Force
Import-Module Alkami.PowerShell.Database # ensure that database runners can get run, or show us an error message
#$tiers[0].Where({$_.ComponentType -eq 'SREModule' -and $_.Id -notmatch "\.SRE\."}).Id | Foreach-Object { Import-Module $_ }
Write-Host "$logLead : Starting Redis"
Start-Service -Name "redis-master18620"
Start-Service -Name "redis-slave18621"
Write-Warning "$logLead : If the following two commands (Import-Module WebAdministration, Load-Assembly) fail and stop the installation, please close and reopen your powershell session and try again."
Write-Warning "$logLead : Alkami developers occasionally encounter this on new machine installs, and are working to resolve the issue."
Import-Module WebAdministration
try {
Add-Type -AssemblyName "Microsoft.Web.Administration, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
}
catch {
# Just in case we have IIS Express and IIS loaded (dev machines)
[System.Reflection.Assembly]::LoadFile("C:\Windows\system32\inetsrv\Microsoft.Web.Administration.dll")
}
Assert-PlatformDeveloperKitComponentsInstalled
# Before we install anything after the basics, we need to initialize the databases if they haven't been. We should have already delivered the database component.
if ($null -eq (Get-EnvironmentVariable -Name "Alkami_SDK_Initialization_DatabaseStandup")) {
Write-Host "Ensure default AlkamiDatabases are available and have been configured"
$connectionString = Get-MasterConnectionString
# names are hard-coded especially
Initialize-AlkamiDatabase $connectionString 'AlkamiMaster'
Initialize-AlkamiDatabase $connectionString 'DeveloperDynamic'
# TODO: Incredibly brittle. Will need to be refactored
# Easiest refactor is to restore a masterdb in the initialize functions above
C:\programdata\Alkami\Alkami\MachineSetup\DatabaseCore\Migrate.exe -a C:\programdata\Alkami\Alkami\MachineSetup\DatabaseCore\Alkami.Tools.MasterDatabaseMigration.dll --connectionString $connectionString -provider SqlServer2008 -tag alkamimaster -context sdk
Write-Host "Setting tenant"
Import-DeveloperDynamicTenant $connectionString
Set-EnvironmentVariable -Name "Alkami_SDK_Initialization_DatabaseStandup" -Value (Get-Date) -StoreName Machine
}
# Before we install anything after the basics, we need to initialize the websites and webapps if they haven't been. We should have already delivered the required orb file components.
Write-Host "$logLead : Creating web sites and applications now"
Install-SDKIISComponents
# You have to create the sites before you can run the migrations because of how the database users get setup in the database
# It's a pain, I know, but yay out of order operations ...
Write-host "$logLead : Running migrations"
Invoke-SDKAlkamiMigrations
}
$packages = $packagesToInstall[$tier]
if (Test-IsCollectionNullOrEmpty $packages) {
Write-Host "$logLead : PackagesToInstall was empty"
continue
} else {
Write-Host "$logLead : Installing Tier $tier packages with count $($packages.count)"
}
$potentialServicePaths = @()
$parallelInstall = @()
# We have to check $allPackages because they may already be installed
foreach ($package in $packages) {
$packageFolder = Join-Path -Path (Join-Path -Path $chocoPath -ChildPath "lib") -ChildPath $package.Id
$potentialServicePaths += $packageFolder
}
($potentialServicePaths -Join "`n") | Out-Default -Transcript | Out-Null
foreach ($package in $packages) {
$packageFolder = Join-Path -Path (Join-Path -Path $chocoPath -ChildPath "lib") -ChildPath $package.Id
$runScripts = "-y"
if ($package.HasManifest -and $null -ne $package.ComponentType) {
$runScripts = "--skip-scripts"
$parallelInstall += $packageFolder
}
$runQuietly = "--no-progress"
$sayVeryLittle = "--limit-output"
$ignoreDependencies = "-i"
if ($package.Id -in @("redis-64")) {
$ignoreDependencies = " "
}
$version = "$($package.Version)"
$packageVersion = "[$($package.Version)]"
$chocoSplat = @(
"upgrade"
$package.Id
$ignoreDependencies
$runScripts
$runQuietly
$sayVeryLittle
)
if ($package.VersionCanTakeAny) {
$version = ""
$packageVersion = "the latest available"
# Write-Host "choco upgrade $($package.Id) -i $runScripts"
# choco upgrade $package.Id -i $runScripts #Removed -f
} else {
# Write-Host "choco upgrade $($package.Id) --version $version -i $runScripts"
# choco upgrade $package.Id --version $version -i $runScripts #Removed -f
$chocoSplat += "--version=$version"
}
Write-Host "Downloading$(if (!$package.HasManifest){" and installing"}) $($package.id) - This may take a moment, even if things seem frozen."
# https://stackoverflow.com/questions/38523369/write-host-vs-write-information-in-powershell-5
# Out-Default -Transcript <-- magic
Invoke-CallOperatorWithPathAndParameters -Path Choco -Arguments $chocoSplat *>&1 | Out-Default -Transcript | Out-Null
"Installed package [$($package.Id)] version to $packageVersion" | Out-Default -Transcript | Out-Null
}
if (!(Test-IsCollectionNullOrEmpty $parallelInstall)) {
Write-Host "Running parallel installs. This is going to look like things are locked up. They aren't. Promise."
Invoke-Parallel -Objects $parallelInstall -ReturnObjects -Script {
param ($innerPackagePath)
$path = Join-Path -Path (Join-Path -Path $innerPackagePath -ChildPath tools) -ChildPath "chocolateyInstall.ps1"
if (Test-Path -Path $path) {
Write-Host "Calling $path"
& $path
}
}
}
$servicesInstalled = Invoke-Parallel -Objects $potentialServicePaths -ReturnObjects -Script {
param ($innerPackagePath)
$cimService = Get-ServiceInfoByCIMFragment -QueryFragment $innerPackagePath
if ($null -ne $cimService) {
return $cimService.Name
}
}
if ($tier -lt 3) {
foreach ($serviceName in $servicesInstalled) {
Write-Host "$logLead : Start-AlkamiService -ServiceName $serviceName"
Start-AlkamiService -ServiceName $serviceName
}
} else {
Invoke-Parallel -Objects $servicesInstalled -ReturnObjects -Script {
param ($serviceName)
Start-AlkamiService -ServiceName $serviceName
}
}
}
# We've already done the database migrations, but we need to ensure the database users got setup for componentized web services
# Ensure logins are setup
Invoke-DatabaseConfigurationAlkamiMasterTask
Invoke-DatabaseConfigurationAlkamiTenantTask
if ($startIIS) {
Remove-DotNetTemporaryFiles
Start-IISAndServices # restarts all of the services that were not upgraded/installed but stopped by installing Alkami.SRE.MigrationUtility
}
Write-Host "$logLead : Setting Alkami services back to recover"
Set-SDKServiceRecovery -ServiceName 'All' -Action 'recovery'
Set-SDKServiceStartupType -ServiceName 'All' -StartupType 'Automatic'
} finally {
Stop-Transcript
Write-Host "`n`n`tTranscript log saved to $transcriptPath`n`n"
}
}