SqlServerAlwaysOnTools.psm1
|
Set-StrictMode -Version Latest function Add-SmoAvailabilityDatabase { <# .EXTERNALHELP SqlServerAlwaysOnTools-Help.xml #> [System.Diagnostics.DebuggerStepThrough()] [CmdletBinding( PositionalBinding = $false, SupportsShouldProcess = $true, ConfirmImpact = 'Low', DefaultParameterSetName = 'ServerInstance' )] [OutputType([Microsoft.SqlServer.Management.Smo.AvailabilityDatabase])] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'ServerInstance' )] [ValidateLength(1, 128)] [Alias('SqlServer')] [string]$ServerInstance, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'ServerInstance' )] [ValidateLength(1, 128)] [string]$AvailabilityGroupName, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'ServerInstance' )] [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'SmoAvailabilityGroup' )] [ValidateLength(1, 128)] [string]$DatabaseName, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'SmoAvailabilityGroup' )] [Microsoft.SqlServer.Management.Smo.AvailabilityGroup]$AvailabilityGroupObject ) begin { try { if ($PSCmdlet.ParameterSetName -eq 'ServerInstance') { $AvailabilityGroupObjectParameters = @{ 'ServerInstance' = $ServerInstance 'AvailabilityGroupName' = $AvailabilityGroupName } $AvailabilityGroupObject = Get-SmoAvailabilityGroup @AvailabilityGroupObjectParameters } } catch { throw $_ } } process { try { if ($PSCmdlet.ShouldProcess($DatabaseName, 'Add database to availability group.')) { $SmoAvailabilityDatabase = [Microsoft.SqlServer.Management.Smo.AvailabilityDatabase]::New($AvailabilityGroupObject, $DatabaseName) $SmoAvailabilityDatabase.Create() if (-not $SmoAvailabilityDatabase.IsJoined) { $SmoAvailabilityDatabase.JoinAvailabilityGroup() } [void]$SmoAvailabilityDatabase.Initialize() $SmoAvailabilityDatabase } } catch { throw $_ } } end { } } function Get-SmoAvailabilityDatabase { <# .EXTERNALHELP SqlServerAlwaysOnTools-Help.xml #> [System.Diagnostics.DebuggerStepThrough()] [CmdletBinding( PositionalBinding = $false, SupportsShouldProcess = $false, ConfirmImpact = 'Low', DefaultParameterSetName = 'ServerInstance' )] [OutputType([Microsoft.SqlServer.Management.Smo.AvailabilityDatabase])] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'ServerInstance' )] [ValidateLength(1, 128)] [Alias('SqlServer')] [string]$ServerInstance, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false )] [ValidateLength(1, 128)] [string]$AvailabilityGroupName, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'SmoServer' )] [Microsoft.SqlServer.Management.Smo.Server]$SmoServerObject, [Parameter( Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false )] [ValidateLength(1, 128)] [string]$DatabaseName ) begin { try { if ($PSCmdlet.ParameterSetName -eq 'ServerInstance') { $SmoServerParameters = @{ 'ServerInstance' = $ServerInstance 'DatabaseName' = 'master' } $SmoServerObject = Connect-SmoServer @SmoServerParameters } } catch { if ($PSCmdlet.ParameterSetName -eq 'ServerInstance') { if (Test-Path -Path Variable:\SmoServerObject) { if ($SmoServerObject -is [Microsoft.SqlServer.Management.Smo.Server]) { Disconnect-SmoServer -SmoServerObject $SmoServerObject } } } throw $_ } } process { Try { $AvailabilityGroup = $SmoServerObject.AvailabilityGroups[$AvailabilityGroupName] if ($PSBoundParameters.ContainsKey('DatabaseName')) { $AvailabilityDatabases = $AvailabilityGroup.AvailabilityDatabases[$DatabaseName] if ($null -eq $AvailabilityDatabases) { throw [System.Management.Automation.ErrorRecord]::New( [Exception]::New('Availability Group database not found.'), '1', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $DatabaseName ) } } else { $AvailabilityDatabases = $AvailabilityGroup.AvailabilityDatabases } $AvailabilityDatabases } Catch { throw $_ } finally { if ($PSCmdlet.ParameterSetName -eq 'ServerInstance') { Disconnect-SmoServer -SmoServerObject $SmoServerObject } } } end { } } function Get-SmoAvailabilityDatabaseReplicaState { <# .EXTERNALHELP SqlServerAlwaysOnTools-Help.xml #> [System.Diagnostics.DebuggerStepThrough()] [CmdletBinding( PositionalBinding = $false, SupportsShouldProcess = $false, ConfirmImpact = 'Low', DefaultParameterSetName = 'ServerInstance' )] [OutputType([Microsoft.SqlServer.Management.Smo.DatabaseReplicaState])] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'ServerInstance' )] [ValidateLength(1,128)] [string]$ServerInstance, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'SmoServerObject' )] [Microsoft.SqlServer.Management.Smo.Server]$SmoServerObject, [Parameter( Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false )] [ValidateLength(1, 128)] [string]$AvailabilityGroupName, [Parameter( Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false )] [ValidateLength(1,128)] [string]$DatabaseName ) begin { try { $ServerInstanceParameterSets = @('ServerInstance') if ($PSCmdlet.ParameterSetName -in $ServerInstanceParameterSets) { $SmoServerParameters = @{ 'ServerInstance' = $ServerInstance 'DatabaseName' = 'master' } $SmoServerObject = Connect-SmoServer @SmoServerParameters } $SmoServerObject.Refresh() } catch { if ($PSCmdlet.ParameterSetName -in $ServerInstanceParameterSets) { if (Test-Path -Path Variable:\SmoServerObject) { if ($SmoServerObject -is [Microsoft.SqlServer.Management.Smo.Server]) { Disconnect-SmoServer -SmoServerObject $SmoServerObject } } } throw $_ } } process { try { $AvailabilityGroupParameters = @{ 'SmoServerObject' = $SmoServerObject } if ($PSBoundParameters.ContainsKey('AvailabilityGroupName')) { $AvailabilityGroupParameters.Add('AvailabilityGroupName', $AvailabilityGroupName) } $AvailabilityGroup = Get-SmoAvailabilityGroup @AvailabilityGroupParameters if ($PSBoundParameters.ContainsKey('DatabaseName')) { $Output = $AvailabilityGroup.DatabaseReplicaStates.where({$_.AvailabilityDatabaseName -eq $DatabaseName}) } else { $Output = $AvailabilityGroup.DatabaseReplicaStates } $Output } catch { throw $_ } finally { if ($PSCmdlet.ParameterSetName -in $ServerInstanceParameterSets) { if (Test-Path -Path Variable:\SmoServerObject) { Disconnect-SmoServer -SmoServerObject $SmoServerObject } } } } end { } } function Get-SmoAvailabilityGroup { <# .EXTERNALHELP SqlServerAlwaysOnTools-Help.xml #> [System.Diagnostics.DebuggerStepThrough()] [CmdletBinding( PositionalBinding = $false, SupportsShouldProcess = $false, ConfirmImpact = 'Low', DefaultParameterSetName = 'ServerInstance' )] [OutputType([Microsoft.SqlServer.Management.Smo.AvailabilityGroup])] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'ServerInstance' )] [ValidateLength(1, 128)] [Alias('SqlServer')] [string]$ServerInstance, [Parameter( Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false )] [ValidateLength(1, 128)] [string]$AvailabilityGroupName, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'SmoServer' )] [Microsoft.SqlServer.Management.Smo.Server]$SmoServerObject ) begin { try { if ($PSCmdlet.ParameterSetName -eq 'ServerInstance') { $SmoServerParameters = @{ 'ServerInstance' = $ServerInstance 'DatabaseName' = 'master' } $SmoServerObject = Connect-SmoServer @SmoServerParameters } } catch { if ($PSCmdlet.ParameterSetName -eq 'ServerInstance') { if (Test-Path -Path Variable:\SmoServerObject) { if ($SmoServerObject -is [Microsoft.SqlServer.Management.Smo.Server]) { Disconnect-SmoServer -SmoServerObject $SmoServerObject } } } throw $_ } } process { Try { if ($PSBoundParameters.ContainsKey('AvailabilityGroupName')) { $AvailabilityGroups = $SmoServerObject.AvailabilityGroups[$AvailabilityGroupName] if ($null -eq $AvailabilityGroups) { throw [System.Management.Automation.ErrorRecord]::New( [Exception]::New('Availability Group not found.'), '1', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $AvailabilityGroupName ) } } else { $AvailabilityGroups = $SmoServerObject.AvailabilityGroups } $AvailabilityGroups } Catch { throw $_ } finally { if ($PSCmdlet.ParameterSetName -eq 'ServerInstance') { Disconnect-SmoServer -SmoServerObject $SmoServerObject } } } end { } } function Get-SmoAvailabilityGroupListener { <# .EXTERNALHELP SqlServerAlwaysOnTools-Help.xml #> [System.Diagnostics.DebuggerStepThrough()] [CmdletBinding( PositionalBinding = $false, SupportsShouldProcess = $false, ConfirmImpact = 'Low', DefaultParameterSetName = 'ServerInstance' )] [OutputType([Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener])] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'ServerInstance' )] [ValidateLength(1,128)] [string]$ServerInstance, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'SmoServerObject' )] [Microsoft.SqlServer.Management.Smo.Server]$SmoServerObject, [Parameter( Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false )] [ValidateLength(1, 128)] [string]$AvailabilityGroupName ) begin { try { $ServerInstanceParameterSets = @('ServerInstance') if ($PSCmdlet.ParameterSetName -in $ServerInstanceParameterSets) { $SmoServerParameters = @{ 'ServerInstance' = $ServerInstance 'DatabaseName' = 'master' } $SmoServerObject = Connect-SmoServer @SmoServerParameters } $SmoServerObject.Refresh() } catch { if ($PSCmdlet.ParameterSetName -in $ServerInstanceParameterSets) { if (Test-Path -Path Variable:\SmoServerObject) { if ($SmoServerObject -is [Microsoft.SqlServer.Management.Smo.Server]) { Disconnect-SmoServer -SmoServerObject $SmoServerObject } } } throw $_ } } process { try { $AvailabilityGroupParameters = @{ 'SmoServerObject' = $SmoServerObject } if ($PSBoundParameters.ContainsKey('AvailabilityGroupName')) { $AvailabilityGroupParameters.Add('AvailabilityGroupName', $AvailabilityGroupName) } $AvailabilityGroup = Get-SmoAvailabilityGroup @AvailabilityGroupParameters $AvailabilityGroup.AvailabilityGroupListener } catch { throw $_ } finally { if ($PSCmdlet.ParameterSetName -in $ServerInstanceParameterSets) { if (Test-Path -Path Variable:\SmoServerObject) { Disconnect-SmoServer -SmoServerObject $SmoServerObject } } } } end { } } function Get-SmoAvailabilityGroupSeedingStatus { <# .EXTERNALHELP SqlServerAlwaysOnTools-Help.xml #> [System.Diagnostics.DebuggerStepThrough()] [CmdletBinding( PositionalBinding = $false, SupportsShouldProcess = $false, ConfirmImpact = 'Low', DefaultParameterSetName = 'ServerInstance' )] [OutputType([System.Data.DataRow])] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'ServerInstance' )] [ValidateLength(1,128)] [string]$ServerInstance, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'SqlConnection' )] [Microsoft.Data.SqlClient.SqlConnection]$SqlConnection, [Parameter( Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false )] [ValidateLength(1,128)] [string]$DatabaseName ) begin { $StatusFormatString = "SELECT dhas.ag_db_id , ag.name AS AvailabilityGroupName , adb.database_name , dhas.start_time , dhas.completion_time , dhas.current_state , dhas.performed_seeding , dhas.failure_state , dhas.failure_state_desc , ss.internal_state_desc , ss.remote_machine_name , DatabaseSizeMB = ss.database_size_bytes / 1045876 , TransferredSizeMB = ss.transferred_size_bytes / 1045876 , TransferRateMBS = ss.transfer_rate_bytes_per_second / 1045876 , TimeRemainingSec = CASE WHEN ss.transfer_rate_bytes_per_second = 0 THEN NULL ELSE (ss.database_size_bytes - ss.transferred_size_bytes) / ss.transfer_rate_bytes_per_second END , TimeRemaining = CASE WHEN ss.transfer_rate_bytes_per_second = 0 THEN NULL ELSE CASE WHEN ((ss.database_size_bytes - ss.transferred_size_bytes) / ss.transfer_rate_bytes_per_second) < 360000 THEN '0' ELSE '' END + RTRIM(((ss.database_size_bytes - ss.transferred_size_bytes) / ss.transfer_rate_bytes_per_second) / 3600) + ':' + RIGHT('0' + RTRIM(((ss.database_size_bytes - ss.transferred_size_bytes) / ss.transfer_rate_bytes_per_second) % 3600 / 60), 2) + ':' + RIGHT('0' + RTRIM(((ss.database_size_bytes - ss.transferred_size_bytes) / ss.transfer_rate_bytes_per_second) % 60), 2) END FROM sys.dm_hadr_automatic_seeding dhas JOIN sys.availability_databases_cluster adb ON dhas.ag_db_id = adb.group_database_id JOIN sys.availability_groups ag ON dhas.ag_id = ag.group_id LEFT JOIN sys.dm_hadr_physical_seeding_stats ss ON dhas.operation_id = ss.local_physical_seeding_id{0} ORDER BY ag.name , adb.database_name , dhas.completion_time DESC;" $WhereClauseFormatString = "`r`n`t`tWHERE adb.database_name = N'{0}'" } process { try { if ($PSBoundParameters.ContainsKey('DatabaseName')) { $WhereClause = [string]::Format($WhereClauseFormatString, $DatabaseName) } else { $WhereClause = '' } $SqlClientDataSetParameters = @{ 'DatabaseName' = 'master' 'SqlCommandText' = [string]::Format($StatusFormatString, $WhereClause) 'OutputAs' = 'DataRow' } if ($PSBoundParameters.ContainsKey('ServerInstance')) { $SqlClientDataSetParameters.Add('ServerInstance', $ServerInstance) } if ($PSBoundParameters.ContainsKey('SqlConnection')) { $SqlClientDataSetParameters.Add('SqlConnection', $SqlConnection) } $StatusDataTable = Get-SqlClientDataSet @SqlClientDataSetParameters $StatusDataTable } catch { throw $_ } } end { } } function Move-SqlAvailabilityGroup { <# .EXTERNALHELP SqlServerAlwaysOnTools-Help.xml #> [System.Diagnostics.DebuggerStepThrough()] [CmdletBinding( PositionalBinding = $false, SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ServerInstance' )] [OutputType([System.Void])] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'ServerInstance' )] [ValidateLength(1,128)] [string]$ServerInstance, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'SmoServerObject' )] [Microsoft.SqlServer.Management.Smo.Server]$SmoServerObject, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false )] [ValidateLength(1, 128)] [string]$AvailabilityGroupName, [Parameter( Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false )] [switch]$AllowDataLoss ) begin { try { $ServerInstanceParameterSets = @('ServerInstance') if ($PSCmdlet.ParameterSetName -in $ServerInstanceParameterSets) { $SmoServerParameters = @{ 'ServerInstance' = $ServerInstance 'DatabaseName' = 'master' 'StatementTimeout' = 0 } $SmoServerObject = Connect-SmoServer @SmoServerParameters } } catch { if ($PSCmdlet.ParameterSetName -in $ServerInstanceParameterSets) { if (Test-Path -Path Variable:\SmoServerObject) { if ($SmoServerObject -is [Microsoft.SqlServer.Management.Smo.Server]) { Disconnect-SmoServer -SmoServerObject $SmoServerObject } } } throw $_ } [int]$TotalSteps = 7 [int]$CurrentStep = 0 $ProgressParameters = @{ 'Id' = 0 'Activity' = 'Failover Availability Group' 'Status' = [string]::Format('Step {0} of {1}', $CurrentStep, $TotalSteps) 'CurrentOperation' = '' 'PercentComplete' = 0 } $RetryIntervalSeconds = 10 $RetryCount = 30 } process { try { $CurrentStep++ $ProgressParameters.Status = [string]::Format('Step {0} of {1}', $CurrentStep, $TotalSteps) $ProgressParameters.CurrentOperation = 'Checking primary replica...' $ProgressParameters.PercentComplete = ($CurrentStep - 1) / $TotalSteps * 100 Write-Verbose $ProgressParameters.CurrentOperation Write-Progress @ProgressParameters $AvailabilityGroup = Get-SmoAvailabilityGroup -SmoServerObject $SmoServerObject -AvailabilityGroupName $AvailabilityGroupName if ($AvailabilityGroup.PrimaryReplicaServerName -eq $SmoServerObject.NetName) { throw [System.Management.Automation.ErrorRecord]::New( [Exception]::New('The availability group is already on the primary replica.'), '1', [System.Management.Automation.ErrorCategory]::InvalidOperation, $AvailabilityGroupName ) } $CurrentStep++ $ProgressParameters.Status = [string]::Format('Step {0} of {1}', $CurrentStep, $TotalSteps) $ProgressParameters.CurrentOperation = 'Checking availability databases...' $ProgressParameters.PercentComplete = ($CurrentStep - 1) / $TotalSteps * 100 Write-Verbose $ProgressParameters.CurrentOperation Write-Progress @ProgressParameters $AvailabilityDatabases = Get-SmoAvailabilityDatabase -SmoServerObject $SmoServerObject -AvailabilityGroupName $AvailabilityGroupName if ($AvailabilityDatabases.where({$_.IsFailoverReady -eq $false}).Count -gt 0) { throw [System.Management.Automation.ErrorRecord]::New( [Exception]::New('One or more databases in the availability group are not failover ready.'), '1', [System.Management.Automation.ErrorCategory]::InvalidOperation, $AvailabilityGroupName ) } $CurrentStep++ $ProgressParameters.Status = [string]::Format('Step {0} of {1}', $CurrentStep, $TotalSteps) $ProgressParameters.CurrentOperation = 'Checking availability mode...' $ProgressParameters.PercentComplete = ($CurrentStep - 1) / $TotalSteps * 100 Write-Verbose $ProgressParameters.CurrentOperation Write-Progress @ProgressParameters $TargetReplica = $AvailabilityGroup.AvailabilityReplicas.Where({$_.Name -ne $SmoServerObject.NetName}) $PrimaryReplica = $AvailabilityGroup.AvailabilityReplicas.Where({$_.Name -eq $AvailabilityGroup.PrimaryReplicaServerName}) if ($TargetReplica.AvailabilityMode -ne 'SynchronousCommit') { if (-not $AllowDataLoss) { throw [System.Management.Automation.ErrorRecord]::New( [Exception]::New('The availability replica is not configured for synchronous commit. Use the -AllowDataLoss switch to allow failover with potential data loss.'), '1', [System.Management.Automation.ErrorCategory]::InvalidOperation, $TargetReplica.Name ) } else { Write-Warning 'The availability replica is not configured for synchronous commit. Proceeding with failover with potential data loss.' } } $CurrentStep++ $ProgressParameters.Status = [string]::Format('Step {0} of {1}', $CurrentStep, $TotalSteps) $ProgressParameters.CurrentOperation = 'Checking replica states...' $ProgressParameters.PercentComplete = ($CurrentStep - 1) / $TotalSteps * 100 Write-Verbose $ProgressParameters.CurrentOperation Write-Progress @ProgressParameters if ($TargetReplica.MemberState -ne 'Online') { throw [System.Management.Automation.ErrorRecord]::New( [Exception]::New('The availability replica is not connected.'), '1', [System.Management.Automation.ErrorCategory]::InvalidOperation, $TargetReplica.Name ) } if ($PrimaryReplica.MemberState -ne 'Online') { if (-not $AllowDataLoss) { throw [System.Management.Automation.ErrorRecord]::New( [Exception]::New('The primary replica is not connected. Use the -AllowDataLoss switch to allow failover with potential data loss.'), '1', [System.Management.Automation.ErrorCategory]::InvalidOperation, $PrimaryReplica.Name ) } else { Write-Warning 'The primary replica is not connected. Proceeding with failover with potential data loss.' } } $CurrentStep++ $ProgressParameters.Status = [string]::Format('Step {0} of {1}', $CurrentStep, $TotalSteps) $ProgressParameters.CurrentOperation = 'Checking for running jobs...' $ProgressParameters.PercentComplete = ($CurrentStep - 1) / $TotalSteps * 100 Write-Verbose $ProgressParameters.CurrentOperation Write-Progress @ProgressParameters $PrimaryReplicaFQDN = [System.Text.RegularExpressions.Regex]::Match($PrimaryReplica.EndpointUrl, '(?<=:\/\/)[^:\/]+').Value $PrimaryReplicaSmoServer = Connect-SmoServer -ServerInstance $PrimaryReplicaFQDN -DatabaseName 'master' $PrimaryReplicaJobServer = $PrimaryReplicaSmoServer.JobServer $TargetReplicaJobServer = $SmoServerObject.JobServer $ProgressParameters1 = @{ 'Id' = 1 'ParentID' = 0 'Activity' = 'Checking for running jobs' 'Status' = [string]::Format('Step {0} of {1}', 0, $RetryCount) 'CurrentOperation' = '' 'PercentComplete' = 0 / $RetryCount * 100 } for ($i = 0; $i -lt $RetryCount; $i++) { $ProgressParameters1.Activity = 'Waiting for jobs to complete before proceeding with failover.' $ProgressParameters1.Status = [string]::Format('Check {0} of {1}', $i + 1, $RetryCount) $ProgressParameters1.CurrentOperation = [string]::Format('Check {0}', $i + 1) $ProgressParameters1.PercentComplete = $i / $RetryCount * 100 Write-Verbose $ProgressParameters1.CurrentOperation Write-Progress @ProgressParameters1 $TotalRunningJobs = $PrimaryReplicaJobServer.Jobs.Where({$_.CurrentRunStatus -eq 'Executing'}).Count + $TargetReplicaJobServer.Jobs.Where({$_.CurrentRunStatus -eq 'Executing'}).Count if ($TotalRunningJobs -eq 0) { break } else { Start-Sleep -Seconds $RetryIntervalSeconds foreach ($Jobs in @($PrimaryReplicaJobServer.Jobs, $TargetReplicaJobServer.Jobs)) { foreach ($Job in $Jobs) { $Job.Refresh() } } } } Write-Progress -Id 1 -Activity $ProgressParameters1.Activity -Completed if ($TotalRunningJobs -gt 0) { throw [System.Management.Automation.ErrorRecord]::New( [Exception]::New('One or more jobs are still running on the availability replicas. Please check for running jobs and ensure all jobs are completed before proceeding with failover.'), '1', [System.Management.Automation.ErrorCategory]::InvalidOperation, $AvailabilityGroupName ) } $CurrentStep++ $ProgressParameters.Status = [string]::Format('Step {0} of {1}', $CurrentStep, $TotalSteps) $ProgressParameters.CurrentOperation = 'Failing over availability group...' $ProgressParameters.PercentComplete = ($CurrentStep - 1) / $TotalSteps * 100 Write-Verbose $ProgressParameters.CurrentOperation Write-Progress @ProgressParameters if ($PSCmdlet.ShouldProcess($AvailabilityGroupName, 'Move availability group')) { if ($AllowDataLoss) { $AvailabilityGroup.FailoverWithPotentialDataLoss() } else { $AvailabilityGroup.Failover() } Write-Verbose "Failover of availability group $AvailabilityGroupName initiated successfully." $AvailabilityGroup.Refresh() Write-Verbose "Current primary replica for availability group $AvailabilityGroupName is $($AvailabilityGroup.PrimaryReplicaServerName)." } $CurrentStep++ $ProgressParameters.Status = [string]::Format('Step {0} of {1}', $CurrentStep, $TotalSteps) $ProgressParameters.CurrentOperation = 'Checking synchronization state...' $ProgressParameters.PercentComplete = ($CurrentStep - 1) / $TotalSteps * 100 Write-Verbose $ProgressParameters.CurrentOperation Write-Progress @ProgressParameters $ProgressParameters1 = @{ 'Id' = 1 'ParentID' = 0 'Activity' = 'Checking for running jobs' 'Status' = [string]::Format('Step {0} of {1}', 0, $RetryCount) 'CurrentOperation' = '' 'PercentComplete' = 0 / $RetryCount * 100 } if ($PSCmdlet.ShouldProcess($AvailabilityGroupName, 'Check synchronization state of availability databases after failover')) { for ($i = 0; $i -lt $RetryCount; $i++) { $ProgressParameters1.Activity = 'Waiting for jobs to complete before proceeding with failover.' $ProgressParameters1.Status = [string]::Format('Check {0} of {1}', $i + 1, $RetryCount) $ProgressParameters1.CurrentOperation = [string]::Format('Check {0}', $i + 1) $ProgressParameters1.PercentComplete = $i / $RetryCount * 100 Write-Verbose $ProgressParameters1.CurrentOperation Write-Progress @ProgressParameters1 $PostFailoverAvailabilityDatabases = Get-SmoAvailabilityDatabase -SmoServerObject $SmoServerObject -AvailabilityGroupName $AvailabilityGroupName $NotSynchronizedDatabases = $PostFailoverAvailabilityDatabases.Where({$_.SynchronizationState -ne 'Synchronized'}) if ($NotSynchronizedDatabases.Count -eq 0) { Write-Verbose "All databases in availability group $AvailabilityGroupName are synchronized after failover." break } else { Write-Verbose "Waiting for databases in availability group $AvailabilityGroupName to synchronize after failover. Attempt $($i + 1) of $RetryCount." Start-Sleep -Seconds $RetryIntervalSeconds } } Write-Progress -Id 1 -Activity $ProgressParameters1.Activity -Completed if ($NotSynchronizedDatabases.Count -gt 0) { Write-Warning "One or more databases in availability group $AvailabilityGroupName are not synchronized after failover. Please check the synchronization state of the databases and resolve any issues as necessary. Not synchronized databases: $($NotSynchronizedDatabases.Name -join ', ')." } } } catch { throw $_ } finally { if ($PSCmdlet.ParameterSetName -in $ServerInstanceParameterSets) { Disconnect-SmoServer -SmoServerObject $SmoServerObject } if (Test-Path -Path Variable:\ProgressParameters1) { Write-Progress -Id 1 -Activity $ProgressParameters1.Activity -Completed } Write-Progress -Id 0 -Activity 'Failover Availability Group' -Completed } } end { } } function Remove-SmoAvailabilityDatabase { <# .EXTERNALHELP SqlServerAlwaysOnTools-Help.xml #> [System.Diagnostics.DebuggerStepThrough()] [CmdletBinding( PositionalBinding = $false, SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ServerInstance' )] [OutputType([Microsoft.SqlServer.Management.Smo.AvailabilityGroup])] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'ServerInstance' )] [ValidateLength(1, 128)] [Alias('SqlServer')] [string]$ServerInstance, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'ServerInstance' )] [ValidateLength(1, 128)] [string]$AvailabilityGroupName, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'ServerInstance' )] [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'SmoAvailabilityGroup' )] [ValidateLength(1, 128)] [string]$DatabaseName, [Parameter( Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, ParameterSetName = 'SmoAvailabilityGroup' )] [Microsoft.SqlServer.Management.Smo.AvailabilityGroup]$AvailabilityGroupObject, [Parameter( Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false )] [switch]$RemoveReplicaDatabase ) begin { try { if ($PSCmdlet.ParameterSetName -eq 'ServerInstance') { $AvailabilityGroupObject = Get-SmoAvailabilityGroup -ServerInstance $ServerInstance -AvailabilityGroupName $AvailabilityGroupName } } catch { throw $_ } } process { try { if ($DatabaseName -NotIn $AvailabilityGroupObject.AvailabilityDatabases.Name) { throw [System.Management.Automation.ErrorRecord]::New( [Exception]::New('Database not found in availability group.'), '1', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $DatabaseName ) } $SmoAvailabilityDatabase = $AvailabilityGroupObject.AvailabilityDatabases[$DatabaseName] if ($PSCmdlet.ShouldProcess($DatabaseName, 'Remove database from availability group')) { $SmoAvailabilityDatabase.Drop() } if ($RemoveReplicaDatabase) { $SecondaryReplicas = $AvailabilityGroupObject.AvailabilityReplicas.where({$_.Role -eq 'Secondary'}) foreach ($SecondaryReplica in [Uri]$SecondaryReplicas.EndpointUrl) { $DatabaseObject = Get-SmoDatabaseObject -ServerInstance $SecondaryReplica.Host -DatabaseName $DatabaseName if ($PSCmdlet.ShouldProcess($DatabaseName, "Drop database from secondary node")) { $StopLoop = $false $Attempt = 0 do { try { $DatabaseObject.Drop() $StopLoop = $true } catch { if ($Attempt -gt 2) { Write-Warning "Unable to drop database $Database from $($SecondaryReplica.Host). Database will need to be manually removed." $StopLoop = $true } else { Start-Sleep -Milliseconds 500 $Attempt++ } } } while (-not $StopLoop) } } $AvailabilityGroupObject.Refresh() Start-Sleep -Milliseconds 500 } } catch { throw $_ } finally { if ($PSCmdlet.ParameterSetName -eq 'ServerInstance') { Disconnect-SmoServer -SmoServerObject $AvailabilityGroupObject.Parent } } } end { } } # SIG # Begin signature block # MIInywYJKoZIhvcNAQcCoIInvDCCJ7gCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCLhWPoFj44GJvL # Op1/9TNtSa4KPgVNk+CzYIcnrg1awKCCINswggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwgga0MIIEnKADAgECAhANx6xXBf8hmS5AQyIMOkmGMA0GCSqG # SIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0yNTA1MDcwMDAwMDBaFw0zODAxMTQyMzU5NTlaMGkx # CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4 # RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYg # MjAyNSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0eDHTCphB # cr48RsAcrHXbo0ZodLRRF51NrY0NlLWZloMsVO1DahGPNRcybEKq+RuwOnPhof6p # vF4uGjwjqNjfEvUi6wuim5bap+0lgloM2zX4kftn5B1IpYzTqpyFQ/4Bt0mAxAHe # HYNnQxqXmRinvuNgxVBdJkf77S2uPoCj7GH8BLuxBG5AvftBdsOECS1UkxBvMgEd # gkFiDNYiOTx4OtiFcMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3mmdglTcaarps0wjU # jsZvkgFkriK9tUKJm/s80FiocSk1VYLZlDwFt+cVFBURJg6zMUjZa/zbCclF83bR # VFLeGkuAhHiGPMvSGmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS5xLrfxnGpTXiUOeS # LsJygoLPp66bkDX1ZlAeSpQl92QOMeRxykvq6gbylsXQskBBBnGy3tW/AMOMCZIV # NSaz7BX8VtYGqLt9MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqLXvJWnY0v5ydPpOjL # 6s36czwzsucuoKs7Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7psNOdgJMoiwOrUG2Zd # SoQbU2rMkpLiQ6bGRinZbI4OLu9BMIFm1UUl9VnePs6BaaeEWvjJSjNm2qA+sdFU # eEY0qVjPKOWug/G6X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCCAVkwEgYDVR0TAQH/ # BAgwBgEB/wIBADAdBgNVHQ4EFgQU729TSunkBnx6yuKQVvYv1Ensy04wHwYDVR0j # BBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1Ud # JQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0 # cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0 # cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8E # PDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz # dGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEw # DQYJKoZIhvcNAQELBQADggIBABfO+xaAHP4HPRF2cTC9vgvItTSmf83Qh8WIGjB/ # T8ObXAZz8OjuhUxjaaFdleMM0lBryPTQM2qEJPe36zwbSI/mS83afsl3YTj+IQhQ # E7jU/kXjjytJgnn0hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgXf9r5nWMQwr8Myb9r # EVKChHyfpzee5kH0F8HABBgr0UdqirZ7bowe9Vj2AIMD8liyrukZ2iA/wdG2th9y # 1IsA0QF8dTXqvcnTmpfeQh35k5zOCPmSNq1UH410ANVko43+Cdmu4y81hjajV/gx # dEkMx1NKU4uHQcKfZxAvBAKqMVuqte69M9J6A47OvgRaPs+2ykgcGV00TYr2Lr3t # y9qIijanrUR3anzEwlvzZiiyfTPjLbnFRsjsYg39OlV8cipDoq7+qNNjqFzeGxcy # tL5TTLL4ZaoBdqbhOhZ3ZRDUphPvSRmMThi0vw9vODRzW6AxnJll38F0cuJG7uEB # YTptMSbhdhGQDpOXgpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAlZ66RzIg9sC+NJpud # /v4+7RWsWCiKi9EOLLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1MIDpMPx0LckTetiS # uEtQvLsNz3Qbp7wGWqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZsq8WhbaM2tszWkPZP # ubdcMIIGuTCCBKGgAwIBAgIRAJmjgAomVTtlq9xuhKaz6jkwDQYJKoZIhvcNAQEM # BQAwgYAxCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2ll # cyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx # JDAiBgNVBAMTG0NlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EgMjAeFw0yMTA1MTkw # NTMyMThaFw0zNjA1MTgwNTMyMThaMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhB # c3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNp # Z25pbmcgMjAyMSBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJ0j # zwQwIzvBRiznM3M+Y116dbq+XE26vest+L7k5n5TeJkgH4Cyk74IL9uP61olRsxs # U/WBAElTMNQI/HsE0uCJ3VPLO1UufnY0qDHG7yCnJOvoSNbIbMpT+Cci75scCx7U # sKK1fcJo4TXetu4du2vEXa09Tx/bndCBfp47zJNsamzUyD7J1rcNxOw5g6FJg0Im # Iv7nCeNn3B6gZG28WAwe0mDqLrvU49chyKIc7gvCjan3GH+2eP4mYJASflBTQ3HO # s6JGdriSMVoD1lzBJobtYDF4L/GhlLEXWgrVQ9m0pW37KuwYqpY42grp/kSYE4BU # QrbLgBMNKRvfhQPskDfZ/5GbTCyvlqPN+0OEDmYGKlVkOMenDO/xtMrMINRJS5SY # +jWCi8PRHAVxO0xdx8m2bWL4/ZQ1dp0/JhUpHEpABMc3eKax8GI1F03mSJVV6o/n # mmKqDE6TK34eTAgDiBuZJzeEPyR7rq30yOVw2DvetlmWssewAhX+cnSaaBKMEj9O # 2GgYkPJ16Q5Da1APYO6n/6wpCm1qUOW6Ln1J6tVImDyAB5Xs3+JriasaiJ7P5KpX # eiVV/HIsW3ej85A6cGaOEpQA2gotiUqZSkoQUjQ9+hPxDVb/Lqz0tMjp6RuLSKAR # sVQgETwoNQZ8jCeKwSQHDkpwFndfCceZ/OfCUqjxAgMBAAGjggFVMIIBUTAPBgNV # HRMBAf8EBTADAQH/MB0GA1UdDgQWBBTddF1MANt7n6B0yrFu9zzAMsBwzTAfBgNV # HSMEGDAWgBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwEwYD # VR0lBAwwCgYIKwYBBQUHAwMwMAYDVR0fBCkwJzAloCOgIYYfaHR0cDovL2NybC5j # ZXJ0dW0ucGwvY3RuY2EyLmNybDBsBggrBgEFBQcBAQRgMF4wKAYIKwYBBQUHMAGG # HGh0dHA6Ly9zdWJjYS5vY3NwLWNlcnR1bS5jb20wMgYIKwYBBQUHMAKGJmh0dHA6 # Ly9yZXBvc2l0b3J5LmNlcnR1bS5wbC9jdG5jYTIuY2VyMDkGA1UdIAQyMDAwLgYE # VR0gADAmMCQGCCsGAQUFBwIBFhhodHRwOi8vd3d3LmNlcnR1bS5wbC9DUFMwDQYJ # KoZIhvcNAQEMBQADggIBAHWIWA/lj1AomlOfEOxD/PQ7bcmahmJ9l0Q4SZC+j/v0 # 9CD2csX8Yl7pmJQETIMEcy0VErSZePdC/eAvSxhd7488x/Cat4ke+AUZZDtfCd8y # HZgikGuS8mePCHyAiU2VSXgoQ1MrkMuqxg8S1FALDtHqnizYS1bIMOv8znyJjZQE # Sp9RT+6NH024/IqTRsRwSLrYkbFq4VjNn/KV3Xd8dpmyQiirZdrONoPSlCRxCIi5 # 4vQcqKiFLpeBm5S0IoDtLoIe21kSw5tAnWPazS6sgN2oXvFpcVVpMcq0C4x/CLSN # e0XckmmGsl9z4UUguAJtf+5gE8GVsEg/ge3jHGTYaZ/MyfujE8hOmKBAUkVa7NMx # RSB1EdPFpNIpEn/pSHuSL+kWN/2xQBJaDFPr1AX0qLgkXmcEi6PFnaw5T17UdIIn # A58rTu3mefNuzUtse4AgYmxEmJDodf8NbVcU6VdjWtz0e58WFZT7tST6EWQmx/Oo # HPelE77lojq7lpsjhDCzhhp4kfsfszxf9g2hoCtltXhCX6NqsqwTT7xe8LgMkH4h # Vy8L1h2pqGLT2aNCx7h/F95/QvsTeGGjY7dssMzq/rSshFQKLZ8lPb8hFTmiGDJN # yHga5hZ59IGynk08mHhBFM/0MLeBzlAQq1utNjQprztZ5vv/NJy8ua9AGbwkMWkO # MIIG4DCCBMigAwIBAgIQQ7s0QZ8qUnHOP66HyvajHjANBgkqhkiG9w0BAQsFADBW # MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu # MSQwIgYDVQQDExtDZXJ0dW0gQ29kZSBTaWduaW5nIDIwMjEgQ0EwHhcNMjYwOTEy # MjEzNTQ3WhcNMjcwOTEyMjEzNTQ2WjCBhTELMAkGA1UEBhMCVVMxFzAVBgNVBAgM # DlNvdXRoIENhcm9saW5hMREwDwYDVQQHDAhOZXdiZXJyeTEeMBwGA1UECgwVT3Bl # biBTb3VyY2UgRGV2ZWxvcGVyMSowKAYDVQQDDCFPcGVuIFNvdXJjZSBEZXZlbG9w # ZXIgUm9iZXJ0IEVkZXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC6 # 72ybkWUB620cgb49nkhI4VvtWKABeSD8277f0Jww+/5fKUxoX2vC77QjmaSXU+I9 # LB2YTj8yCSTDVlxIwJoS7KgsIN9P2EC6ehJe9FRI0n2Rt33/VlRY8kuSh2XsIVGg # i6Y3RooWVjVmYCAOXuV5zrqjGY1P72nrSIekxcGCTo1Y0nmRwsECCIYn7b3ZM065 # u9b3AtYf3oI4grblWhtY0kbuHNCSeDjpp+qExQgeIs6OpFACJSDASFiDnF8+L+bB # V+UtUcFbvlu0WpQyblXw9vdN7BEIdqZ/1fxyhjHxqpSuOoTqoZcyDjilXtnMOfWp # gfgdKPFRc6NHwrmxUyNLAYHOsqBC+bMxamurB1qCJ/16lFbW/YWJRrJsaeA2WaPw # 8ulkUnZUoP9JgyVE1nwZbhZOgE3YwlVLmBBsAKsRiyJWBYqG1VdaMpfzLYJVNTc8 # 4F/90uC2BvIoafcFfNyc8dIpdd7Ni17JmYuD0+/u1gqUi3p+Qdlk/y+Vgsb1x+0z # kaMA6CzjFA83szdzwRLFDDnaUOVngyRR8+JLnSNuEw9nNM3G5WWRGBDOkLbwq+NO # H1gWWAaJJYOlTARrg7ea2JHLl66CBJaaledp2u8hBl1Y5yQTpIkVYx0VPt1Oa7Nq # kOkXlNjmmlYsZm5pe3fSTBPx5YdUOeIl1sAWXGH/+QIDAQABo4IBeDCCAXQwDAYD # VR0TAQH/BAIwADA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY2NzY2EyMDIxLmNy # bC5jZXJ0dW0ucGwvY2NzY2EyMDIxLmNybDBzBggrBgEFBQcBAQRnMGUwLAYIKwYB # BQUHMAGGIGh0dHA6Ly9jY3NjYTIwMjEub2NzcC1jZXJ0dW0uY29tMDUGCCsGAQUF # BzAChilodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwvY2NzY2EyMDIxLmNlcjAf # BgNVHSMEGDAWgBTddF1MANt7n6B0yrFu9zzAMsBwzTAdBgNVHQ4EFgQUCdHvF5Qw # xL78QY6vfJNPC89uTWEwSwYDVR0gBEQwQjAIBgZngQwBBAEwNgYLKoRoAYb2dwIF # AQQwJzAlBggrBgEFBQcCARYZaHR0cHM6Ly93d3cuY2VydHVtLnBsL0NQUzATBgNV # HSUEDDAKBggrBgEFBQcDAzAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQAD # ggIBADGvhUwApFlOiMwxr4muEgH+EK8xGwyw7qZGQZYdHQZV7E6ev+4i0u2ywMbF # H0XcYpaB4xubprYIGbltJhIXoIM+BmIB6mgzgAtKMJBIWMECnACZKPAWPJ5vp3Xu # GLCg0gwQGZEKJInwEFLzplH3G5g8hTO8KSLmWLVWoWHTTA3WI4LgTf/XRs3QYqur # bB1gWRWa+vx8J/4I6znbpnpRDxy/jCYh9qtv21Dk3BovIPnfaj50JOWJhWeongQ6 # Dgd4/FZhM/U1Fj/g1W7WDMal9q43MABwmrHPbxrIEK1V5vXwAhK1m9eSaZ8bqbeA # SId0wOYzIyEziquoO5TCdf/lSi8nD4BIm2E+h738pLQXWvr6tYWyvqaUN0uk5f27 # NsXlVRYp8EUZPP83BJMaQJFgTsYPMeZejAndk3nuqPVGeCL6WW90M7eK5sPbTAmW # WrSnYFx4pgnMR3X3s14074ytJ3o3ycKa0bxjhMcoCTfMDmV7jUUhATpW8iZ2/E4b # +0s7DmbN37VsBngsj04vMyVxhcNLSwdFDTQEgkEwHccChlw0anfoZJ7Xui4x5RSr # j3rOyyrf7mFYIpsbDYjVxBo5/JVJuu5h2+BRxY9MLoSMknm391tCI7aVC/XTl/zW # rX1kJeCx0nYBnZnmjWCRrCXWOX5V8QaLAnK/R6durGeXnlo3MIIG7TCCBNWgAwIB # AgIQCE/cM09+RU7bww+P+ZIYNTANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJV # UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRy # dXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExMB4X # DTI2MDgwNTAwMDAwMFoXDTM3MTEwNDIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAV # BgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBTSEEyNTYg # UlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIwMjYgMTCCAiIwDQYJKoZIhvcN # AQEBBQADggIPADCCAgoCggIBALZ7pvLJ/s1K+NSbTGWz/TjGMPh8CQ6RucZCLv5a # nHzWJjF/NWJrFIhy24fcpKXlgRiky4WAawDfU3YP0BMxt9l3Dm5oCG5Z69AqEN1k # gHg2epx+l+lZBcmJCcN0ASURML5uFIS80sZsDwO3BSkUxDjLJhBI+qiZP3aixAC/ # qEGLjsBNlLol9VZ7pfGEXiMlneJIC5/YKuizVzNFKZZEeoy/0B8Zm+nzKBgSWG52 # lCO1w+nCg6XpCtklTJXeIg283hw7TmmsZXR+SMbjbrEOvZ3fP2VxIgeR28Y90ZSt # d3F9VuA5RVynb/whITPAo9b75Zr4Ta6Mj3URm26QZYMn/FnbuTegcoRcFEZ9FOqM # 5T6MTdtr/n74lIT/ug0eeOzmZ6QTFg33otX+bFRsIolvykE1jive4PuESaT8zzVe # FWDAMDtozNgLctkGD1ZjkEyZtJrLl5ya0m5doH/ScpaZCZVl6pNUOCybMc/kxC6E # AmSJY24L0yYKD1Nkddsnb/ItVKi/2nXpQNMu1PT5prW83vV8d67WowuUs0HdY4H8 # AMLGvdL/WHEj3ZnqMqAQQP9u3Ai9t+5eQ02GDwy0ODjdzi0xlp70W+ow63/0++YD # EX1M0iwgUHwbrJvfpklkZQvw3+kv3vUPItdwroczk9icflf55W1zOEKAcJVAIXpc # MCU9AgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQUyWOKMC7U # SvtulPPm40B+9ezN4jAfBgNVHSMEGDAWgBTvb1NK6eQGfHrK4pBW9i/USezLTjAO # BgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsGAQUF # BwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w # XQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy # dFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNydDBf # BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNl # cnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5jcmww # IAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUA # A4ICAQCNxTphHp1SCt+ZrAmAfn0oQLFr0mLywSLaDXQIENoyKqxrFbJblzCVP/pk # XmwXOdrOpWygLzlT12os5ipDCy35RBCg2UMeApEtrfGhz45F4Wt4WGdNdIbRWt3Y # TYJmpR+b7lr4d7Uwn+H600u4D7RnOGf8Wj4UNgAdZkfHhHv1mx9EVh71SJelcEN/ # oORSjXzdjfw1iZH9d8Nh/thn6hH23d+VsPAr6GAYyzSA02nXD1nYLI7Ijmiv+xLC # iYC41DSFYL3GhTiy0PxpawPtGRyaBVGzq+UiTfM8pD7KVyF5aQyWP4KhVGUUTnmm # /RlYJoW3TiXA/+t0YcT2oRVBm3JETjajHug2AL+v5jhtKVnd3D0rbHXEu27o+Q8p # 4sEWPMqKDB+qbceb6T/6WcwTwXmQ9lOCLLYcsQeSWmvKqzpAec9etE14jOQAzLKW # dE3w/TCaKtLRaRT7LCkRYVnhA2D73FLje1O5b3HR5eHs0NzU/+xX7NbEdcofy0W3 # Wdwd1XOqtlpg/JgwtKfZM5dqO94lbUveOiJBI+xZEbGRsMNbXmMREUTgu+Oca7Y7 # 3MPWcslIx2VhkSKSXjDbD6rgg39H5Mh7QfieAIjWagkJNt68Yfim6cjEzVSiLSeZ # fdkr5dtFPTW6jATlWJdYeeDRGCyatf8R1hSjzSvdN8yWQPT9gzGCBkYwggZCAgEB # MGowVjELMAkGA1UEBhMCUEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMg # Uy5BLjEkMCIGA1UEAxMbQ2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENBAhBDuzRB # nypScc4/rofK9qMeMA0GCWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAI # oAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIB # CzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIOpnBapYboZcnSRpaojq # vxUv7VxxgjomZJ8aq+SRMvNkMA0GCSqGSIb3DQEBAQUABIICAD0ozfwjgibBpitl # kd0J8MJ2BXLk+jaY05EzRikn34U0O7vxLwk9oOr3RR1rHkW/BWPo0/5zpfOndOKp # qO8YgauNxSp1DyQ5/gecV02GePlRB5mQq4njrTCj3IM9M2ivmJWX5bUWM4CdiLec # sfh6OJLZNrOcAmV4PydUSACsBrePUnYtM2ixOITNGN6tPH+gJ7LVg1rLfLz2Wnv4 # rOb/0Y3tsYW8P53yCrGnDYmuSFQdTqgQjYfs64h2o6EMhjG7KgcbOiwZZokXmJg/ # IknL+IG48pHdAzawJwKJkYwAqFMEk/OsiEzrDtbcB94NIYiH2XxM14hpt90uH7yS # eyhDynVdexzgH8ID+mLn+5QbbSjvnFDb1XEEQu4Scp8CvSdlnp3MfBgpIxR0NJgI # HsNeQV1hTd/HMWiKhA/437ZxaXDo9r/VwvileBitxoGPsNuH/8N3E0nIWexLMV5C # JeRO5sMYgmwZRQSLPtq8jx/m/35SV7uPyg643YjLjdPseoNJln7jgyU8KETULLmS # l0Zxw/HA5W9GUYSuOUgGQG/E8Cc50wcptIJ1PGsVoh/MKyOgK+4+kvZzFMbiPEWq # WDNSC2kiDu6chzB46dBKw4RxcxO3+oUGfQFrnNn0He9z7483Wf3FHFs2nxH01qCJ # aGGLBj+E7vd/P9VXFpoU4q4k/4EXoYIDJjCCAyIGCSqGSIb3DQEJBjGCAxMwggMP # AgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEw # PwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2 # IFNIQTI1NiAyMDI1IENBMQIQCE/cM09+RU7bww+P+ZIYNTANBglghkgBZQMEAgEF # AKBpMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTI2 # MDkxMzE5MzYwN1owLwYJKoZIhvcNAQkEMSIEIOjX+ZyWwvJtMuSs4t6sNkXrod4H # 0Z9jcYnxRFkocMOAMA0GCSqGSIb3DQEBAQUABIICAAHKDsKp3WATmr0Xwlf6HLhr # CqqhTd0F+21guR+2WCMPO9jdL7PYX/SCdgegUOwucPRthMjMC3vrNFqiqYWAhXWJ # xKn3cBoEiwQMKpNbniXe+WMKB65zrwVHKQfVS2/71+9rPWH+fvtozztAVRfyLI15 # e4BSoQrn3GYJJ7I5lopiV8zspIk0eXbGiRUUnuaVjOUPEE6FEjzg0HGszG2ICPch # YJd/S8jyk9Pd/CGl0L973Mjzd2BGz962DhEH0npuD9SLTRAHYW3bcZJUS1YSXbUA # ih5nbdANYo2wnAxEyx+cfUh15C5x859MWYwZ3MXaXHvk38nVk3auqEDmNDcwPmaC # xD5kf6E4BlDyqNFgPK8vDwSaASpfBOvYVazUrXnndG3nyi5hp+0V/8vHtO9e9vX6 # 5OycTb80Pss2bbM381TiSY5gudrWkm/NlBjqfF7lEDkJI4X81mOvQDMobBEHWK/U # ohkqWHnFGIs6BR7Z6xIn21UkCTRjsG+0ccEdpzAyzoRzqcVWzKESGAwbGtgpY9sk # Xpzo5LtQp+3Vb+myRDnXZCY3KNDCpgpxH9j3YnKI538c5bnqnShzBbfd8lLQBx4z # qpL5/8dj8YdDyc83JrQirLVFtBSfoexW4IMG45VEgusf3Eo7ndGOa1fOBrzBQayM # PIOQUpEEHP7clqFkG4F6 # SIG # End signature block |