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

299 lines
13 KiB
PowerShell

Function Install-Widget {
<#
.SYNOPSIS
Installs a widget either on a server or a developer machine from a choco location, stops and starts IIS if it was running before.
.EXAMPLE
Install-Widget -WidgetName Authentication -SourcePath c:\programdata\chocolatey\lib\alkami.apps.authentication
.PARAMETER WidgetName
The name of the widget to install
.PARAMETER SourcePath
The path to the widget to be installed, usually a chocolatey lib folder
.PARAMETER IsAdmin
Indicate that the base web application name to look in is WebClientAdmin, otherwise is WebClient
.PARAMETER RemoveLogs
Indicating that the ORB logs should be purged
.PARAMETER NoSymlink
Force installation using File Copy operations, omitting a call to Test-InstallerUseSymlinkStrategy, even on hosts with the ENVIRONMENT VARIABLE 'Alkami.Installer.UseSymlink' set
.LINK
Test-InstallerUseSymlinkStrategy
.OUTPUTS
This function will only Write-Information, but also show the files that will be tentatively copied over
Install-Widget output may be verbose from the underlying calls being made.
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true, Position=0)]
[string]$WidgetName,
[Parameter(Mandatory=$true, Position=1)]
[string]$SourcePath,
[Parameter(Mandatory = $false)]
[switch]$IsAdmin,
[Parameter(Mandatory = $false)]
[switch]$RemoveLogs,
[Parameter(Mandatory = $false)]
[switch]$NoSymlink
)
$loglead = Get-LogLeadName
Write-Host "$loglead : $WidgetName being installed by $($env:USERNAME) on $($env:COMPUTERNAME) at $(Get-Date)"
if ( -not (Test-IsDeveloperMachine) -and -not (Test-IsWebServer)) {
Write-Warning "$loglead : Cannot install widgets on the APP tier"
return
}
if ( -not (Test-IsAdmin)) {
throw "You are NOT running as administrator. Cannot continue."
}
if ($NoSymlink) {
Write-Host "$loglead : Override provided! Using Legacy non-Symlink Installer Strategy"
$usingSymlinkStrategy = $false
} else {
$usingSymlinkStrategy = Test-InstallerUseSymlinkStrategy
if ($usingSymlinkStrategy) {
Write-Host "$loglead : Using Symlink Installer Strategy."
} else {
Write-Host "$loglead : Using Legacy non-Symlink Installer Strategy"
}
}
if ($usingSymlinkStrategy) {
$FILE_OPERATION_STRING = "SYMLINKing"
} else {
$FILE_OPERATION_STRING = "COPYing"
}
if ( -not (Test-Path -Path $SourcePath)) {
throw "$loglead : Could not find the source path specified at $SourcePath"
}
$libSourcePath = Join-Path -Path $SourcePath -ChildPath 'lib'
if ( -not (Test-Path -Path $libSourcePath)) {
throw "$loglead : Could not find the lib folder path specified at $libSourcePath"
}
$libSourceFSOFolder = Get-ChildItem -Path $libSourcePath -Directory `
| Sort-Object -Descending `
| Where-Object {(Get-ChildItem -Path $_.FullName -filter "*.dll").Count -gt 0 } `
| Select-Object -First 1
# This was labeled as part of, and committed against, SRE-16977 but that's the wrong ticket number.
# The following line should be this:
# $libSourceFolderPath = $libSourceFSOFolder.FullName
# However, developers implemented packages that UNINTENTIONALLY took advantage of a bug where the .net version subfolders didn't exist.
# We must handle this bug in order to not break the deployment of malformed packages
# This allows for the preceding Get-ChildItem to return $null and the following line to return
# the path it was trying to Get-ChildItem from without problem, instead of $null
# SRE-18955 - Added the Where-Object clause above to handle folders existing with no dlls due to poor choco cleanup.
# libSourceFSOFolder may be null (and that's acceptable). So .name will be null
# and $libSourceFolderPath will correctly be $libSourcePath.
$libSourceFolderPath = Join-Path -Path $libSourcePath -ChildPath $libSourceFSOFolder.Name
Write-Host "$loglead : Looking for dll files in $libSourceFolderPath"
if ((Get-ChildItem -Path $libSourceFolderPath -Filter *.dll).Count -eq 0) {
throw "$loglead : Could not find any dll files under $libSourceFolderPath"
}
## Second use-case will be for installing from a project folder and getting this from the bin path of the built solution
## That above case is to-do for cbrand - 2018-08-16
$contentTopPath = Join-Path -Path $SourcePath -ChildPath "Content"
$contentSourcePath = Join-Path -Path $contentTopPath -ChildPath "Areas"
$contentSourcePathFound = $true
if ( -not (Test-Path -Path $contentSourcePath)) {
## Don't throw right here because this could be an API widget
Write-Warning "$loglead : Is this an API style widget? Could not find the content path specified at $contentSourcePath"
$contentSourcePathFound = $false
}
if ($contentSourcePathFound -and (Get-ChildItem $contentSourcePath -Directory).Count -eq 0) {
## Don't throw right here because this could be an API widget
Write-Warning "$loglead : Is this an API style widget? Could not find any content folders under $contentSourcePath"
$contentSourcePathFound = $false
}
$contentSourceFolderPath = ""
if ($contentSourcePathFound) {
$preferredContentSourceFolderPath = Join-Path -Path $contentSourcePath -ChildPath "App"
$contentSourceFolderPath = $preferredContentSourceFolderPath
if ( -not (Test-Path -Path $contentSourceFolderPath)) {
Write-Host "$loglead : Not able to find the content folder $contentSourceFolderPath looking for another"
$contentSourceFSOFolder = (Get-ChildItem $contentSourcePath -Directory) | Sort-Object -Descending | Select-Object -First 1
# SRE-16977 - following line should be this
# $contentSourceFolderPath = $contentSourceFSOFolder.FullName
# However, developers implemented packages that UNINTENTIONALLY took advantage of this bug
# We must reintroduce this bug in order to not break the deployment of malformed packages
# This allows for the preceding Get-ChildItem to return $null and the following line to return
# the path it was trying to Get-ChildItem from without problem, instead of $null
$contentSourceFolderPath = Join-Path -Path $contentSourcePath -ChildPath $contentSourceFSOFolder.Name
}
Write-Host "$loglead : Found content folder $contentSourceFolderPath"
}
Write-Host "$loglead : Ready to install $WidgetName"
Write-Host "$loglead : $FILE_OPERATION_STRING DLL/LIB files from $libSourceFolderPath"
if ($contentSourcePathFound) {
Write-Host "$loglead : $FILE_OPERATION_STRING CONTENT files from $contentSourceFolderPath"
}
$appName = "WebClient"
if ($IsAdmin) {
$appName = "WebClientAdmin"
}
## Get the IIS sites by path then get the distinct application pool names for all sites bound to this path.
$ORB_PATH = Get-OrbPath
$orbBaseWebclientPath = Join-Path -Path $ORB_PATH -ChildPath $appName
Write-Host "$loglead : Orb Base webclient path $orbBaseWebclientPath"
$iisApplicationPools = @()
$iisSites = Get-IISSitesByPath -Path $orbBaseWebclientPath
foreach ($site in $iisSites) {
if ($site.ApplicationPool -notin $iisApplicationPools) {
$iisApplicationPools += $site.ApplicationPool
}
}
$appPathTemp = Join-Path -Path $ORB_PATH -ChildPath $appName
$orbAreaPath = Join-Path -Path $appPathTemp -ChildPath "Areas"
$orbAreaWidgetContentPath = Join-Path $orbAreaPath $WidgetName
$orbAreaBinPath = Join-Path $orbAreaWidgetContentPath "bin"
# In the case of using the symlink installer strategy, we just always goto to the app root bin
# In the non-symlink case, we would send to C:\Orb\WebClientAdmin\bin or C:\Orb\WebClient\Areas\<AreaName>\bin
if ($IsAdmin -or $usingSymlinkStrategy) {
$orbAreaBinPath = Join-Path -Path $appPathTemp -ChildPath "bin"
}
## Stop any/all $iisApplicationPools that are running.
$isRunning = $false
foreach ($appPool in $iisApplicationPools) {
Write-Host "$loglead : Test-IISAppPoolByName -Name $appPool"
$isSiteRunning = Test-IISAppPoolByName -Name $appPool
if ($isSiteRunning) {
$isRunning = $true
}
if($isSiteRunning) {
Write-Host "$loglead : Stopping $appPool"
(Stop-WebAppPool -Name $appPool) | Out-Null
Do {
Start-Sleep -Milliseconds 100
} Until ((Get-WebAppPoolState -Name $appPool).Value -eq "Stopped" )
}
}
if ($RemoveLogs -and $isRunning) {
$logfiles = Get-LogPathsForOrbApplication -AppName $appName
foreach($logPath in $logfiles) {
if (Test-Path -Path $logPath) {
Write-Host "$loglead : Removing $logPath"
(Remove-FileSystemItem -Path $logPath -ErrorAction Stop) | Out-Null
}
}
}
# Remove the folder to the widget areas in Orb before starting.
$symlinkAreaContentFolderAlreadyCorrect = $false
if ($usingSymlinkStrategy) {
$isOrbAreaWidgetPathSymlink = Test-IsSymlink -Path $orbAreaWidgetContentPath
Write-Host "$loglead : Is the existing ORB Widget Area path already a Symlink? $isOrbAreaWidgetPathSymlink"
if ($isOrbAreaWidgetPathSymlink) {
$symlinkAreaContentFolderAlreadyCorrect = Test-PathMatch -FirstPath $orbAreaWidgetContentPath -SecondPath $contentSourceFolderPath
}
}
if ((Test-Path -Path $orbAreaWidgetContentPath) -and -not $symlinkAreaContentFolderAlreadyCorrect) {
Write-Host "$loglead : Removing item $orbAreaWidgetContentPath"
(Remove-FileSystemItem -Path $orbAreaWidgetContentPath -Recurse -ErrorAction Stop) | Out-Null
}
if ($usingSymlinkStrategy) {
Write-Host "$loglead : $FILE_OPERATION_STRING folder $contentSourceFolderPath into Destination $orbAreaWidgetContentPath"
if ($contentSourcePathFound) {
# Link the <widget>\App\ folder to <orb-areas>\<widget-name>
New-Symlink -Path $contentSourceFolderPath -Destination $orbAreaPath -Name $WidgetName
}
Write-Host "$loglead : Get all of the dll, pdb and xml files in $libSourceFolderPath to be symlinked directly to $orbAreaBinPath"
$filesInlibSourceFolderPath = Get-ChildItem -Path $libSourceFolderPath -Recurse
$fileTypesToLink = @(".dll",".pdb",".xml")
foreach ($file in $filesInlibSourceFolderPath) {
if ($file.Extension -in $fileTypesToLink) {
$libFilePath = Join-Path -Path $libSourceFolderPath -ChildPath $file
$orbAreaBinFilePath = Join-Path -Path $orbAreaBinPath -ChildPath $file
if ( -not (Test-IsSymlink -Path $orbAreaBinFilePath)) {
New-Symlink -Path $libFilePath -Destination $orbAreaBinPath
}
}
}
} else {
## Legacy filecopy codepath
foreach ($iisSite in $iisSites) {
$tempFilePath = Get-SiteTempDirectoryPath -siteOrAppName $($iisSite.Name) ##$appName
if ($isRunning -and -not ([string]::IsNullOrWhiteSpace($tempFilePath)) -and (Test-Path -Path $tempFilePath)) {
Write-Host "$loglead : Removing item $tempFilePath"
(Remove-FileSystemItem -Path $tempFilePath -Recurse -ErrorAction Stop) | Out-Null
}
}
## Widget CONTENT Directory should never exist because we deleted it but let's make sure it does ...
if ( -not (Test-Path -Path $orbAreaWidgetContentPath)) {
Write-Host "$loglead : Creating directory $orbAreaWidgetContentPath"
(New-Item -Path $orbAreaWidgetContentPath -ItemType Directory) | Out-Null
}
if ($contentSourcePathFound) {
$copyWidgetContentScript = {
param($sbSourceFolder, $sbOrbPath)
$sbSourcePath = Join-Path -Path $sbSourceFolder -ChildPath '*'
(Copy-Item -Path $sbSourcePath -Destination $sbOrbPath -Recurse -Force) | Out-Null
}
Write-Host "$loglead : $FILE_OPERATION_STRING CONTENT source folder: $contentSourceFolderPath to CONTENT destination path: $orbAreaWidgetContentPath"
Invoke-CommandWithRetry -MaxRetries 3 -SecondsDelay 1 -Arguments @($contentSourceFolderPath, $orbAreaWidgetContentPath) -ScriptBlock $copyWidgetContentScript
}
## Widget LIB directory should never exist because we deleted it but let's make sure it does ...
if ( -not (Test-Path -Path $orbAreaBinPath)) {
Write-Host "$loglead : Creating directory $orbAreaBinPath"
(New-Item -Path $orbAreaBinPath -ItemType Directory) | Out-Null
}
Write-Host "$loglead : $FILE_OPERATION_STRING LIB source folder: $libSourceFolderPath to LIB destination path: $orbAreaBinPath"
$copyWidgetLibScript = {
param($sbSourceFolder, $sbOrbPath)
$sbSourcePath = Join-Path -Path $sbSourceFolder -ChildPath '*'
(Copy-Item -Path $sbSourcePath -Destination $sbOrbPath -Recurse -Force) | Out-Null
}
Invoke-CommandWithRetry -MaxRetries 3 -SecondsDelay 1 -Arguments @($libSourceFolderPath, $orbAreaBinPath) -ScriptBlock $copyWidgetLibScript
}
if ($isRunning) {
foreach ($appPool in $iisApplicationPools) {
Write-Host "$loglead : Starting App Pool: $appPool"
(Start-WebAppPool -Name $appPool) | Out-Null
}
}
}