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\\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 \App\ folder to \ 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 } } }