Patch/Install-Release.ps1
<#
.SYNOPSIS The function performs upgrade of BC installation to new release .DESCRIPTION The function checks the database related to instance provided. Imports new objects and runs data upgrade procedures. It can temporary swap customer license in database with development license if it's needed for upgrade to succeed. Plugin scripts are available with the following names: BeforeImportPatch, AfterImportPatch, BeforeDataUpgrade, AfterDataUpgrade, BeforeUpgradeExtensions, AfterUpgradeExtensions. Plugin scripts should contain a call to Setup-UpgradeTask function and add an upgrade task before or after task mentioned in plugin name. Plugins can be placed in either root folder (ReleaseFolder) or in dot-folders (ReleaseSubFolder). Supported subfolders: * Extensions - should contain *.app files which will be automatically installed/upgraded on the instance * .name - folders started with the dot will be proposed as value for ReleaseSubFolder parameter, there might be several such folders but only one should be selected for installation .PARAMETER ServerInstance Specifies the Microsoft Dynaimcs Business Central Server instance that is used during upgrade .PARAMETER ReleaseFolder Path to folder where all release data stored. There must be a zip file in this folder named like "XXX_1_NNNNNNN.zip". This file will be theated like a main patch and will be instlled in process. .PARAMETER ReleaseSubFolder The name of subfolder in release folder which will be installe along with the release. By default this folder's name should start with the dot, then it will be hadnled by autocompletion. But you can provide any subfolder name manually. There must be a zip file in this folder named like "XXX_2_NNNNNNN.zip". This file will be theated like a supplimentary patch and will be instlled after a main patch in process. .PARAMETER DevLicense Specifies the path to the development license. This license will be imported to the database before the upgrade. .PARAMETER CustLicense Specifies the path to the customer license. This license will be imported to the database when upgrade is finished. .PARAMETER RapidStartPackageFile Specifies the path to the RapidStart package to be imported after the successfull upgrade .PARAMETER NoSleep If that switch is ON then all delays will be skipped. Only use it to speed up testing process. .PARAMETER Force Forces installation if only release subfolder is selected .EXAMPLE Install-Release -ServerInstance prod -DevLicense "C:\License\DevLicense.flf" -ReleaseFolder "c:\Release2021.01" -ReleaseSubFolder ".Customer01" #> function Install-Release { [alias("inrls")] [CmdletBinding()] param( [parameter(Mandatory=$true)] [string]$ServerInstance, [parameter(Mandatory=$false)] [string]$ReleaseFolder = (Get-Location), [parameter(Mandatory=$false)] [ArgumentCompleter({ param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) $SearchPath = $FakeBoundParams.ReleaseFolder Get-ChildItem -Path "$SearchPath\*" -Directory -Filter ".*" | % { """$($_.Name)""" } })] [Alias("iml")] [string]$ReleaseSubFolder, [parameter(Mandatory=$false)] [string]$DevLicense, [parameter(Mandatory=$false)] [string]$CustLicense, [parameter(Mandatory=$false,DontShow)] [switch]$NoSleep, [parameter(Mandatory=$false,DontShow)] [switch]$Force ) #Requires -RunAsAdministrator $ErrorActionPreference = "Stop" if ($NoSleep) { Write-Warning "Only use NoSleep switch for testing purposes" } $PrimaryPatch = Get-ChildItem -Path "$ReleaseFolder" -Filter "*_1_*.zip" | % { $_.FullName } if (!$PrimaryPatch) { if (!$Force) { $decision = $Host.UI.PromptForChoice('Warning! Primary patch not found!', "There is no patch in $ReleaseFolder. Are you sure you want to proceed and install only IML patch?", @('&Yes'; '&No'), 1) if ($decision -eq 1) { return } } else { Write-Warning "Primary patch not found under $ReleaseFolder. Execution forced without primary patch." } $PrimaryPatch = Join-Path $ReleaseFolder "\" # that trailing slash is important for GetDirectoryName } else { $PrimaryPatch | % { Write-Verbose "Primary patch > $_" } } if ($ReleaseSubFolder) { $SecondaryPatch = Get-ChildItem -Path (Join-Path $ReleaseFolder $ReleaseSubFolder) -Filter "*_2_*.zip" | % { $_.FullName } if (!$SecondaryPatch) { Write-Error "Secondary patch not found under $(Join-Path $ReleaseFolder $ReleaseSubFolder)" } $SecondaryPatch | % { Write-Verbose "Secondary patch > $_" } } try { ($UpgradeTasksStatistics = Start-Upgrade ` -NAVServerInstance $ServerInstance ` -PrimaryPatch $PrimaryPatch ` -SecondaryPatch $SecondaryPatch ` -DevLicense $DevLicense ` -CustLicense $CustLicense ` -NoSleep:$NoSleep ` ) | %{ if($_.Status -eq 'Failed') { Write-Error $_."Error" } } } finally { # Uncomment the following line in order to have a better rendered view, in a separate window, on the Upgrade Tasks Statistics # $UpgradeTasksStatistics | Out-GridView $UpgradeTasksStatistics | ft } } <# .SYNOPSIS The function performs upgrade of BC installation to new release .DESCRIPTION The function checks the database related to instance provided. Imports new objects and runs data upgrade procedures. It can temporary swap customer license in database with development license if it's needed for upgrade to succeed. .PARAMETER NAVServerInstance Specifies the Microsoft Dynaimcs Business Central Server instance that is used during upgrade .PARAMETER PrimaryPatch File name of the patch archive to be installed first .PARAMETER SecondaryPatch File name of the patch archive to be installed second .PARAMETER RapidStartPackageFile Specifies the path to the RapidStart package to be imported after the successfull upgrade .PARAMETER DevLicense Specifies the path to the development license .PARAMETER CustLicense Specifies the path to the customer license .PARAMETER NoSleep If that switch is ON then all delays will be skipped. Only use it to speed up testing process. #> function Start-Upgrade { [CmdletBinding()] param ( [parameter(Mandatory=$true)] [string]$NAVServerInstance, [parameter(Mandatory=$true)] [array]$PrimaryPatch, [array]$SecondaryPatch, [string]$DevLicense = "", [string]$CustLicense = "", [switch]$NoSleep ) BEGIN { Write-Verbose "=========================================================================================" Write-Verbose ("Upgrade script starting at " + (Get-Date).ToLongTimeString() + "...") Write-Verbose "=========================================================================================" } PROCESS { $ErrorActionPreference = "Stop" $error.Clear(); $RootFolder = [System.IO.Path]::GetDirectoryName($PrimaryPatch[0]) $SubFolder = if ($SecondaryPatch) { [System.IO.Path]::GetDirectoryName($SecondaryPatch[0]) } else { "" } # Ensure the NAV Management Module is loaded Import-NavModule -Service -Development # Ensure the SQLPS PowerShell module is loaded Test-SqlServerLoaded #region Verify that the prerequisites required by the script are met # The NAV Server Instance exists if($null -eq (Get-NAVServerInstance $NAVServerInstance)) { Write-Error "The Microsoft Dynamics NAV Server instance $NAVServerInstance does not exist." return } $NavServerName = "localhost" $DatabaseName = (Get-NAVServerConfiguration -ServerInstance $NavServerInstance -AsXML).SelectSingleNode("configuration/appSettings/add[@key='DatabaseName']").value $DatabaseServer = (Get-NAVServerConfiguration -ServerInstance $NavServerInstance -AsXML).SelectSingleNode("configuration/appSettings/add[@key='DatabaseServer']").value $DatabaseInstance = (Get-NAVServerConfiguration -ServerInstance $NavServerInstance -AsXML).SelectSingleNode("configuration/appSettings/add[@key='DatabaseInstance']").value $DatabaseServerInstance = Get-SqlServerInstance -DatabaseServer $DatabaseServer -DatabaseInstance $DatabaseInstance $IsMultitenant = (Get-NAVServerConfiguration -ServerInstance $NavServerInstance -AsXML).SelectSingleNode("configuration/appSettings/add[@key='Multitenant']").value $AdditionalInstances = Get-AdjacentInstances $NavServerInstance Write-Verbose "Upgrading > $DatabaseName @ $DatabaseServerInstance" # The NAV Database exists on the Sql Server Instance if(!(Test-NAVDatabaseOnSqlInstance -DatabaseServer $DatabaseServer -DatabaseInstance $DatabaseInstance -DatabaseName $DatabaseName)) { Write-Error "Database '$DatabaseName' does not exist on SQL Server instance '$DatabaseServerInstance'" return } #endregion # Initilize an empty list that will be populated with all the tasks that are executed part of Microsoft Dynamics NAV Data Upgrade process. # The list will include statistics regarding execution time, status and the associated script block $UpgradeTasks = [ordered]@{} #region Backup current license from the application part of the database (table '$ndo$dbproperty') , if it exists if($DevLicense -and !$CustLicense){ $CustLicense = [System.IO.Path]::GetTempFileName() . Setup-UpgradeTask ` -TaskName "Backup current application license" ` -ScriptBlock { Write-Verbose "Backup license to $CustLicense" Export-NAVLicense ` -DatabaseServer $DatabaseServerInstance ` -DatabaseName $DatabaseName ` -LicenseFilePath $CustLicense } | %{ $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } } #endregion #region Import development license, and restart the server in order for the license to be loaded if($DevLicense){ . Setup-UpgradeTask ` -TaskName "Import dev license" ` -ScriptBlock { Import-NAVServerLicense -ServerInstance $NAVServerInstance -LicenseFile $DevLicense -Database NavDatabase -WarningAction SilentlyContinue Set-NAVServerInstance -ServerInstance $NAVServerInstance -Restart } | %{ $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } } #endregion #region Synchronize the BC database . Setup-UpgradeTask ` -TaskName "Synchronize schema for all tables" ` -ScriptBlock { $ProgressPreferenceBack = $ProgressPreference; $ProgressPreference = "SilentlyContinue" Get-NAVTenant -ServerInstance $NavServerInstance | Sync-NAVTenant -ServerInstance $NavServerInstance -Mode Sync -force -ErrorAction Stop $ProgressPreference = $ProgressPreferenceBack; } | %{ $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } #endregion #region Check for unsynchronized database changes. If there are any, abort the upgrade . Setup-UpgradeTask ` -TaskName "Check for unsynchronized database changes" ` -ScriptBlock { if(!$NoSleep) { Start-Sleep -Seconds 15 } if ($IsMultitenant.ToUpper() -eq "TRUE") { $NavTenants = Get-NAVTenant $NavServerInstance Foreach ($NavTenant in $NavTenants) { # Synchronize schema for all tables $CurrentNavTenantSettings = Get-NAVTenant -ServerInstance $NavServerInstance -Tenant $NavTenant.Id if ($CurrentNavTenantSettings.state -ne "Operational") { Throw "Tenant " + $NavTenant.Id + " is not operational, the upgrade is aborted. Run Sync-navtenant with mode 'CheckOnly' to get more information." return } } } else { # Synchronize schema for all tables $NavTenantSettings = Get-NAVTenant -ServerInstance $NavServerInstance -Tenant Default if ($NavTenantSettings.state -ne "Operational") { Throw "Tenant " + $NavTenant.Id + " is not operational, the upgrade is aborted. Run Sync-navtenant with mode 'CheckOnly' to get more information." return } } } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } #endregion #Region Shutdown additional instances if ($AdditionalInstances) { . Setup-UpgradeTask ` -TaskName "Stop additional instances" ` -ScriptBlock { $AdditionalInstances | % { Write-Verbose "Stopping instance $_" } $AdditionalInstances | Stop-NAVServerInstance -ErrorAction Stop | Out-Null } | %{ $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } } #endregion #Region Restart BC Server Instance . Setup-UpgradeTask ` -TaskName "Start BC Server instance" ` -ScriptBlock { Set-NAVServerInstance $NavServerInstance -Start -Force -ErrorAction Stop } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } #endregion #Region BeforeImportPatch $pluginName = "BeforeImportPatch" $pluginPath = Join-Path $RootFolder "$pluginName.ps1" if ($pluginPath -and (Test-Path $pluginPath -PathType Leaf)) { . ($pluginPath) } $pluginPath = "" #endregion #Region Import objects #Region Import primary objects if ($PrimaryPatch -and (Test-Path $PrimaryPatch[0] -PathType Leaf)) { . Setup-UpgradeTask ` -TaskName "Importing update objects" ` -ScriptBlock { if(!$NoSleep) { Start-Sleep -Seconds 60 } $PrimaryPatch | % { Install-Patch -instanceName $NavServerInstance -path $_ -allowReinstall -allowRelease -noSleep:$NoSleep } } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } } #endregion #Region Import additional objects if ($SecondaryPatch -and (Test-Path $SecondaryPatch[0] -PathType Leaf)) { #Region BeforeImportPatch $pluginName = "BeforeImportPatch" $pluginPath = Join-Path $SubFolder "$pluginName.ps1" if ($pluginPath -and (Test-Path $pluginPath -PathType Leaf)) { . ($pluginPath) } $pluginPath = "" #endregion . Setup-UpgradeTask ` -TaskName "Importing update objects for IML" ` -ScriptBlock { if(!$NoSleep) { Start-Sleep -Seconds 15 } $SecondaryPatch | % { Install-Patch -instanceName $NavServerInstance -path $_ -allowReinstall -allowRelease -noSleep:$NoSleep } } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } } #endregion #endregion #Region AfterImportPatch $pluginName = "AfterImportPatch" $pluginPath = Join-Path $RootFolder "$pluginName.ps1" if ($pluginPath -and (Test-Path $pluginPath -PathType Leaf)) { . ($pluginPath) } if ($SubFolder) { $pluginPath = Join-Path $SubFolder "$pluginName.ps1" if ($pluginPath -and (Test-Path $pluginPath -PathType Leaf)) { . ($pluginPath) } } $pluginPath = "" #endregion #Region Perform Schema Synchronization for all tenants . Setup-UpgradeTask ` -TaskName "Compile uncompiled objects" ` -ScriptBlock { if(!$NoSleep) { Start-Sleep -Seconds 10 } Compile-NAVApplicationObject -DatabaseServer $DatabaseServerInstance -DatabaseName $DatabaseName ` -NavServerInstance $NavServerInstance -NavServerName $NavServerName ` -SynchronizeSchemaChanges No -ErrorAction SilentlyContinue } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } #endregion #Region Perform Schema Synchronization for all tenants . Setup-UpgradeTask ` -TaskName "Synchronize schema for all tables" ` -ScriptBlock { if(!$NoSleep) { Start-Sleep -Seconds 10 } $ProgressPreferenceBack = $ProgressPreference; $ProgressPreference = "SilentlyContinue" Get-NAVTenant -ServerInstance $NavServerInstance | Sync-NAVTenant -ServerInstance $NavServerInstance -Mode Sync -force -ErrorAction Stop $ProgressPreference = $ProgressPreferenceBack; } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } #endregion #Region MenuSuite Compilation . Setup-UpgradeTask ` -TaskName "MenuSuite objects compilation" ` -ScriptBlock { Compile-NAVApplicationObject -DatabaseServer $DatabaseServerInstance -DatabaseName $DatabaseName ` -Recompile -Filter 'Type=MenuSuite' ` -SynchronizeSchemaChanges No -ErrorAction Stop } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } #endregion #Region BeforeDataUpgrade $pluginName = "BeforeDataUpgrade" $pluginPath = Join-Path $RootFolder "$pluginName.ps1" if ($pluginPath -and (Test-Path $pluginPath -PathType Leaf)) { . ($pluginPath) } if ($SubFolder) { $pluginPath = Join-Path $SubFolder "$pluginName.ps1" if ($pluginPath -and (Test-Path $pluginPath -PathType Leaf)) { . ($pluginPath) } } $pluginPath = "" #endregion #Region Invoke the Data Upgrade process . Setup-UpgradeTask ` -TaskName "Invoke data upgrade" ` -ScriptBlock { $NavTenants = Get-NAVTenant $NavServerInstance -ErrorAction Stop $NavTenants | % { $_.Id } | Invoke-NAVDataUpgrade -ServerInstance $NAVServerInstance -ErrorAction Stop } | %{ $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } #endregion #Region AfterDataUpgrade $pluginName = "AfterDataUpgrade" $pluginPath = Join-Path $RootFolder "$pluginName.ps1" if ($pluginPath -and (Test-Path $pluginPath -PathType Leaf)) { . ($pluginPath) } if ($SubFolder) { $pluginPath = Join-Path $SubFolder "$pluginName.ps1" if ($pluginPath -and (Test-Path $pluginPath -PathType Leaf)) { . ($pluginPath) } } $pluginPath = "" #endregion #Region Delete Killme Objects . Setup-UpgradeTask ` -TaskName "Delete killme objects" ` -ScriptBlock { Delete-NAVApplicationObject -DatabaseServer $DatabaseServerInstance -DatabaseName $DatabaseName -NavServerName $NavServerName -NavServerInstance $NavServerInstance ` -NavServerManagementPort $NavManagementPort -Filter 'Name=@Kill*' -SynchronizeSchemaChanges No -Confirm:$false -ErrorAction Stop } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } #endregion #region Delete Upgrade Objects . Setup-UpgradeTask ` -TaskName "Delete upgrade objects" ` -ScriptBlock { Delete-NAVApplicationObject -DatabaseServer $DatabaseServerInstance -DatabaseName $DatabaseName -NavServerName $NavServerName -NavServerInstance $NavServerInstance ` -NavServerManagementPort $NavManagementPort -Filter 'Version List=@*UPG*' -SynchronizeSchemaChanges No -Confirm:$false -ErrorAction Stop } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } #endregion #region Synchronize schema for all killme tables . Setup-UpgradeTask ` -TaskName "Synchronize schema with force" ` -ScriptBlock { if(!$NoSleep) { Start-Sleep -Seconds 1 } $ProgressPreferenceBack = $ProgressPreference; $ProgressPreference = "SilentlyContinue" Get-NAVTenant -ServerInstance $NavServerInstance | Sync-NAVTenant -ServerInstance $NavServerInstance -Mode Force -force -ErrorAction Stop $ProgressPreference = $ProgressPreferenceBack; } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } #endregion #region Import customer license to database if($DevLicense){ . Setup-UpgradeTask ` -TaskName "Import customer license" ` -ScriptBlock { Write-Verbose "Restoring license from $CustLicense" Import-NAVServerLicense -ServerInstance $NAVServerInstance -LicenseFile $CustLicense -Database NavDatabase -WarningAction SilentlyContinue } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } } #endregion #Region ServiceTierRestart . Setup-UpgradeTask ` -TaskName "Restarting the NAV Server Instance" ` -ScriptBlock { Set-NAVServerInstance $NavServerInstance -Restart -Force -ErrorAction Stop } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } #endregion #Region BeforeUpgradeExtensions $pluginPath = Join-Path $ReleaseFolder "BeforeUpgradeExtensions.ps1" $pluginName = "BeforeUpgradeExtensions" $pluginPath = Join-Path $RootFolder "$pluginName.ps1" if ($pluginPath -and (Test-Path $pluginPath -PathType Leaf)) { . ($pluginPath) } if ($SubFolder) { $pluginPath = Join-Path $SubFolder "$pluginName.ps1" if ($pluginPath -and (Test-Path $pluginPath -PathType Leaf)) { . ($pluginPath) } } $pluginPath = "" #endregion #region Upgrade extensions $extensionFolder = (Join-Path $RootFolder "Extensions") if (Test-Path $extensionFolder -PathType Container ) { . Setup-UpgradeTask ` -TaskName "Upgrading extensions" ` -ScriptBlock { Upgrade-Extension -instanceName $NavServerInstance -path $extensionFolder } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } } if ($SubFolder) { $extensionSubFolder = (Join-Path $SubFolder "Extensions") if (Test-Path $extensionSubFolder -PathType Container ) { . Setup-UpgradeTask ` -TaskName "Upgrading extensions for $SubFolder" ` -ScriptBlock { Upgrade-Extension -instanceName $NavServerInstance -path $extensionSubFolder } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } } } #endregion #Region AfterUpgradeExtensions $pluginName = "AfterUpgradeExtensions" $pluginPath = Join-Path $RootFolder "$pluginName.ps1" if ($pluginPath -and (Test-Path $pluginPath -PathType Leaf)) { . ($pluginPath) } if ($SubFolder) { $pluginPath = Join-Path $SubFolder "$pluginName.ps1" if ($pluginPath -and (Test-Path $pluginPath -PathType Leaf)) { . ($pluginPath) } } $pluginPath = "" #endregion #Region Optionally, run RapidStart package import $RapidStartPackageFile = Get-ChildItem $RootFolder -Filter *.rapidstart if($RapidStartPackageFile) { . Setup-UpgradeTask ` -TaskName "RapidStart Package import" ` -ScriptBlock { $RapidStartPackageFile | % { Invoke-NAVRapidStartDataImport -ServerInstance $NAVServerInstance -RapidStartPackageFile $_.FullName } } | %{ $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } } if ($SubFolder) { $RapidStartPackageFile = Get-ChildItem $SubFolder -Filter *.rapidstart if($RapidStartPackageFile) { . Setup-UpgradeTask ` -TaskName "RapidStart Package import (subfolder)" ` -ScriptBlock { $RapidStartPackageFile | % { Invoke-NAVRapidStartDataImport -ServerInstance $NAVServerInstance -RapidStartPackageFile $_.FullName } } | %{ $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } } } #endregion #Region AdditionalServiceTierStart if ($AdditionalInstances) { . Setup-UpgradeTask ` -TaskName "Start additional instances" ` -ScriptBlock { $AdditionalInstances | Write-Verbose $AdditionalInstances | Start-NAVServerInstance -Force -ErrorAction Continue } | % { $UpgradeTasks.Add($_.Statistics, $_.ScriptBlock) } } #endregion # Run the upgrade tasks and stop if an error has occurred foreach($UpgradeTask in $UpgradeTasks.GetEnumerator()) { Execute-UpgradeTask -currentTask ([ref]$UpgradeTask.Name) -scriptBlock $UpgradeTask.Value if($UpgradeTask.Name.Status -eq 'Failed') { Write-Host -ForegroundColor DarkCyan "------------------------------------NOTE-------------------------------------------------" Write-Host -ForegroundColor DarkCyan "The development license is loaded into the $DatabaseName database." Write-Host -ForegroundColor DarkCyan "Customer license saved to $CustomerLicenseBackup" Write-Host -ForegroundColor DarkCyan "-----------------------------------------------------------------------------------------" Write-Host -ForegroundColor Red "-----------------------------------------------------------------------------------------" Write-Host -ForegroundColor Red "The upgrade completed with errors." Write-Host -ForegroundColor Red "-----------------------------------------------------------------------------------------" return $UpgradeTasks.Keys } } Write-Host -ForegroundColor Green "-----------------------------------------------------------------------------------------" Write-Host -ForegroundColor Green "The upgrade completed successfully." Write-Host -ForegroundColor Green "You can start the Microsoft Dynamics Business Central Windows client on the upgraded database using $NavServerInstance." Write-Host -ForegroundColor Green "-----------------------------------------------------------------------------------------" if ($DevLicense -and !$CustLicense) { Write-Host -ForegroundColor DarkCyan "------------------------------------NOTE-------------------------------------------------" Write-Host -ForegroundColor DarkCyan "The development license is loaded into the $DatabaseName database." Write-Host -ForegroundColor DarkCyan "-----------------------------------------------------------------------------------------" } return $UpgradeTasks.Keys } END { Write-Verbose "=========================================================================================" Write-Verbose ("Upgrade script finished at " + (Get-Date).ToLongTimeString() + ".") Write-Verbose "=========================================================================================" } } function Setup-UpgradeTask([string]$TaskName,[scriptblock]$ScriptBlock) { $initTaskStatistics = New-Object PSObject Add-Member -InputObject $initTaskStatistics -MemberType NoteProperty -Name "Upgrade Task" -Value $TaskName Add-Member -InputObject $initTaskStatistics -MemberType NoteProperty -Name "Start Time" -Value "" Add-Member -InputObject $initTaskStatistics -MemberType NoteProperty -Name "Duration" -Value "" Add-Member -InputObject $initTaskStatistics -MemberType NoteProperty -Name "Status" -Value 'NotStarted' Add-Member -InputObject $initTaskStatistics -MemberType NoteProperty -Name "Error" -Value "" $taskContent = New-Object PSObject Add-Member -InputObject $taskContent -MemberType NoteProperty -Name "Statistics" -Value $initTaskStatistics Add-Member -InputObject $taskContent -MemberType NoteProperty -name "ScriptBlock" -Value $ScriptBlock return $taskContent } function Execute-UpgradeTask([PSObject][ref]$currentTask, [scriptblock]$scriptBlock) { Write-Host "Running Upgrade Task `"$($currentTask.'Upgrade Task')`"..." $startTime = Get-Date $currentTask.'Start Time' = $startTime.ToLongTimeString() try { . $scriptBlock | Out-Null $currentTask."Status" = 'Completed' } catch [Exception] { $currentTask."Status" = 'Failed' $currentTask."Error" = $_.Exception.Message + [Environment]::NewLine + "Script stack trace: " + [Environment]::NewLine + $_.ScriptStackTrace } finally { $duration = NEW-TIMESPAN -Start $startTime $durationFormat = '{0:00}:{1:00}:{2:00}.{3:000}' -f $duration.Hours, $duration.Minutes, $duration.Seconds, $duration.Milliseconds $currentTask.'Duration' = $durationFormat } } . (Join-Path $PSScriptRoot "Cmdlets\Misc\ArgumentCompleter.ps1") Register-ArgumentCompleter -CommandName Install-Release -ParameterName ServerInstance -ScriptBlock $InstanceNameArgumentCompleter Export-ModuleMember -Alias "inrls" -Function "Install-Release" |