SQL Server 2016 SP1 Liberado! Muitas novidades!

Hoje é um grande dia para a comunidade SQL Server!
Não, não pelo simples fato do lançamento de um Service Pack, mas sim pelo lançamento do Service Pack de maior impacto dos últimos tempos!
O SP1 do SQL Server trouxe MUITAS novidades e vou tentar resumir o que achei mais interessante.

A primeira novidade é que VÁRIOS recursos antes apenas disponíveis na edição ENTERPRISE agora estão disponíveis nas demais edições. Isso mesmo, recursos como in-memory OLTP (Hekaton), Column Store, Database Snapshots, Compressão e Particionamento, entre outros, estão disponíveis até mesmo na edição Express.

Há inclusive vários recursos de destaque que são novos do SQL Server 2016 e que já foram disponibilizados para as demais edições, entre eles Always Encrypted, dynamic data masking e row-level security!

Obviamente estes recursos obedecem a algumas limitações da própria edição, mas isso não torna a mudança menos relevante. Por exemplo, não será possível utilizar o Chance Data Capture (CDC) no SQL Server Express Edition, pois este não possui o SQL Server Agent.

No caso de recursos como Hekaton e Column Store nas novas edições onde ele agora estará presente será permitido utilizar 25% da RAM para tais recursos, mas o ponto chave é que estes 25% não contam para o total atual. Ou seja, se você utiliza a edição Express, limitada a 1GB de RAM, poderá usar 256MB de RAM para o Hekaton e OUTROS 256MB para o Column Store, sem interferir no 1GB utilizado pela instância como um todo.

Resumindo, na prática há um ganho de 50% no total de RAM das demais edições!

Veja nessa imagem os novos recursos disponíveis agora em todas as edições:

ee_features

Essa outra imagem faz uma comparação entre as instalações RDM x SP1.

ee_features2

Uma vantagem dessa mudança, que talvez não seja percebida por todos, é que agora é possível executar o MESMO CÓDIGO em qualquer edição do produto. Isso permite que você cresça sua empresa e/ou produto que consume dados do SQL Server sem maiores impactos ou necessidades de adaptação!

Isso não quer dizer que não vale mais a pena pagar a mais para ter a edição ENTERPRISE. Muitos recursos interessantes continuam existindo nesta edição, principalmente em relação a operações ONLINE e recursos de ALTA DISPONIBILIDADE!

Uma comparação completa entre as edições pode ser vista nesse link.

Um outro recurso bem interessante é a possibilidade de CLONAR um Banco de Dados, levando todo seu schema, metadados, estatísticas, mas SEM OS DADOS. A sintaxe padrão pode ser vista neste link oficial da MS sobre o SP1.

Outra novidade que talvez seja a mais simples, mas que certamente vai gerar muita comemoração é que agora é possível usar a sintaxe de CREATE OR ALTER, para procedures, triggers, functions, views.. como já existe em outros SGBDs e a comunidade sempre reclamou do fato de não existir no SQL Server!

Há ainda outra novidade que também já era solicitada por muita gente na comunidade e que veio com este SP foi a possibilidade de utilizar HINTs para alterar o comportamento, a nível de consulta, de forma que antes só era possível através de Trace Flags!

Isso mesmo, com a opção de consulta USE HINT você pode, por exemplo, desativar o parameter sniffing em uma determinada consulta e isso SEM NECESSIDADE de permissão de SYSADMIN.

Por enquanto são apenas 9 hints, mas acredito que isto seja ampliado com o tempo.

use_hint

 

Entre as novidades, foram também adicionadas uma série de novas DMFs, melhorias em DMVs/Extended Events, novos alertas e até mesmo Trace Flags. Os detalhes podem ser vistos nesse post oficial do time do produto ou nesse outro post.

Há também algumas novidades para o SSRS 2016, mas sobre estas vou deixar este link para que possam ler mais detalhes.

Já a nível do SSAS 2016, que não é meu ponto forte, vi que alguns bugs foram corrigidos e o que parece ter causado maior alegria na comunidade foi o fato de que o SSAS Tabular agora é NUMA-Aware.

ssas.png

 

Por enquanto é isso! Novas informações serão adicionadas no blog do time do produto, que promete posts com exemplos de uso e mais detalhes sobre estas novidades.

 

Obs: NÃO há expectativa de que estes recursos/novidades sejam levados para versões ANTERIORES do SQL Server então, se deseja tirar proveito delas, é hora de migrar para o SQL Server 2016!

Obs 2: não esqueça que, por mais que hajam várias novidades interessantes, a aplicação de um Service Pack é um processo crítico que deve ser VALIDADO em um ambiente de HOMOLOGAÇÃO antes!

Publicado em Artigos, patches, Virtual PASS BR | Deixe um comentário

Vídeos das sessões do Microsoft Ignite 2015

Pessoal,

para quem não foi para o Microsoft Ignite 2015 (como eu!) e deseja ver vídeos das sessões, os mesmos já estão disponíveis neste link.

Inclusive há sessões de “exam prep”, preparatórias para certificações, como para as prova 70-461 e 70-462!

Não perca a chance de assistir tantas sessões de altíssima qualidade de forma gratuita!

Publicado em Vídeos, Virtual PASS BR | Deixe um comentário

Instalando um Cluster SQL Server em modo silencioso/unattended – Considerações e Problemas

Muita gente já conhece ou já ouviu falar da possibilidade de instalar o SQL Server em modo silencioso/unattended, mas o que alguns não sabem é que é possível inclusive fazer a instalação de uma Failover Cluster Instance (FCI) através desse modo.

Alguém pode perguntar o motivo de realizar a instalação do cluster via modo silencioso, mas imagine o tempo que se “perde” ao fazer uma instalação de várias instâncias “normais” via GUI e agora considere o fato de que em uma FCI são necessários mais passos durante a instalação. Resumindo, a economia de tempo é ainda maior (sem contar a segurança de ter o ambiente padronizado, sem riscos de erros de digitação, ou pelo menos com risco reduzido).

Bem, o processo de instalação em si é praticamente o mesmo, a diferença é que temos alguns parâmetros diferentes ou adicionais a informar.

Eu prefiro trabalhar do seguinte modo, deixo todos os parâmetros que serão iguais para todoas as instâncias no configuration file (.ini) e passo os demais parâmetros na linha de comando com os valores que mudam para cada instância. Desse modo tenho um único configuration file.

Na verdade no meu ambiente fiz 2 tipos de configuration files, um para ambientes OLTP “convencionais” e outro para ambientes de BI (onde instalo também SSAS, SSIS, SSRS e, consequentemente, possui vários parâmetros adicionais).

Na instalação de uma FCI, eu prefiro utilizar o modo advanced, realizando o “prepare” do ambiente e depois o “complete” do mesmo.

Dessa forma uma possível linha de comando (aberta como ADMINISTRADOR, cuidado com o UAC!) para o “prepare”, que deve ser executado em todos os nós, poderia ser essa (estou utilizando o exemplo de um ambiente de BI):

setup.exe /ConfigurationFile=C:\SQL\ConfigurationFile_Prepare_BI.ini /AGTSVCPASSWORD=”senha” /SQLSVCPASSWORD=”senha” /ASSVCPASSWORD=”senha” /ISSVCPASSWORD=”senha” /RSSVCPASSWORD=”senha” /INSTANCENAME=”BI” /INSTANCEID=”BI”

Os parâmetros são todos muito bem documentados e informações mais detalhadas sobre os mesmos e sobre o processo de instalação em si podem ser encontrados na documentação oficial.

No configuration file, foram fornecidos os seguintes parâmetros:

  • ACTION=”PrepareFailoverCluster”
  • ENU=”True”
  • QUIET=”True”
  • QUIETSIMPLE=”False”
  • UpdateEnabled=”True”
  • FEATURES=SQLENGINE,REPLICATION,FULLTEXT,DQ,AS,RS,RS_SHP,RS_SHPWFE,DQC,BIDS,IS,SSMS,ADV_SSMS
  • UpdateSource=”C:\SQL”
  • HELP=”False”
  • INDICATEPROGRESS=”False”
  • X86=”False”
  • INSTALLSHAREDDIR=”C:\Program Files\Microsoft SQL Server”
  • INSTALLSHAREDWOWDIR=”C:\Program Files (x86)\Microsoft SQL Server”
  • SQMREPORTING=”False”
  • RSINSTALLMODE=”FilesOnlyMode”
  • RSSHPINSTALLMODE=”SharePointFilesOnlyMode”
  • ERRORREPORTING=”False”
  • INSTANCEDIR=”C:\Program Files\Microsoft SQL Server”
  • AGTSVCACCOUNT=”domínio\conta”
  • ISSVCSTARTUPTYPE=”Automatic”
  • ISSVCACCOUNT=”domínio\conta”
  • ASSVCACCOUNT=”domínio\conta”
  • ASSVCSTARTUPTYPE=”Manual”
  • ASPROVIDERMSOLAP=”1″
  • FILESTREAMLEVEL=”0″
  • SQLSVCACCOUNT=”domínio\conta”
  • RSSVCACCOUNT=”domínio\conta”
  • RSSVCSTARTUPTYPE=”Automatic”
  • FTSVCACCOUNT=”NT Service\MSSQLFDLauncher$BI”

O único parâmetro dessa linha de comando que eu gostaria de comentar aqui é o /UpdateSource, que informa onde estão os arquivos de atualização (Hotfixes, Cumulative Updates, Service Packs, inclusive vários ao mesmo tempo) que você possa querer aplicar já durante a atualização, o que certamente é uma boa prática e que vai te economizar um bom tempo.

Já para o “Complete”, que só precisa ser executado em um dos nós, uma possível linha de comando seria:

setup.exe /ConfigurationFile=C:\SQL\ConfigurationFile_Complete_BI.ini /INSTANCENAME=”BI” /FAILOVERCLUSTERDISKS=”Nome do Disco” /FAILOVERCLUSTERGROUP=”SQL Server (BI)” /FAILOVERCLUSTERIPADDRESSES=”IPv4;00.000.000.000;DMZ_ADM;255.255.255.0″ /FAILOVERCLUSTERNETWORKNAME=”nome de rede” /INSTALLSQLDATADIR=X:\ /SQLBACKUPDIR=X:\ /SQLUSERDBDIR=X:\ /SQLUSERDBLOGDIR=”X:\mount_point\LOG” /SQLTEMPDBDIR=X:\ /SQLTEMPDBLOGDIR=”X:\mount_point\LOG” /SAPWD=”senha” /ASDATADIR=”X:\OLAP\Data” /ASLOGDIR=”X:\OLAP\Log” /ASBACKUPDIR=”X:\OLAP\Backup” /ASTEMPDIR=”X:\OLAP\Temp” /ASCONFIGDIR=”X:\OLAP\Config”

Obviamente as informações fornecidas aqui foram “mascaradas” para não passar nenhuma informação confidencial, mas vejam que no exemplo eu uso mount points, o que em um ambiente com várias instâncias ajuda bastante e que, neste artigo pelo menos, não irei detalhar mais (há bastante documentação na internet).

No configuration file de complete haviam os parâmetros abaixo:

  • ACTION=”CompleteFailoverCluster”
  • ENU=”True”
  • ; UIMODE=”Normal” (comentado, será necessário mais adiante)
  • QUIET=”False”
  • QUIETSIMPLE=”True”
  • HELP=”False”
  • INDICATEPROGRESS=”False”
  • X86=”False”
  • CONFIRMIPDEPENDENCYCHANGE=”False”
  • ASCOLLATION=”collation”
  • ASSYSADMINACCOUNTS=”domínio\conta”
  • ASSERVERMODE=”MULTIDIMENSIONAL”
  • SQLCOLLATION=”collation”
  • SQLSYSADMINACCOUNTS=”domínio\conta”
  • SECURITYMODE=”modo”

Na verdade o parâmetro QUITESIMPLE normalmente ficaria como FALSE e o QUITE como TRUE, para deixar o processo totalmente em background, mas foi uma opção minha inverter os valores para ver algumas informações durante os testes. Vou explicar mais adiante o motivo.

O processo em si da instalação é bem simples e não é comum enfrentar muitos problemas, mas você precisa prestar atenção a alguns detalhes que podem dar alguma dor de cabeça.

O primeiro deles é o formato dos paths que, se informados de maneira “não padronizada”, podem gerar erros na instalação. Uma boa documentação para tal se encontra nesse link.

Já outro problema interessante que eu enfrentei, e o mais curioso, foi o fato de que o nome dos /FAILOVERCLUSTERDISKS são CASE SENSITIVE. Eu pelo menos nunca havia visto ninguém comentar ou alguma documentação a respeito, mas passei por isso recentemente e, após receber algumas mensagens de “folder path is invalid” ou “The volume that contains SQL Server data directory X:\MSSQL11.Instância\MSSQL\DATA does not belong to the cluster group.”, mesmo seguindo as recomendações do link que citei agora a pouco, verifiquei esse fato.

No meu caso o problema foi um pouco mais complicado, pois não havia mensagem citando o disco, mas após remover e adicionar os discos da instância novamente aos recursos do cluster do windows passei a receber a mensagem mais detalhada sobre o disco.

No caso de algum problema e você necessite realizar um troubleshooting há alguns parâmetros que podem te ajudar. Os primeiros, que acabo de mostrar, são QUIET e QUIETSIMPLE. Inverter seus valores não dará muitas informações, apenas exibirá algumas telas onde não será necessária internvenção, mas já te permite ter uma noção de onde ocorre o problema.

Outra opção interessante é deixar QUIET e QUITESIMPLE como FALSE e mudar o parâmetro UIMODE para “Normal” (no meu exemplo acima, apenas tirar o ; da frente para que não fique como comentário). Esse parâmetro, aliado ao fato de que fornecemos um configuration file e uma série de parâmetros, fará com que a GUI seja exibida e você precise navegar pelas telas de instalação, mas já com os valores, já fornecidos, estejam todos preenchidos. Isso permite que você verifique se alguma tela apresenta erro nos parâmetros informados e possa então corrigí-lo no configuration file ou nos parâmetros da linha de comando. Isso por exemplo me deu uma dica sobre o problema com o nome do disco ser CASE SENSITIVE que citei anteriormente, mas na hora eu não entendi. Com o nome do disco em minúsculas nos parâmetros e em maiúsculas no cluster, ao chegar na tela do disco ele nunca vinha selecionado, mas eu pensava que era apenas alguma falha da GUI, mas não, estava demonstrando exatamente qual era o problema.

Uma última dica é verificar os registros do arquivo detail.txt na pasta de log da instalação (C:\Program Files\Microsoft SQL Server\110\Setup Bootstrap\Log\ e acessar a pasta com data e hora da instalação). Nele são registradas todas as mensagens da instalação. Caso queira, você pode utilizar o parâmetro /INDICATEPROGRESS como TRUE para que ele jogue no prompt as mensagens da instalação e acompanhar as saídas na tela durante a instalação.

Links complementares:

Publicado em Artigos, Virtual PASS BR | Deixe um comentário

Analisando problemas e Reconstruindo o ReportServerTempDB

Muitas vezes ele passa desapercebido, mas o banco de dados ReportServerTempDB possui grande importância para o funcionamento do seu ambiente de SQL Server Reporting Services (SSRS) e, ao contrário do que alguns podem pensar, este banco não é descartável e precisa, por exemplo, ser incluído em sua política de backups.

Para começar, vamos citar algumas características deste banco de dados:

  • Seus dados expiram baseado nas configurações de expiração
  • Ajuda a melhorar a performance de execução dos relatórios, já que armazena dados de CACHE que são acessados durante a execução dos relatórios
  • Armazena dados sobre sessões e execuções de relatórios, relatórios em cache
  • Reinicializações do serviço limpa seus dados temporários (aqui vale o destaque, o banco NÃO é recriado na reinicialização)

Considerando então que esse banco NÃO é recriado durante a reinicialização do serviço, precisamos considerar o que acontece se perdermos esse banco de dados, devido a uma corrupção, por exemplo.

Inicialmente, o seu ambiente SSRS não vai funcionar corretamente. Por mais que este banco seja “temporário”, ele é frequentemente utilizado durante operações básicas do SSRS e você verá muito rapidamente alguma mensagem de erro ao tentar acessar seu ambiente.

O ideal a fazer nessa situação é restaurar o banco de dados a partir de um backup, para então reiniciar o serviço, que vai limpar algum dado temporário, e então seu ambiente deve ficar novamente operacional.

O problema é, o que fazer caso eu não tenha um backup válido para uso? (Se você não inclui esse banco em sua política de backups, faça isso agora!)

Uma possibilidade seria recriar o banco com o mesmo nome, mas como já disse antes, este banco não tem seus objetos recriados durante a reinicialização, então isso não resolveria.

De qualquer forma, esse passo ainda seria útil, desde que você lembre de criar o banco com o collate correto, normalmente o ReportServerTempDB usa um collation bem incomum, no meu caso era o “Latin1_General_CI_AS_KS_WS”. Você pode se basear em sua documentação ou então no collation do banco de dados ReportServer.

Depois disso você deve recriar a ROLE “RSExecRole”. Adicione os usuários que fazem parte dessa mesma ROLE no banco de dados ReportServerTempDB, nesta role no ReportServerTempDB.

Em seguida você deve recriar os objetos do seu banco de dados (lembre-se de mudar o contexto para o banco ReportServerTempDB) utilizando o script “CatalogTempDB.sql” que fica na pasta “C:\Program Files\Microsoft SQL Server\MSRS10.SQL01\Reporting Services\ReportServer” (este caminho pode ser diferente, dependendo de onde o SSRS foi instalado).

O problema é que, mesmo após recriar este banco a partir desse script, ao acessar o seu ambiente você pode se deparar com mensagens do tipo (isso baseado no que vi em um SQL Server 2008 R2!):

“An error occurred within the report server database. This may be due to a connection failure, timeout or low disk condition within the database. (rsReportServerDatabaseError)

Invalid column name ‘EditSessionID’. Invalid column name ‘SitePath’. Invalid column name ‘SiteZone’. Invalid column name ‘DataSetInfo’. Invalid column name ‘ReportDefinitionPath’.”

Se você pesquisar, verá que essas colunas mencionadas são da tabela SessionData no ReportServerTempDB.

Pelo que eu pude entender da situação, o problema é que o script que mencionei acima recria o banco, mas na forma que ele tinha na versão RTM! Ou seja, tudo que foi alterado neste banco de dados devido as atualizações seguintes fica faltando.

E então, o que fazer?

Bem, considerando o ambiente que eu tinha quando enfrentei essa situação, tive que recriar alguns objetos e alterar outros (me baseei em outras bases que tinha, no mesmo build, e nas mensagens de erro). A ordem de criação/alteração dos objetos nesse caso foi importante, pois há constraint entre as mesmas. O meu ambiente estava no build 10.50.4266.

1) Recriar as tabelas Temp*, na sequência abaixo:

  • TempCatalog
  • TempDataSets
  • TempDataSources

2) Criar a tabela DBUpgradeHistory e importar os dados da mesma de uma base com os mesmos dados, ou seja, que esteja no mesmo build.

3) Verificar a estrutura das demais tabelas, pois devido as atualizações, algumas tiveram a estrutura alterada (colunas adicionadas, índices, etc)

Aqui uma falha minha, esqueci de anotar os objetos que foram alterados e o que foi alterado, mas de qualquer forma isso faria o procedimento válido apenas para o build 4266. Caso você enfrente esse problema, e não tenha um backup, o ideal realmente será comparar a sua base com uma já existente.

Sei que nesse ponto alguém deve estar comentando “há, já que preciso ter uma outra base no mesmo build, é mais fácil gerar o script dela, alterar apenas o nome do banco de dados, se necessário, e criar a base novamente”.

Isso é verdade, mas a ideia aqui era explicar o motivo do problema e como ele pode ser contornado detalhadamente.

Por fim, apenas mais um comentário sobre o ReportServerTempDB. Como banco de dados para dados temporários, sabiam que este banco pode sofrer de problemas de contenção similares aos do Tempdb?

A forma de análise é basicamente a mesma, contadores do perfmon e DMVs. E a solução?

Assim como no Tempdb, alocar os data files do ReportServerTempDB para um disco dedicado e/ou gerar mais datafiles, sempre lembrando de mantê-los com o mesmo tamanho e a mesma política de crescimento.

Fontes interessantes:

Publicado em Artigos, Virtual PASS BR | Deixe um comentário

Automatically configuring Min/Max Memory in a SQL Server Cluster

When we have a failover cluster environment, one of our main worries is to keep CPU and RAM levels balanced between the nodes and in a way that satisfies all the instances.

The problem is when there is a failover. Since we don’t have a way to automatically configure Min and Max RAM on the instances, we have to do it manually.

Thinking on this (and after analysing some solutions on the web) I have made this powershell script, with the main point being the possibility to give each instance a different weight, so they get RAM proportionally to their importance/needs.

This is still a first version that does not take in consideration SSRS, SSIS or SSAS, but this is something I have in mind and will be available in a new version soon!

Here is the script!

#based on the script found on the url below
#http://sqlblog.com/blogs/merrill_aldrich/archive/2010/01/22/auto-tuning-memory-configuration-on-a-cluster.aspx

#you can see a different approach on the url below
#http://blogs.msdn.com/b/sql_pfe_blog/archive/2013/02/19/use-powershell-script-via-startup-agent-job-to-balance-memory-between-two-instances-on-a-cluster-on-a-failover.aspx

function sendMail([string] $subject) {

#SMTP server name
$smtpServer = "smtp.domain.com"

#Creating a Mail object
$msg = new-object Net.Mail.MailMessage

#Creating SMTP server object
$smtp = new-object Net.Mail.SmtpClient($smtpServer)

#Email structure
$msg.From = "sqlserver@domain.com"
$msg.ReplyTo = "reply@domain.com"
$msg.To.Add("dbas@domain.com")
$msg.subject = "$Subject"
$msg.isBodyHTML = $false;
$msg.Body = "Min/Max Memory has been changed!"

#Sending email
$smtp.Send($msg)
}

function Get-LargePageInformation([string]$SQLInstance, [ref]$sum) {
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null
$svr = new-object ('Microsoft.SqlServer.Management.SMO.Server') $SQLInstance
$err = $svr.ReadErrorLog() #search inside the current log

$err | Select-String -inputobject {$_.Text} -pattern 'Using large pages for buffer pool' -context 0,0 | % { $sum += $_Matches.count};
}

function Get-SQLInstanceConfig( [string]$SQLInstance, [ref]$hostName, [ref]$maxServerMemory, [ref]$ServerMemoryMB ) {

# Function to establish a connection to a clustered SQL Server instance,
# read max server memory configuration value and current physical host name
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null

$srv = new-object Microsoft.SQLServer.Management.Smo.Server($SQLInstance)
#connect using windows authentication
$srv.ConnectionContext.LoginSecure = $true

#To connect using windows authentication using a different user uncomment the next 5 lines
# $varDBUser = "USER"
# $varDBPassword = "PASSWORD"
# $srv.ConnectionContext.ConnectAsUser = $true
# $srv.ConnectionContext.ConnectAsUserName = $varDBUser
# $srv.ConnectionContext.ConnectAsUserPassword = $varDBPassword

$maxServerMemory.Value = $srv.Configuration.MaxServerMemory.RunValue # gets the current value

$hostName.Value = $srv.ComputerNamePhysicalNetBIOS
$ServerMemoryMB.value = $srv.PhysicalMemory
}

function Set-SQLInstanceMemory( [string]$SQLInstanceName, [int]$maxMemSetting, [int]$minMemSetting) {

# Function to set min/max server memory on a given SQL instance

#write-host "Reconfiguring" [$SQLInstanceName] Maximum Memory to: $maxMemSetting
#write-host "Reconfiguring" [$SQLInstanceName] Minimum Memory to: $minMemSetting

[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null

$srv = new-object Microsoft.SQLServer.Management.Smo.Server($SQLInstanceName)
#connect using windows authentication
$srv.ConnectionContext.LoginSecure = $true

#To connect using windows authentication using a different user uncomment the next 5 lines
# $varDBUser = "USER"
# $varDBPassword = "PASSWORD"
# $srv.ConnectionContext.ConnectAsUser = $true
# $srv.ConnectionContext.ConnectAsUserName = $varDBUser
# $srv.ConnectionContext.ConnectAsUserPassword = $varDBPassword

$srv.Configuration.MaxServerMemory.ConfigValue = $maxMemSetting
$srv.Configuration.MinServerMemory.ConfigValue = $minMemSetting

$srv.Configuration.Alter() # applies the ConfigValue change

sendMail -subject ("Configurando RAM da instância: " + $SQLInstanceName + " - Máxima(MB): " + $maxMemSetting + "| Mínima(MB): " + $minMemSetting)
}

 

######################## List the instances on the cluster:

$SQLInstances = @( "VIRTUAL_SERVER1\INSTANCE","VIRTUAL_SERVER2\INSTANCE",...,"VIRTUAL_SERVERN\INSTANCE")
# Make a hashtable to sort out which SQL instance is running on which host,
# with how much memory. One table entry for each host, with each entry containing
# an empty hash table we will load with SQL instance configuration info later:
# here you may choose to only include the name of the host where the script will run, so the routine will only
# configure the memory on this node, or may include the name of all nodes, if you want memory to be reconfigured
# on all of them

$hostsTable = @{ "NODE1" = @{}; "NODE2" = @{}; "NODEN" = @{}; }

# Make a hashtable to sort out which SQL instance is from which category.
# One table entry for each host, with each entry containing an empty hash
# table we will load with SQL instance category that will determine its ammout of memory

$HostsCategories = @{ "NODE1" = @{}; "NODE2" = @{}; "NODEN" = @{}; }
# Store the hosts that have at least one instance using Large Page Allocation
$hostsUsingLP = New-Object System.Collections.ArrayList

#variables
[string] $Servidor, [string] $hostName | Out-Null
[int] $ServerMemoryMB, [int] $maxServerMemory, [int] $defaultOSMem, [int] $defaultMemPerInstance, [int] $SumOfWeights, [int] $maxMem, [int] $minMem,[int] $sum | Out-Null
$defaultOSMem = 4096 #amount of memory that will be left for the OS on the machine
$defaultMemPerInstance = 2048 #for each SQL instance this amount of memory will be added to the amount of memory to the OS

#defining category for each instance, the higher the category, more memory is configured for the instance
$InstanceCategory = @{ }
$InstanceCategory.Add("VIRTUAL_SERVER1\INSTANCE",4)
$InstanceCategory.Add("VIRUTAL_SERVER2\INSTANCE",3)
$InstanceCategory.Add("VIRTUAL_SERVERN\INSTANCE",2)

###################### Get the configs for each SQL Server instance

foreach( $SQLInstance in $SQLInstances ) {

$hostName = $null | Out-Null
$maxServerMemory = $null
$ServerMemoryMB = $null
$sum = 0

Get-SQLInstanceConfig $SQLInstance ([REF]$hostName) ([REF]$maxServerMemory) ([REF]$ServerMemoryMB)

# If we really can't see one of the instances, it's best to bail at this point,
# rather than reconfiguring memory settings without complete information

if( $hostName -eq $null ) {
throw ( "Could not connect to one of the SQL instances on the cluster." )
}

#get info about if the instance is using large page allocation or not, which would make it impossible to change its memory configurations before next startup
Get-LargePageInformation $SQLInstance ([REF] $sum)

# Put the current SQLInstance and its max memory value into the right slot
# in the hosts table, to classify the instance by host
if ($sum -eq 0) { #if the instance does not use LP allocation
$hostsTable[$hostName].Add( $SQLInstance, $maxServerMemory)

if($HostsCategories[$hostname][$InstanceCategory[$SQLInstance],1] -eq $null) {
$HostsCategories[$hostname].Add($InstanceCategory[$SQLInstance],1)
}
else {
$HostsCategories[$hostname][$InstanceCategory[$SQLInstance]] += 1
}
}
else { # save the host name to exclude it from the routine later
$hostsUsingLP.Add($hostname) | Out-Null
}

}

# Remove the hosts where there is at least one instance using Large Page Allocation, since memory can only be reallocated during next restart
if ($hostsUsingLP.count -gt 0) { # if the array is not null...
$hostsUsingLP = $hostsUsingLP | select -Unique
}

foreach( $hostEntry in $hostsUsingLP.getEnumerator() ) {
$hostsTable.Remove($hostname)
}

# For each physical cluster node, calculate a reasonable memory limit
# per SQL instance, then verify or correct the max memory value for
# each SQL Server instance running on that host
foreach( $hostEntry in $hostsTable.getEnumerator() ) {
#write-host
#write-host $hostEntry.Name
#write-host " Num SQL instances on this host:" $hostEntry.Value.count

$aggregateMemory = $ServerMemoryMB - $defaultOSMem - ($defaultMemPerInstance * $hostEntry.Value.count) +1 # +1 since it always count 1MB less, don't know why

#write-host " Max memory allowed for all SQL Server instances:" $aggregateMemory
#write-host

$SumOfWeights = $null
foreach( $SQLInstanceEntry in $hostEntry.Value.getEnumerator() ) {
$SumOfWeights += $InstanceCategory[$SQLInstanceEntry.Name]
}

foreach( $SQLInstanceEntry in $hostEntry.Value.getEnumerator() ) {
$maxMem = $aggregateMemory * 100/$SumOfWeights * $InstanceCategory[$SQLInstanceEntry.Name]/100

#using maxMen/2 as minMem value. You can choose a different approach
$minMem = ($aggregateMemory * 100/$SumOfWeights * $InstanceCategory[$SQLInstanceEntry.Name]/100)/2

#Write-Host ""
#write-host " " $SQLInstanceEntry.Name is set to $SQLInstanceEntry.Value

if ( $SQLInstanceEntry.Value -ne $maxMemSetting ) {
Set-SQLInstanceMemory $SQLInstanceEntry.Name $maxMem $minMem
}
}
}

So you should create a powershell file (.ps1) with this code, with the same name and location on all nodes and create a job with a powershell step that calls this file.

In my case I have decided to create another step, before the powershell step, that runs a WAITFOR DELAY of 1 minute (the time depends on your environment), to avoid that the change is made many times, every time one of the instances that are migrating from node goes online.

So this time for the WAITFOR should be enough time to have all the instances migrating back online. You can see details of this type of job in the second URL I provide at the end of this article.

And you, how you deal with this? Do you have a similar script for this kind of situation? Leave a comment!

Sources of information for this script:

Publicado em Artigos, Powershell | Marcado com , , | Deixe um comentário

Configurando dinamicamente Max/Min Memory de seus clusters SQL Server

Quando temos um ambiente em cluster, uma das principais preocupações é manter o ambiente devidamente balanceado, de modo que o consumo de CPU e memória RAM fique parecido entre os nós do cluster.

O problema é que no momento de um failover, apesar de podermos realizar algumas configurações para definir para onde irão as instâncias, não temos uma maneira de automaticamente configurar o consumo de memória RAM das instâncias (Min e Max Memory), de forma a tornar este consumo condizente com a relação memória do servidor x número de instâncias atualmente no nó.

Pensando nisso criei um script em powershell que visa fazer essa configuração de forma automática, levando em consideração alguns parâmetros previamente definidos, como uma “classificação” de prioridade de cada instância.

Essa é uma primeira versão, que ainda não leva em conta questões como instâncias do SSRS, SSIS ou SSAS, algo que devo incluir na próxima versão do script.

Segue abaixo o script!

#based on the script found on the url below
#http://sqlblog.com/blogs/merrill_aldrich/archive/2010/01/22/auto-tuning-memory-configuration-on-a-cluster.aspx

#you can see a different approach on the url below
#http://blogs.msdn.com/b/sql_pfe_blog/archive/2013/02/19/use-powershell-script-via-startup-agent-job-to-balance-memory-between-two-instances-on-a-cluster-on-a-failover.aspx

function sendMail([string] $subject) {

#SMTP server name
$smtpServer = "smtp.domain.com"

#Creating a Mail object
$msg = new-object Net.Mail.MailMessage

#Creating SMTP server object
$smtp = new-object Net.Mail.SmtpClient($smtpServer)

#Email structure
$msg.From = "sqlserver@domain.com"
$msg.ReplyTo = "reply@domain.com"
$msg.To.Add("dbas@domain.com")
$msg.subject = "$Subject"
$msg.isBodyHTML = $false;
$msg.Body = "Quantidades Mínima e Máxima de memória RAM foram reconfiguradas!"

#Sending email
$smtp.Send($msg)
}

function Get-LargePageInformation([string]$SQLInstance, [ref]$sum) {
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null
$svr = new-object ('Microsoft.SqlServer.Management.SMO.Server') $SQLInstance
$err = $svr.ReadErrorLog() #search inside the current log

$err | Select-String -inputobject {$_.Text} -pattern 'Using large pages for buffer pool' -context 0,0 | % { $sum += $_Matches.count};
}

function Get-SQLInstanceConfig( [string]$SQLInstance, [ref]$hostName, [ref]$maxServerMemory, [ref]$ServerMemoryMB ) {

# Function to establish a connection to a clustered SQL Server instance,
# read max server memory configuration value and current physical host name
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null

$srv = new-object Microsoft.SQLServer.Management.Smo.Server($SQLInstance)
#connect using windows authentication
$srv.ConnectionContext.LoginSecure = $true

#To connect using windows authentication using a different user uncomment the next 5 lines
# $varDBUser = "USUARIO"
# $varDBPassword = "SENHA"
# $srv.ConnectionContext.ConnectAsUser = $true
# $srv.ConnectionContext.ConnectAsUserName = $varDBUser
# $srv.ConnectionContext.ConnectAsUserPassword = $varDBPassword

$maxServerMemory.Value = $srv.Configuration.MaxServerMemory.RunValue # gets the current value

$hostName.Value = $srv.ComputerNamePhysicalNetBIOS
$ServerMemoryMB.value = $srv.PhysicalMemory
}

function Set-SQLInstanceMemory( [string]$SQLInstanceName, [int]$maxMemSetting, [int]$minMemSetting) {

# Function to set min/max server memory on a given SQL instance

#write-host "Reconfiguring" [$SQLInstanceName] Maximum Memory to: $maxMemSetting
#write-host "Reconfiguring" [$SQLInstanceName] Minimum Memory to: $minMemSetting

[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null

$srv = new-object Microsoft.SQLServer.Management.Smo.Server($SQLInstanceName)
#connect using windows authentication
$srv.ConnectionContext.LoginSecure = $true

#To connect using windows authentication using a different user uncomment the next 5 lines
# $varDBUser = "USUARIO"
# $varDBPassword = "SENHA"
# $srv.ConnectionContext.ConnectAsUser = $true
# $srv.ConnectionContext.ConnectAsUserName = $varDBUser
# $srv.ConnectionContext.ConnectAsUserPassword = $varDBPassword

$srv.Configuration.MaxServerMemory.ConfigValue = $maxMemSetting
$srv.Configuration.MinServerMemory.ConfigValue = $minMemSetting

$srv.Configuration.Alter() # applies the ConfigValue change

sendMail -subject ("Configurando RAM da instância: " + $SQLInstanceName + " - Máxima(MB): " + $maxMemSetting + "| Mínima(MB): " + $minMemSetting)
}

 

######################## List the instances on the cluster:

$SQLInstances = @( "VIRTUAL_SERVER1\INSTANCE","VIRTUAL_SERVER2\INSTANCE",...,"VIRTUAL_SERVERN\INSTANCE")
# Make a hashtable to sort out which SQL instance is running on which host,
# with how much memory. One table entry for each host, with each entry containing
# an empty hash table we will load with SQL instance configuration info later:
# here you may choose to only include the name of the host where the script will run, so the routine will only
# configure the memory on this node, or may include the name of all nodes, if you want memory to be reconfigured
# on all of them

$hostsTable = @{ "NODE1" = @{}; "NODE2" = @{}; "NODEN" = @{}; }

# Make a hashtable to sort out which SQL instance is from which category.
# One table entry for each host, with each entry containing an empty hash
# table we will load with SQL instance category that will determine its ammout of memory

$HostsCategories = @{ "NODE1" = @{}; "NODE2" = @{}; "NODEN" = @{}; }
# Store the hosts that have at least one instance using Large Page Allocation
$hostsUsingLP = New-Object System.Collections.ArrayList

#variables
[string] $Servidor, [string] $hostName | Out-Null
[int] $ServerMemoryMB, [int] $maxServerMemory, [int] $defaultOSMem, [int] $defaultMemPerInstance, [int] $SumOfWeights, [int] $maxMem, [int] $minMem,[int] $sum | Out-Null
$defaultOSMem = 4096 #amount of memory that will be left for the OS on the machine
$defaultMemPerInstance = 2048 #for each SQL instance this amount of memory will be added to the amount of memory to the OS

#defining category for each instance, the higher the category, more memory is configured for the instance
$InstanceCategory = @{ }
$InstanceCategory.Add("VIRTUAL_SERVER1\INSTANCE",4)
$InstanceCategory.Add("VIRUTAL_SERVER2\INSTANCE",3)
$InstanceCategory.Add("VIRTUAL_SERVERN\INSTANCE",2)

###################### Get the configs for each SQL Server instance

foreach( $SQLInstance in $SQLInstances ) {

$hostName = $null | Out-Null
$maxServerMemory = $null
$ServerMemoryMB = $null
$sum = 0

Get-SQLInstanceConfig $SQLInstance ([REF]$hostName) ([REF]$maxServerMemory) ([REF]$ServerMemoryMB)

# If we really can't see one of the instances, it's best to bail at this point,
# rather than reconfiguring memory settings without complete information

if( $hostName -eq $null ) {
throw ( "Could not connect to one of the SQL instances on the cluster." )
}

#get info about if the instance is using large page allocation or not, which would make it impossible to change its memory configurations before next startup
Get-LargePageInformation $SQLInstance ([REF] $sum)

# Put the current SQLInstance and its max memory value into the right slot
# in the hosts table, to classify the instance by host
if ($sum -eq 0) { #if the instance does not use LP allocation
$hostsTable[$hostName].Add( $SQLInstance, $maxServerMemory)

if($HostsCategories[$hostname][$InstanceCategory[$SQLInstance],1] -eq $null) {
$HostsCategories[$hostname].Add($InstanceCategory[$SQLInstance],1)
}
else {
$HostsCategories[$hostname][$InstanceCategory[$SQLInstance]] += 1
}
}
else { # save the host name to exclude it from the routine later
$hostsUsingLP.Add($hostname) | Out-Null
}

}

# Remove the hosts where there is at least one instance using Large Page Allocation, since memory can only be reallocated during next restart
if ($hostsUsingLP.count -gt 0) { # if the array is not null...
$hostsUsingLP = $hostsUsingLP | select -Unique
}

foreach( $hostEntry in $hostsUsingLP.getEnumerator() ) {
$hostsTable.Remove($hostname)
}

# For each physical cluster node, calculate a reasonable memory limit
# per SQL instance, then verify or correct the max memory value for
# each SQL Server instance running on that host
foreach( $hostEntry in $hostsTable.getEnumerator() ) {
#write-host
#write-host $hostEntry.Name
#write-host " Num SQL instances on this host:" $hostEntry.Value.count

$aggregateMemory = $ServerMemoryMB - $defaultOSMem - ($defaultMemPerInstance * $hostEntry.Value.count) +1 #o +1 porque sempre vem com 1MB a menos, nao sei o motivo

#write-host " Max memory allowed for all SQL Server instances:" $aggregateMemory
#write-host

$SumOfWeights = $null
foreach( $SQLInstanceEntry in $hostEntry.Value.getEnumerator() ) {
$SumOfWeights += $InstanceCategory[$SQLInstanceEntry.Name]
}

foreach( $SQLInstanceEntry in $hostEntry.Value.getEnumerator() ) {
$maxMem = $aggregateMemory * 100/$SumOfWeights * $InstanceCategory[$SQLInstanceEntry.Name]/100

#using maxMen/2 as minMem value. You can choose a different approach
$minMem = ($aggregateMemory * 100/$SumOfWeights * $InstanceCategory[$SQLInstanceEntry.Name]/100)/2

#Write-Host ""
#write-host " " $SQLInstanceEntry.Name is set to $SQLInstanceEntry.Value

if ( $SQLInstanceEntry.Value -ne $maxMemSetting ) {
Set-SQLInstanceMemory $SQLInstanceEntry.Name $maxMem $minMem
}
}
}

Você deve criar um arquivo powershell (.ps1) contendo esse código, com o mesmo nome e no mesmo caminho em cada um dos nós do seu cluster e criar um job com um step do tipo Powershell que chame esse arquivo.

No meu caso, preferi criar um um step antes que executa um WAITFOR DELAY de 1 minuto (esse tempo vai variar em cada ambiente), para evitar que, enquanto as instâncias ainda estejam subindo, o script faça a alteração na configuração de memória, o que faria que essa configuração fosse alterada várias vezes, a medida que cada instância ficasse online. O tempo do WAITFOR deve ser suficiente para que todas as instâncias fiquem online. Há uma explicação para esse procedimento no segundo link que listo abaixo.

E você, como lida com isso? Tem algum tipo de script para esse tipo de situação? Deixe seu comentário!

Fontes de informação para esse script:

Publicado em Artigos, Powershell, Virtual PASS BR | Marcado com , , | Deixe um comentário

Restoring SQL Server backups made with System Center Data Protection Manager using Powershell – New Version

Some time ago I have posted here a powershell script that I have written to automate restores from SQL Server backups made with System Center Data Protection Manager.

As the time passed I had the need for new functionalities and the script has been changed/incremented.

Here are the new features of the script:

  • #mail_subject – Message that will be used on the e-mail’s subject after the script execution has finished. – Optional, default message will be used in this case.
  • #DBnotToRestore – Database that will be ignored (will not be restored)
  • #LogFileName – Name and path for the log file. If this is not informed the default value of C:\DPM.TXT wil be used
  • #DBtoStartAT – Name of the first database to be restored. Any other database whose name is smaller than the value of this variable will not be restored.
  • #DBtoFinishAT – Name of the last database to be restored. Any other database whose name is bigger than the value of this variable will not be restored.
  • #ResetLogFile – Defines if the log file will be reseted or not, being useful mainly when we use the options DBtoStartAT and DBtoFinishAT to execute a restore in different moments, but having the same log.
  • #Inactive – Used when restoring a backup from an inactive Protection Group, no longer protected, but with its files still available. When used, makes the option sqlProtectionGroupName to be ignored

And here is the new version of the script:

–Updated on 25/04/2013, minor issue corrected in the code


#this script is based on Raj Rao's work as described at
#http://blog.aggregatedintelligence.com/2012/01/dpmrestoring-protected-sqlserver.html
#which is based on Wilson Souza's work as described at
#http://social.technet.microsoft.com/Forums/en-US/dpmsqlbackup/thread/e5e50339-5707-4e72-bb9a-56f6d60ba926
#and is compatible with SCDPM 2012

#pre-requisites

#you must run Enable-PSRemoting on Restore Server
#Account that is going to run the script/restore must be administrator on the server where the restore happens

#you must also run the commands bellow
#source for commands below
#http://social.technet.microsoft.com/Forums/en-US/dpmpowershell/thread/e34a5413-89a7-475a-833b-ec9030e2f0cb

#Run Get-WSManCredSSP on server from where .ps1 will run
 #if you receive
 #The machine is configured to allow delegating fresh credentials to the following target(s): wsman/*
 #This computer is not configured to receive credentials from a remote client computer
 #you are fine
 #if not, you should run:
 #Enable-WSManCredSSP -role client -DelegateComputer *

#Run Get-WSManCredSSP on remote server
 #if you receive
 #The machine is not configured to allow delegating fresh credentials.
 #This computer is configured to receive credentials from a remote client computer
 #you are fine
 #if not, you should run:
 #Enable-WSManCredSSP -role server
function RestoreDbFromDPM
{
 #Parâmetros de entrada
 Param(
 [string] $dpmServerName,
 [string] $sqlProtectionGroupName,
 [string] $serverName,
 [string] $databaseName,
 [string] $restoreToServerName,
 [string] $restorePathMdf,
 [string] $restorePathLog,
 [bool] $doTheRestore = $false,
 [string] $dateOfRestoreToUse = $null,
 [string] $dpmServerUsedToProtectClient = $null,
 [string] $restoreToSQLInstance,
 [string] $NewdatabaseName = $null,
 [bool] $DropOriginalDatabase = $false,
 [String] $Source = $null,
 [bool] $allDatabases = $false,
 [bool] $SystemDatabases = $false,
 [string] $idiom = "english",
 [string] $mail_subject = $null,
 [string] $DBnotToRestore,
 [string] $LogFileName = "C:\DPM.txt",
 [string] $DBtoStartAT = $null,
 [string] $DBtoFinishAT = "ZZZZZZZZZ", #doing this so when the parameter is not informed it will list ALL databases
 [bool] $ResetLogFile = $true,
 [bool] $Inactive = $false
 )

$startDate = $null;
 $endDate = $null;

 #making sure there is no variables with values that would confuse the code
 if($allDatabases -eq $true) {
 $databaseName = $null
 $NewdatabaseName = $null
 }

 if ($dateOfRestoreToUse -ne $null -and $dateOfRestoreToUse.Length -gt 0) { #if a date was provided then setup a date-range
 $startDate = Get-Date $dateOfRestoreToUse;
 $endDate = $startDate.AddDays(1).AddSeconds(-1); #one day
 }

#load DPM snapin
 if ( (Get-PSSnapin -Name 'Microsoft.DataProtectionManager.PowerShell' -ErrorAction SilentlyContinue) -eq $null )
 {
 #Add-PSSnapin -Name 'Microsoft.DataProtectionManager.PowerShell' --does not work for DPM2012
 Import-Module DataProtectionManager

if ($idiom -eq "english") {
 Write-Host "Completed loading DPM powershell snapin"
 }
 else {
 Write-Host "O snapin do powershell foi carregado com sucesso!"
 }
 }
 else
 {
 if ($idiom -eq "english") {
 Write-Host "DPM powershell snapin is already loaded"
 }
 else {
 Write-Host "O snapin do powershell já está carregado na memória!"
 }
 }

 RepointDpmAgent $dpmServerName $restoreToServerName

 Connect-DPMServer $dpmServerName; #lets connect

#create or reset the file
 if ($ResetLogFile -eq $true) {
 out-file $LogFileName -encoding ASCII

if ($idiom -eq "english") {
 "Database,Source Server,Destination Server,Backup Date/Time,Status" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 "Banco,Servidor de Origem,Servidor de Destino,Data/Hora do Backup,Status" | out-file $LogFileName -encoding ASCII -append
 }
 }
 if($Inactive -eq $false) #if restoring from an inactive Data Source, there is no Protection Group information, the PG is NULL
 {
 $sqlProtectionGroup=Get-ProtectionGroup $dpmServerName | where-object { $_.FriendlyName -eq $sqlProtectionGroupName}
 if($sqlProtectionGroup -eq $null)
 {
 if ($idiom -eq "english") {
 Write-Host "`nThe protection group $sqlProtectionGroupName was not found on the server $dpmServerName";
 "$null,$ServerName,$restoreToSQLInstance,$null,The protection group $sqlProtectionGroupName was not found on the server $dpmServerName" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 Write-Host "`nO protection group $sqlProtectionGroupName nao foi encontrado no servidor $dpmServerName";
 "$null,$ServerName,$restoreToSQLInstance,$null,O protection group $sqlProtectionGroupName nao foi encontrado no servidor $dpmServerName" | out-file $LogFileName -encoding ASCII -append
 }

return;
 }
 else
 {
 if ($idiom -eq "english") {
 Write-Host "`nThe protection group $sqlProtectionGroupName was found on the server $dpmServerName";
 }
 else {
 Write-Host "`nO protection group $sqlProtectionGroupName foi encontrado no servidor $dpmServerName";
 }
 }
 }

#get the list of the libraries
 $LIB = Get-DPMLibrary –DPMServerName $dpmServerName

#find out what is the library so we can use it on $recoveryOption for Recover-RecoverableItem
 #for now I am not considering the possibility there is more than one library, so I am using only the first one listed ($LIB[0])

if($allDatabases -eq $true)
 {

#Get the list of databases in the protection group
 if($Inactive -eq $false) {
 $databaseList = Get-DataSource -ProtectionGroup $sqlProtectionGroup | foreach {$_.name} | sort-object | get-unique
 }
 else
 {
 $databaseListTemp = Get-DataSource -DPMServerName $dpmServerName -Inactive #gets all inactive data sources
$databaseList = @()

 foreach($databaseInstanceTemp in $databaseListTemp) {
 if($databaseInstanceTemp.Instance -eq $serverName) { #filter data sources from desired instance
 $databaseList += $databaseInstanceTemp.Name
 }
 }
 }

if ($idiom -eq "english") {
 write-host "`nList of Databases:`n"
 }
 else {
 write-host "`nLista de bancos de dados:`n"
 }

-split $databaseList

foreach($databaseInstance in $databaseList) {

if($SystemDatabases -ne $true)
 {
 if($databaseInstance -ne "master" -and $databaseInstance -ne "model" -and $databaseInstance -ne "msdb" -and $databaseInstance -ne "distribution" -and $databaseInstance -ne "tempdb")
 {
 if (($databaseInstance -ge $DBtoStartAT) -and ($databaseInstance -le $DBtoFinishAT)) {
 if ($DBnotToRestore -ne $databaseInstance) {
 if ($idiom -eq "english") {
 write-host "`nBeginning the process of restoring the database $databaseInstance`n"
 }
 else {
 write-host "`nIniciando o processo de restauração do banco de dados $databaseInstance`n"
 }

RestoreSingleDatabase -databaseName $databaseInstance -restoreToServerName $restoreToServerName -restorePathMdf $restorePathMdf -restorePathLog $restorePathLog -doTheRestore $doTheRestore -dateOfRestoreToUse $dateOfRestoreToUse -restoreToSQLInstance $restoreToSQLInstance -NewdatabaseName $Newdatabasename -DropOriginalDatabase $DropOriginalDatabase -Source $Source -Inactive $Inactive
 }
 else {
 if ($idiom -eq "english") {
 write-host "`nSkipping database $databaseInstance since it was informed on the '-DBnotToRestore' variable`n"
 "$databaseInstance,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 write-host "`nIgnorando o banco de dados $databaseInstance já que este foi informado na variável '-DBnotToRestore'`n"
 "$databaseInstance,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 }
 }
 else {
 if ($idiom -eq "english") {
 write-host "`nIgnoring database $databaseInstance since its name is not in the range between '-DBToStartAT' and '-DBtoFinishAT'`n"
 }
 else {
 write-host "`nIgnorando o banco de dados $databaseInstance já que seu nome não está entre os valores de '-DBToStartAT' e '-DBtoFinishAT'`n"
 }
 }
 }
 else
 {
 if ($DBnotToRestore -ne $databaseInstance) {
 if ($idiom -eq "english") {
 write-host "`nIgnoring database $databaseInstance since '-systemDatabases' is set to FALSE`n"
 }
 else {
 write-host "`nIgnorando banco de dados $databaseInstance já que a opção '-systemDatabases' está configurada como FALSE`n"
 }
 }
 else {
 if ($idiom -eq "english") {
 write-host "`nSkipping database $databaseInstance since it was informed on the '-DBnotToRestore' variable`n"
 "$databaseInstance,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 write-host "`nIgnorando o banco de dados $databaseInstance já que este foi informado na variável '-DBnotToRestore'`n"
 "$databaseInstance,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 }
 }
 }
 else
 {
 if($databaseInstance -eq "master" -or $databaseInstance -eq "model" -or $databaseInstance -eq "msdb" -or $databaseInstance -eq "distribution" -or $databaseInstance -eq "tempdb") {
 $NewdatabaseName = $databaseInstance + "_temp"
 }
 else {
 $NewdatabaseName = $databaseInstance
 }

 if (($databaseInstance -ge $DBtoStartAT) -and ($databaseInstance -le $DBtoFinishAT)) {
 if ($DBnotToRestore -ne $databaseInstance) {
 if ($idiom -eq "english") {
 write-host "`nBeginning the process of restoring the database $databaseInstance as $NewdatabaseName`n"
 }
 else {
 write-host "`nIniciando o processo de restauração do banco de dados $databaseInstance como $NewdatabaseName`n"
 }

RestoreSingleDatabase -databaseName $databaseInstance -restoreToServerName $restoreToServerName -restorePathMdf $restorePathMdf -restorePathLog $restorePathLog -doTheRestore $doTheRestore -dateOfRestoreToUse $dateOfRestoreToUse -restoreToSQLInstance $restoreToSQLInstance -NewdatabaseName $NewdatabaseName -DropOriginalDatabase $DropOriginalDatabase -Source $Source -Inactive $Inactive

 }
 else {
 if ($idiom -eq "english") {
 write-host "`nSkipping database $databaseInstance since it was informed on the '-DBnotToRestore' variable`n"
 "$databaseInstance,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 write-host "`nIgnorando o banco de dados $databaseInstance já que este foi informado na variável '-DBnotToRestore'`n"
 "$databaseInstance,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 }
 }
 else {
 if ($idiom -eq "english") {
 write-host "`nIgnoring database $databaseInstance since its name is not in the range between '-DBToStartAT' and '-DBtoFinishAT'`n"
 }
 else {
 write-host "`nIgnorando o banco de dados $databaseInstance já que seu nome não está entre os valores de '-DBToStartAT' e '-DBtoFinishAT'`n"
 }

#reset variable that was used to set the suffix to the database name, so it may use this name to the next database
 $NewdatabaseName = $null;
 }
 }
 }
 }
 else {
 if ($DBnotToRestore -ne $databaseName) {
 if ($idiom -eq "english") {
 write-host "`nBeginning the process of restoring the database $databaseName`n"
 }
 else {
 write-host "`nIniciando processo de restauração do banco de dados $databaseName`n"
 }

RestoreSingleDatabase -databaseName $databaseName -restoreToServerName $restoreToServerName -restorePathMdf $restorePathMdf -restorePathLog $restorePathLog -doTheRestore $doTheRestore -dateOfRestoreToUse $dateOfRestoreToUse -restoreToSQLInstance $restoreToSQLInstance -NewdatabaseName $NewdatabaseName -DropOriginalDatabase $DropOriginalDatabase -Source $Source -Inactive $Inactive

 }
 else {
 if ($idiom -eq "english") {
 write-host "`nSkipping database $databaseName since it was informed on the '-DBnotToRestore' variable`n"
 "$databaseName,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 write-host "`nIgnorando o banco de dados $databaseName já que este foi informado na variável '-DBnotToRestore'`n"
 "$databaseName,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 }
 }

if (($idiom -eq "english") -and ($mail_subject -eq $null)){
 Sendmail -subject "Restore of databases on server $Servername" -idiom $idiom
 }
 else {
 if (($idiom -ne "english") -and ($mail_subject -eq $null)){
 Sendmail -subject "Restore de bancos de dados no servidor $Servername" -idiom $idiom
 }
 else {
 Sendmail -subject $mail_subject -idiom $idiom
 }
 }

if ($idiom -eq "english") {
 write-host "Disconnecting from server $dpmServerName"
 }
 else {
 write-host "Desconectando do servidor $dpmServerName"
 }
 Disconnect-DPMServer $dpmServerName

 if ($dpmServerUsedToProtectClient.Length -gt 0) { #we have been provided the dpmserver for protection - so repoint
 RepointDpmAgent $dpmServerUsedToProtectClient $restoreToServerName
 }
}
function RestoreSingleDatabase
{
 Param(
 [string] $databaseName,
 [string] $restoreToServerName,
 [string] $restorePathMdf,
 [string] $restorePathLog,
 [bool] $doTheRestore = $false,
 [string] $dateOfRestoreToUse = $null,
 [string] $restoreToSQLInstance,
 [string] $NewdatabaseName = $null,
 [bool] $DropOriginalDatabase = $false,
 [String] $Source = $null,
 [bool] $Inactive = $false
 )

#find the data-source for the database-name on the server requested
 if($Inactive -eq $false) {
 $sqlDataSource = Get-DataSource -ProtectionGroup $sqlProtectionGroup | where-object { $_.name -eq $databaseName -and $_.Instance -eq $serverName}
 }
 else
 {
 $sqlDataSourceTemp = Get-DataSource -DPMServerName $dpmServerName -Inactive

foreach($databaseInstanceTemp in $sqlDataSourceTemp) {
 if($databaseInstanceTemp.Instance -eq $serverName -and $databaseInstanceTemp.Name -eq $databaseName) {
 $sqlDataSource = $databaseInstanceTemp
 }
 }
 }

if ($sqlDataSource -ne $null)
 {
 if ($idiom -eq "english") {
 Write-Host "`nThe Data Source: $sqlDataSource was found!`n"
 }
 else {
 Write-Host "`nO Data Source: $sqlDataSource foi encontrado!`n"
 }

$sqlDs = [Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.SQLDataSource]$sqlDataSource;
 #find a recoverypoint that is not incremental - not sure why incremental restores bomb! (full express backups)
 #allowed searching for backups in media and disk since
 $recoveryPoints = Get-Recoverypoint -DataSource $sqlDs | where-object { $_.HasFastRecoveryMarker -eq "Fast" -and $_.IsRecoverable -and $Source -match $_.datalocation} | sort-object $_.datalocation #list disk backups first

if ($recoveryPoints -eq $null) {
 if ($idiom -eq "english") {
 Write-Host "A recovery point for the database: $databaseName on server: $serverName was not found in the protection group $($sqlProtectionGroup.FriendlyName)`n" -ForegroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,A recovery point was not found in the protection group $($sqlProtectionGroup.FriendlyName)" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 Write-Host "nao foi encontrado um recovery point para o banco de dados: $databaseName no servidor: $serverName no protection group $($sqlProtectionGroup.FriendlyName)`n" -ForegroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,nao foi encontrado um recovery point no protection group $($sqlProtectionGroup.FriendlyName)" | out-file $LogFileName -encoding ASCII -append
 }

return
 }
 else {
 if ($idiom -eq "english") {
 Write-Host "A recovery point for the database: $databaseName on server: $serverName was found in the protection group $($sqlProtectionGroup.FriendlyName)`n"
 }
 else {
 Write-Host "Foi encontrado um recovery point para o banco de dados: $databaseName no servidor: $serverName no protection group $($sqlProtectionGroup.FriendlyName)`n"
 }
 }

if ($startDate -ne $null) #range has been provided - lets find a recovery point within the date range
 {
 $recoveryPoints = $recoveryPoints | Where-Object {$_.RepresentedPointInTime -ge $startDate -and $_.RepresentedPointInTime -lt $endDate
 };

 if ($recoveryPoints -ne $null) {
 if ($idiom -eq "english") {
 Write-Host "A recovery point for the specified date: $startDate was found. Recovery Point date: $($recoveryPoints.RepresentedPointInTime)"
 }
 else {
 Write-Host "Um recovery point para a data especificada: $startDate foi encontrado. Data do Recovery Point: $($recoveryPoints.RepresentedPointInTime)"
 }
 }
 else {
 if ($idiom -eq "english") {
 Write-Host "A recovery point for the database: $databaseName on server: $serverName was not found in the protection group $sqlProtectionGroupName within the daterange: $startDate to $endDate. Restore cannot proceed!" -ForegroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,A recovery point was not found in the protection group $sqlProtectionGroupName within the daterange: $startDate to $endDate." | out-file $LogFileName -encoding ASCII -append
 }
 else {

[string] $Data_Inicio = $StartDate.ToString("dd/MM/yyyy hh:mm:ss")
 [string] $Data_Fim = $EndDate.ToString("dd/MM/yyyy hh:mm:ss")

Write-Host "Um recovery point para o banco de dados: $databaseName no servidor: $serverName nao foi encontrado no protection group $sqlProtectionGroupName dentro do periodo: $Data_Inicio ate $Data_Fim. O Restore nao pode ser realizado!" -ForegroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,Um recovery point nao foi encontrado no protection group $sqlProtectionGroupName dentro do periodo: $Data_Inicio ate $Data_Fim." | out-file $LogFileName -encoding ASCII -append
 }

return
 }
 }
 if ($recoveryPoints.Count) { #check if we got back an array
 $recoveryPointToUse = $recoveryPoints[-1]; #array - select the latest
 }
 else {
 $recoveryPointToUse = $recoveryPoints;
 }

 if ($recoveryPointToUse -eq $null) {
 if ($idiom -eq "english") {
 Write-Host "A recovery point for the database: $databaseName on server: $serverName was not found in the protection group" $($sqlProtectionGroup.FriendlyName) -ForegroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,A recovery point was not found in the protection group $($sqlProtectionGroup.FriendlyName)" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 Write-Host "nao foi encontrado um recovery point para o banco de dados $databaseName no servidor: $serverName no protection group $($sqlProtectionGroup.FriendlyName)" -ForegroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,nao foi encontrado um recovery point no protection group $($sqlProtectionGroup.FriendlyName)" | out-file $LogFileName -encoding ASCII -append
 }

return
 }
 else {
 if ($idiom -eq "english") {
 write-host "`nIt was possible to select a recovery point for the database: $databaseName on server: $serverName in the protection group $($sqlProtectionGroup.FriendlyName)"
 }
 else {
 write-host "`nFoi encontrado um recovery point para o banco de dados: $databaseName no servidor: $serverName no protection group $($sqlProtectionGroup.FriendlyName)"
 }
 }

 $length = $recoveryPointToUse.PhysicalPath.Length; #Length = num files (eg: mdf and log = 2)

if ($idiom -eq "english") {
 Write-Host "`nThe number of files to recover on the database: $databaseName is $Length"
 }
 else {
 Write-Host "`nO número de arquivos a recuperar do banco de dados: $databaseName é $Length"
 }

#lets setup the alt.database details.
 $alternateDatabaseDetails = New-Object -TypeName Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.AlternateDatabaseDetailsType;
 $LocationMapping = New-Object Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.FileLocationMapping[] $length;
 $alternateDatabaseDetails.LocationMapping = $LocationMapping

 $i = 0;
 $a = $null;

while($i -lt $length)
 {
 $alternateDatabaseDetails.LocationMapping[$i] = New-Object -TypeName Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.FileLocationMapping;
 $alternateDatabaseDetails.LocationMapping[$i].FileName = $recoveryPointToUse.FileSpecifications[$i].FileSpecification;
 $alternateDatabaseDetails.LocationMapping[$i].SourceLocation = [Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.OMCommon.PathHelper]::GetParentDirectory($recoveryPointToUse.PhysicalPath[$i]);
 if ($alternateDatabaseDetails.LocationMapping[$i].FileName.ToLower().EndsWith(".ldf")) {
 $alternateDatabaseDetails.LocationMapping[$i].DestinationLocation = $restorePathLog
 }
 else {
 $alternateDatabaseDetails.LocationMapping[$i].DestinationLocation = $restorePathMdf
 }
 $i++;
 }
 $alternateDatabaseDetails.InstanceName = $restoreToSQLInstance;

#verify if the database should be restored with a different name
 if ($NewdatabaseName -ne $null -and $NewdatabaseName -ne "") {
 $alternateDatabaseDetails.DatabaseName = $NewdatabaseName

if($NewdatabaseName -ne "master" -and $NewdatabaseName -ne "model" -and $NewdatabaseName -ne "msdb" -and $NewdatabaseName -ne "distribution" -and $NewdatabaseName -ne "tempdb") {
 #drop the database if it already exists - else DPM fails on the restore
 DropDatabase $restoreToSQLInstance $NewdatabaseName;
 }
 else {
 if ($idiom -eq "english") {
 Write-Host "`nCan't drop system database $NewdatabaseName! Database won't be restored!"
 }
 else {
 Write-Host "`nImpossível remover banco de dados de sistema $NewdatabaseName! Banco de dados nao será restaurado!"
 }

return;
 }

if ($DropOriginalDatabase -eq $true ) { #-and $databaseName -ne $NewdatabaseName)

if($databaseName -ne "master" -and $databaseName -ne "model" -and $databaseName -ne "msdb" -and $databaseName -ne "distribution" -and $databaseName -ne "tempdb") {
 #drop the database if it already exists - else DPM fails on the restore
 DropDatabase $restoreToSQLInstance $databaseName;
 }
 else
 {
 if ($idiom -eq "english") {
 Write-Host "`nCan't drop original database $databaseName since it is a system database! Proceeding to restore!"
 }
 else {
 Write-Host "`nImpossível remover banco de dados original $databaseName já que ele é um banco de dados de sistema! Continuando com o restore!"
 }
 }
 }
 }
 else {
 $alternateDatabaseDetails.DatabaseName = $databaseName

if($databaseName -ne "master" -and $databaseName -ne "model" -and $databaseName -ne "msdb" -and $databaseName -ne "distribution" -and $databaseName -ne "tempdb") {
 #drop the database if it already exists - else DPM fails on the restore
 DropDatabase $restoreToSQLInstance $databaseName;
 }
 else
 {
 if ($idiom -eq "english") {
 #Write-Host "`nCan't drop original database $databaseName since it is a system database! Database won't be restored!"
 Write-Host "`nAn attempt to restore the system database $databaseName will be done, but it may fail if the database already exists!"
 }
 else {
 #Write-Host "`nImpossível remover banco de dados original $databaseName já que ele é um banco de dados de sistema! Banco de dados nao será restaurado!"
 Write-Host "`nUma tentativa de restore do banco de dados de sistema $databasename será feita, mas ela pode falhar caso o banco de dados já exista!"
 }
 }
 }

#made changes to the end of this command, adding information about the library
 #$recoveryOption = New-RecoveryOption -TargetServer $restoreToServerName -RecoveryLocation OriginalServerWithDBRename -SQL -RecoveryType Recover -AlternateDatabaseDetails $alternateDatabaseDetails -DPMLibrary $LIB[0]
 $recoveryOption = New-DPMRecoveryOption -TargetServer $restoreToServerName -RecoveryLocation OriginalServerWithDBRename -SQL -RecoveryType Recover -AlternateDatabaseDetails $alternateDatabaseDetails -DPMLibrary $LIB[0]

$dbsize = ($recoveryPointToUse.Size / (1gb)).ToString(".##");

if ($idiom -eq "english") {
 Write-Host "restoring database: $($alternateDatabaseDetails.DatabaseName)`n"`
 " with backup from $($recoveryPointToUse.RepresentedPointInTime)`n to SQL Server: $($alternateDatabaseDetails.InstanceName)`n DB size: $dbsize GB .....`n"
 }
 else {
 Write-Host "Restaurando o banco de dados: $($alternateDatabaseDetails.DatabaseName)`n"`
 " com o backup de $($recoveryPointToUse.RepresentedPointInTime)`n para o SQL Server: $($alternateDatabaseDetails.InstanceName)`n Tamanho do BD: $dbsize GB .....`n"
 }

 if ($doTheRestore) {
 $restoreJob = Recover-RecoverableItem -RecoverableItem $recoveryPointToUse -RecoveryOption $recoveryOption;

if ($idiom -eq "english") {
 Write-Host "Restore Status: $($restoreJob.Status)`n HasCompleted: $($restoreJob.HasCompleted)`n Start: $($restoreJob.StartTime)"
 }
 else {
 Write-Host "Status do Restore: $($restoreJob.Status)`n Concluído: $($restoreJob.HasCompleted)`n Início: $($restoreJob.StartTime)"
 }

$waitTime = 3; #initial wait time
 while ($restoreJob -ne $null -and $restoreJob.HasCompleted -eq $false)
 {
 Write-Host "." -NoNewline;
 Start-Sleep -Seconds $waitTime;
 $waitTime = 3;
 }

 Write-Host ""

 if($restoreJob.Status -ne "Succeeded")
 {
 if ($idiom -eq "english") {
 Write-Host "Restore Status: $($restoreJob.Status)`n Start: $($restoreJob.StartTime)`n End: $($restoreJob.EndTime)" -ForeGroundColor Red
 }
 else {
 Write-Host "Status do Restore: $($restoreJob.Status)`n Início: $($restoreJob.StartTime)`n Fim: $($restoreJob.EndTime)" -ForeGroundColor Red
 }
 }
 else
 {
 if ($idiom -eq "english") {
 Write-Host "Restore Status: $($restoreJob.Status)`n Start: $($restoreJob.StartTime)`n End: $($restoreJob.EndTime)" -ForeGroundColor DarkGreen
 }
 else {
 Write-Host "Status do Restore: $($restoreJob.Status)`n início: $($restoreJob.StartTime)`n Fim: $($restoreJob.EndTime)" -ForeGroundColor DarkGreen
 }
 }
 if ($idiom -eq "english") {
 "$databaseName,$serverName,$restoreToSQLInstance,$($recoveryPointToUse.RepresentedPointInTime),$($restoreJob.Status)" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 "$databaseName,$serverName,$restoreToSQLInstance,$($recoveryPointToUse.RepresentedPointInTime.ToString("dd/MM/yyyy hh:mm:ss")),$($restoreJob.Status)" | out-file $LogFileName -encoding ASCII -append
 }

 $td = (New-Timespan -Start $restoreJob.StartTime -end $restoreJob.EndTime)

if ($idiom -eq "english") {
 Write-Host "Elapsed time: Hours: $($td.Hours) Minutes:$($td.Minutes) Seconds:$($td.Seconds) MSecs:$($td.Milliseconds)"
 }
 else {
 Write-Host "Duração do Restore: Horas: $($td.Hours) Minutos:$($td.Minutes) Segundos:$($td.Seconds) MSecs:$($td.Milliseconds)"
 }

}
 else {
 if ($idiom -eq "english") {
 Write-Host "DoTheRestore is set to false - restore is not being performed!" -BackgroundColor Red
 }
 else {
 Write-Host "DoTheRestore está configurado como false - restore nao está sendo executado!" -BackgroundColor Red
 }
 }
 }
 else {
 if ($idiom -eq "english") {
 Write-Host "Database $databaseName on $serverName was not found" -ForeGroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,Database $databaseName on $serverName was not found" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 Write-Host "O Banco de dados $databaseName nao foi encontrado no servidor $serverName" -ForeGroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,O Banco de dados $databaseName nao foi encontrado no servidor $serverName" | out-file $LogFileName -encoding ASCII -append
 }
 }
}
function DropDatabase([string] $restoreToServerName, [string] $databaseName)
{

if ($idiom -eq "english") {
 Write-Host "`nChecking if database $databaseName needs to be dropped"
 }
 else {
 Write-Host "`nVerificando se o banco de dados $databaseName precisa ser removido"
 }

[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | out-null
 $sqlServerSmo = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server ($restoreToServerName)
 if ($sqlServerSmo.databases[$databaseName] -ne $null) {

if ($idiom -eq "english") {
 Write-Host "Dropping database $databaseName on server $restoreToServerName"
 }
 else {
 Write-Host "Removendo o banco de dados $databaseName no servidor $restoreToServerName"
 }

$sqlServerSmo.KillAllProcesses($databaseName)
 $sqlServerSmo.databases[$databaseName].drop()

if ($idiom -eq "english") {
 Write-Host "Database $databaseName on server $restoreToServerName has been dropped`n"
 }
 else {
 Write-Host "O Banco de dados $databaseName foi removido do servidor $restoreToServerName`n"
 }
 }
 else {
 if ($idiom -eq "english") {
 Write-Host "Database $databaseName does not exist on server $restoreToServerName`n"
 }
 else {
 Write-Host "Banco de dados $databaseName nao existe no servidor $restoreToServerName`n"
 }
 }
}

function RepointDpmAgent([string] $dpmServerName, [string] $dpmClient)
{
 #add this to avoid manual entry of password
 $Username = 'domain\user'
 $Password = 'Password'
 $pass = ConvertTo-SecureString -AsPlainText $Password -Force
 $Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass

if ($idiom -eq "english") {
 Write-Host "Setting the DPMServer for $dpmClient to $dpmServerName"
 }
 else {
 Write-Host "Configurando o servidor $dpmServerName como DPMServer para $dpmClient"
 }

Invoke-Command -ComputerName $dpmClient -ArgumentList $dpmServerName -authentication credssp -Credential $Cred -ScriptBlock {param($serverName) $cmd = "C:\Program Files\Microsoft Data Protection Manager\DPM\bin\SetDpmServer.exe"; & $cmd -dpmServerName $serverName;}
}
function sendMail([string] $subject, [string] $idiom)
{

if ($idiom -eq "english") {
 Write-Host "Sending Email"
 }
 else {
 Write-Host "Enviando Email"
 }

$a = "<style>"
 $a = $a + "BODY{background-color:white;}"
 $a = $a + "TABLE{border-width: 2px;border-style: solid;border-color: black;border-collapse: collapse;}"
 $a = $a + "TH{border-width: 2px;padding: 5px;border-style: solid;border-color: black;background-color:gray}"
 $a = $a + "TD{border-width: 2px;padding: 5px;border-style: solid;border-color: black;}"
 $a = $a + "</style>"

#SMTP server name
 $smtpServer = "smtp.domain.com"

#Creating a Mail object
 $msg = new-object Net.Mail.MailMessage

#Creating SMTP server object
 $smtp = new-object Net.Mail.SmtpClient($smtpServer)

#Email structure
 $msg.From = "dpm@domain.com"
 $msg.ReplyTo = "your_email@domain.com"
 $msg.To.Add("your_email@domain.com")
 $msg.subject = "$Subject"
 $msg.isBodyHTML = $true;
 $msg.Body = (Import-csv $LogFileName | convertTo-HTML -head $a)

#Sending email
 $smtp.Send($msg)
}

####Explicando os parâmetros (PT-BR)

#dpmServerName - nome do servidor DPM que está gerenciando os backups
#sqlProtectionGroupName - Nome do protection group que possui os backups
#serverName - nome do servidor\instância original SQL Server, onde os backups foram executados
#databaseName - nome do banco de dados que será restaurado
#restoreToServerName - nome do servidor que hospeda a instância SQL Server onde os backups serao restaurados
#restorePathMdf - caminho dos arquivos MDF/NDF no servidor destino
#restorePathLog - caminho dos arquivos LDF no servidor destino
#doTheRestore = - flag que define se o restore será realmente executado ou se será executada apenas uma simulação. É opcional e por padrão é falso
#dateOfRestoreToUse - data do backup que desejamos restaurar - É opcional e por padrão será utilizado o backup mais recente
#dpmServerUsedToProtectClient - servidor DPM utilizado para proteger o cliente - É opcional e por padrão será utilizado o mesmo servidor DPM dos backups
#restoreToSQLInstance - instância SQL Server onde serão restaurados os backups
#NewdatabaseName - Novo nome do banco de dados que será utilizado no restore - É opcional, por padrão é utilizado o nome original do banco de dados
#DropOriginalDatabase - flag que define se o banco de dados original será removido ou nao - É opcional e por padrão FALSE
#Source - define se a rotina deve procurar por backups em disco (Disk), fita (Media) ou ambos
#allDatabases - flag que define se devem ser restaurados TODOS os bancos de dados do protection group ou nao - É opcional e por padrão FALSE
#SystemDatabases - flag que define se os bancos de dados de sistema, caso hajam no protection group, devem ser restaurados ou nao - É opcional e por padrão FALSE
#idiom - idioma no qual serão exibidas as mensagens (english ou null (português) ) - É opcional, por padrão exibe as mensagens em inglês
#mail_subject - Mensagem que será utilizada no assunto do e-mail enviado ao final da execução do script. - É opcional, caso não seja definida será utilizada a mensagem padrão.
#DBnotToRestore - Banco de dados que será ignorado (não será restaurado)
#LogFileName - Nome e caminho do arquivo de log que será gerado. Caso não seja informado será utilizado o nome padrão C:\DPM.TXT
#DBtoStartAT - Nome do primeiro banco de dados a ser restaurado. Qualquer outro banco de dados cujo nome for menor que o informado nessa variável não será restaurado.
#DBtoFinishAT - Nome do último banco de dados a ser restaurado. Qualquer outro banco de dados cujo nome for maior que o informado nessa variável não será restaurado.
#ResetLogFile - Define se o arquivo de log deverá ser "resetado" ou não, sendo útil principalmente quando utilizamos as opções DBtoStartAT e DBtoFinishAT para realizar uma operação de Restore em momentos diferentes, mas que precisam do mesmo log.
#Inactive - Informa que os backups a serem restaurados estao em um Protection Group inativo, sem proteção, mas cujos arquivos cotinuam disponíveis. Caso ativado, o parametro sqlProtectionGroupName sera ignorado

####Explaining parameters (EN-US)

#dpmServerName - name of the DPM Server that is managing the backups
#sqlProtectionGroupName - Name of the protection group where the backup is at
#serverName - Original SQL Server server\instance where backups were executed
#databaseName - name of the database that is going to be restored
#restoreToServerName - name of the server that hosts the SQL Server instance where backups will be restored
#restorePathMdf - Path where MDF/NDF files will be restored at on destination server
#restorePathLog - Path where LDF files will be restored at on destination server
#doTheRestore = - flag that defines if restore is going to be executed or if it is going to simulate only. Optional, false is default
#dateOfRestoreToUse - date of the backup that we wish to restore - Optional, default is to restore the most recent backup
#dpmServerUsedToProtectClient - DPM server used to protect the client server where backups will be restored - Optional, default is to use the same server that protects the server
#restoreToSQLInstance - SQL Server instance where backups are going to be restored to
#NewdatabaseName - New database name that will be used on restore - Optional, default is to use original database name
#DropOriginalDatabase - flag that defines if original database will be removed or not - Optional, default is FALSE
#Source - Defines if the script should look for Disk or Media (Tape) backups or both
#allDatabases - flag that defines if ALL databases should be restored - Optional, default is FALSE
#SystemDatabases - flag that defines if system databases, in case they exist in protection group, should be restored - Optional, default is FALSE
#idiom - Idiom on which messages will be desplayed (english or null (brazilian portuguese) ) - Optional, default is english
#mail_subject - Message that will be used on the e-mail's subject after the script execution has finished. - Optional, default message will be used in this case.
#DBnotToRestore - Database that will be ignored (will not be restored)
#LogFileName - Name and path for the log file. If this is not informed the default value of C:\DPM.TXT wil be used
#DBtoStartAT - Name of the first database to be restored. Any other database whose name is smaller than the value of this variable will not be restored.
#DBtoFinishAT - Name of the last database to be restored. Any other database whose name is bigger than the value of this variable will not be restored.
#ResetLogFile - Defines if the log file will be reseted or not, being useful mainly when we use the options DBtoStartAT and DBtoFinishAT to execute a restore in different moments, but having the same log.
#Inactive - Used when restoring a backup from an inactive Protection Group, no longer protected, but with its files still available. When used, makes the option sqlProtectionGroupName to be ignored

####calling the function

#cls;
Publicado em Artigos, Powershell | 1 Comentário

Restaurando backups do SQL Server no System Center Data Protection Manager automaticamente com Powershell – Nova versão

Faz algum tempo postei aqui no blog um script que elaborei para automatizar restores de backups do SQL Server feitos com o System Center Data Protection Manager de forma automática utilizando Powershell.

A medida que o tempo foi passando fui sentindo necessidade de novas funcionalidades e o script foi sendo melhorado/incrementado.

Abaixo explico as novas funcionalidades do script:

  • #mail_subject – Mensagem que será utilizada no assunto do e-mail enviado ao final da execução do script. – É opcional, caso não seja definida será utilizada a mensagem padrão.
  • #DBnotToRestore – Banco de dados que será ignorado (não será restaurado)
  • #LogFileName – Nome e caminho do arquivo de log que será gerado. Caso não seja informado será utilizado o nome padrão C:\DPM.TXT
  • #DBtoStartAT – Nome do primeiro banco de dados a ser restaurado. Qualquer outro banco de dados cujo nome for menor que o informado nessa variável não será restaurado.
  • #DBtoFinishAT – Nome do último banco de dados a ser restaurado. Qualquer outro banco de dados cujo nome for maior que o informado nessa variável não será restaurado.
  • #ResetLogFile – Define se o arquivo de log deverá ser “resetado” ou não, sendo útil principalmente quando utilizamos as opções DBtoStartAT e DBtoFinishAT para realizar uma operação de Restore em momentos diferentes, mas que precisam do mesmo log.
  • #Inactive – Informa que os backups a serem restaurados estao em um Protection Group inativo, sem proteção, mas cujos arquivos cotinuam disponíveis. Caso ativado, o parametro sqlProtectionGroupName sera ignorado

E aqui está a nova versão do script:

–Pequena atualização no script feita no dia 25/04/2013


#this script is based on Raj Rao's work as described at
#http://blog.aggregatedintelligence.com/2012/01/dpmrestoring-protected-sqlserver.html
#which is based on Wilson Souza's work as described at
#http://social.technet.microsoft.com/Forums/en-US/dpmsqlbackup/thread/e5e50339-5707-4e72-bb9a-56f6d60ba926
#and is compatible with SCDPM 2012

#pre-requisites

#you must run Enable-PSRemoting on Restore Server
#Account that is going to run the script/restore must be administrator on the server where the restore happens

#you must also run the commands bellow
#source for commands below
#http://social.technet.microsoft.com/Forums/en-US/dpmpowershell/thread/e34a5413-89a7-475a-833b-ec9030e2f0cb

#Run Get-WSManCredSSP on server from where .ps1 will run
 #if you receive
 #The machine is configured to allow delegating fresh credentials to the following target(s): wsman/*
 #This computer is not configured to receive credentials from a remote client computer
 #you are fine
 #if not, you should run:
 #Enable-WSManCredSSP -role client -DelegateComputer *

#Run Get-WSManCredSSP on remote server
 #if you receive
 #The machine is not configured to allow delegating fresh credentials.
 #This computer is configured to receive credentials from a remote client computer
 #you are fine
 #if not, you should run:
 #Enable-WSManCredSSP -role server
function RestoreDbFromDPM
{
 #Parâmetros de entrada
 Param(
 [string] $dpmServerName,
 [string] $sqlProtectionGroupName,
 [string] $serverName,
 [string] $databaseName,
 [string] $restoreToServerName,
 [string] $restorePathMdf,
 [string] $restorePathLog,
 [bool] $doTheRestore = $false,
 [string] $dateOfRestoreToUse = $null,
 [string] $dpmServerUsedToProtectClient = $null,
 [string] $restoreToSQLInstance,
 [string] $NewdatabaseName = $null,
 [bool] $DropOriginalDatabase = $false,
 [String] $Source = $null,
 [bool] $allDatabases = $false,
 [bool] $SystemDatabases = $false,
 [string] $idiom = "english",
 [string] $mail_subject = $null,
 [string] $DBnotToRestore,
 [string] $LogFileName = "C:\DPM.txt",
 [string] $DBtoStartAT = $null,
 [string] $DBtoFinishAT = "ZZZZZZZZZ", #doing this so when the parameter is not informed it will list ALL databases
 [bool] $ResetLogFile = $true,
 [bool] $Inactive = $false
 )

$startDate = $null;
 $endDate = $null;

 #making sure there is no variables with values that would confuse the code
 if($allDatabases -eq $true) {
 $databaseName = $null
 $NewdatabaseName = $null
 }

 if ($dateOfRestoreToUse -ne $null -and $dateOfRestoreToUse.Length -gt 0) { #if a date was provided then setup a date-range
 $startDate = Get-Date $dateOfRestoreToUse;
 $endDate = $startDate.AddDays(1).AddSeconds(-1); #one day
 }

#load DPM snapin
 if ( (Get-PSSnapin -Name 'Microsoft.DataProtectionManager.PowerShell' -ErrorAction SilentlyContinue) -eq $null )
 {
 #Add-PSSnapin -Name 'Microsoft.DataProtectionManager.PowerShell' --does not work for DPM2012
 Import-Module DataProtectionManager

if ($idiom -eq "english") {
 Write-Host "Completed loading DPM powershell snapin"
 }
 else {
 Write-Host "O snapin do powershell foi carregado com sucesso!"
 }
 }
 else
 {
 if ($idiom -eq "english") {
 Write-Host "DPM powershell snapin is already loaded"
 }
 else {
 Write-Host "O snapin do powershell já está carregado na memória!"
 }
 }

 RepointDpmAgent $dpmServerName $restoreToServerName

 Connect-DPMServer $dpmServerName; #lets connect

#create or reset the file
 if ($ResetLogFile -eq $true) {
 out-file $LogFileName -encoding ASCII

if ($idiom -eq "english") {
 "Database,Source Server,Destination Server,Backup Date/Time,Status" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 "Banco,Servidor de Origem,Servidor de Destino,Data/Hora do Backup,Status" | out-file $LogFileName -encoding ASCII -append
 }
 }
 if($Inactive -eq $false) #if restoring from an inactive Data Source, there is no Protection Group information, the PG is NULL
 {
 $sqlProtectionGroup=Get-ProtectionGroup $dpmServerName | where-object { $_.FriendlyName -eq $sqlProtectionGroupName}
 if($sqlProtectionGroup -eq $null)
 {
 if ($idiom -eq "english") {
 Write-Host "`nThe protection group $sqlProtectionGroupName was not found on the server $dpmServerName";
 "$null,$ServerName,$restoreToSQLInstance,$null,The protection group $sqlProtectionGroupName was not found on the server $dpmServerName" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 Write-Host "`nO protection group $sqlProtectionGroupName nao foi encontrado no servidor $dpmServerName";
 "$null,$ServerName,$restoreToSQLInstance,$null,O protection group $sqlProtectionGroupName nao foi encontrado no servidor $dpmServerName" | out-file $LogFileName -encoding ASCII -append
 }

return;
 }
 else
 {
 if ($idiom -eq "english") {
 Write-Host "`nThe protection group $sqlProtectionGroupName was found on the server $dpmServerName";
 }
 else {
 Write-Host "`nO protection group $sqlProtectionGroupName foi encontrado no servidor $dpmServerName";
 }
 }
 }

#get the list of the libraries
 $LIB = Get-DPMLibrary –DPMServerName $dpmServerName

#find out what is the library so we can use it on $recoveryOption for Recover-RecoverableItem
 #for now I am not considering the possibility there is more than one library, so I am using only the first one listed ($LIB[0])

if($allDatabases -eq $true)
 {

#Get the list of databases in the protection group
 if($Inactive -eq $false) {
 $databaseList = Get-DataSource -ProtectionGroup $sqlProtectionGroup | foreach {$_.name} | sort-object | get-unique
 }
 else
 {
 $databaseListTemp = Get-DataSource -DPMServerName $dpmServerName -Inactive #gets all inactive data sources
$databaseList = @()

 foreach($databaseInstanceTemp in $databaseListTemp) {
 if($databaseInstanceTemp.Instance -eq $serverName) { #filter data sources from desired instance
 $databaseList += $databaseInstanceTemp.Name
 }
 }
 }

if ($idiom -eq "english") {
 write-host "`nList of Databases:`n"
 }
 else {
 write-host "`nLista de bancos de dados:`n"
 }

-split $databaseList

foreach($databaseInstance in $databaseList) {

if($SystemDatabases -ne $true)
 {
 if($databaseInstance -ne "master" -and $databaseInstance -ne "model" -and $databaseInstance -ne "msdb" -and $databaseInstance -ne "distribution" -and $databaseInstance -ne "tempdb")
 {
 if (($databaseInstance -ge $DBtoStartAT) -and ($databaseInstance -le $DBtoFinishAT)) {
 if ($DBnotToRestore -ne $databaseInstance) {
 if ($idiom -eq "english") {
 write-host "`nBeginning the process of restoring the database $databaseInstance`n"
 }
 else {
 write-host "`nIniciando o processo de restauração do banco de dados $databaseInstance`n"
 }

RestoreSingleDatabase -databaseName $databaseInstance -restoreToServerName $restoreToServerName -restorePathMdf $restorePathMdf -restorePathLog $restorePathLog -doTheRestore $doTheRestore -dateOfRestoreToUse $dateOfRestoreToUse -restoreToSQLInstance $restoreToSQLInstance -NewdatabaseName $Newdatabasename -DropOriginalDatabase $DropOriginalDatabase -Source $Source -Inactive $Inactive
 }
 else {
 if ($idiom -eq "english") {
 write-host "`nSkipping database $databaseInstance since it was informed on the '-DBnotToRestore' variable`n"
 "$databaseInstance,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 write-host "`nIgnorando o banco de dados $databaseInstance já que este foi informado na variável '-DBnotToRestore'`n"
 "$databaseInstance,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 }
 }
 else {
 if ($idiom -eq "english") {
 write-host "`nIgnoring database $databaseInstance since its name is not in the range between '-DBToStartAT' and '-DBtoFinishAT'`n"
 }
 else {
 write-host "`nIgnorando o banco de dados $databaseInstance já que seu nome não está entre os valores de '-DBToStartAT' e '-DBtoFinishAT'`n"
 }
 }
 }
 else
 {
 if ($DBnotToRestore -ne $databaseInstance) {
 if ($idiom -eq "english") {
 write-host "`nIgnoring database $databaseInstance since '-systemDatabases' is set to FALSE`n"
 }
 else {
 write-host "`nIgnorando banco de dados $databaseInstance já que a opção '-systemDatabases' está configurada como FALSE`n"
 }
 }
 else {
 if ($idiom -eq "english") {
 write-host "`nSkipping database $databaseInstance since it was informed on the '-DBnotToRestore' variable`n"
 "$databaseInstance,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 write-host "`nIgnorando o banco de dados $databaseInstance já que este foi informado na variável '-DBnotToRestore'`n"
 "$databaseInstance,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 }
 }
 }
 else
 {
 if($databaseInstance -eq "master" -or $databaseInstance -eq "model" -or $databaseInstance -eq "msdb" -or $databaseInstance -eq "distribution" -or $databaseInstance -eq "tempdb") {
 $NewdatabaseName = $databaseInstance + "_temp"
 }
 else {
 $NewdatabaseName = $databaseInstance
 }

 if (($databaseInstance -ge $DBtoStartAT) -and ($databaseInstance -le $DBtoFinishAT)) {
 if ($DBnotToRestore -ne $databaseInstance) {
 if ($idiom -eq "english") {
 write-host "`nBeginning the process of restoring the database $databaseInstance as $NewdatabaseName`n"
 }
 else {
 write-host "`nIniciando o processo de restauração do banco de dados $databaseInstance como $NewdatabaseName`n"
 }

RestoreSingleDatabase -databaseName $databaseInstance -restoreToServerName $restoreToServerName -restorePathMdf $restorePathMdf -restorePathLog $restorePathLog -doTheRestore $doTheRestore -dateOfRestoreToUse $dateOfRestoreToUse -restoreToSQLInstance $restoreToSQLInstance -NewdatabaseName $NewdatabaseName -DropOriginalDatabase $DropOriginalDatabase -Source $Source -Inactive $Inactive

 }
 else {
 if ($idiom -eq "english") {
 write-host "`nSkipping database $databaseInstance since it was informed on the '-DBnotToRestore' variable`n"
 "$databaseInstance,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 write-host "`nIgnorando o banco de dados $databaseInstance já que este foi informado na variável '-DBnotToRestore'`n"
 "$databaseInstance,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 }
 }
 else {
 if ($idiom -eq "english") {
 write-host "`nIgnoring database $databaseInstance since its name is not in the range between '-DBToStartAT' and '-DBtoFinishAT'`n"
 }
 else {
 write-host "`nIgnorando o banco de dados $databaseInstance já que seu nome não está entre os valores de '-DBToStartAT' e '-DBtoFinishAT'`n"
 }

#reset variable that was used to set the suffix to the database name, so it may use this name to the next database
 $NewdatabaseName = $null;
 }
 }
 }
 }
 else {
 if ($DBnotToRestore -ne $databaseName) {
 if ($idiom -eq "english") {
 write-host "`nBeginning the process of restoring the database $databaseName`n"
 }
 else {
 write-host "`nIniciando processo de restauração do banco de dados $databaseName`n"
 }

RestoreSingleDatabase -databaseName $databaseName -restoreToServerName $restoreToServerName -restorePathMdf $restorePathMdf -restorePathLog $restorePathLog -doTheRestore $doTheRestore -dateOfRestoreToUse $dateOfRestoreToUse -restoreToSQLInstance $restoreToSQLInstance -NewdatabaseName $NewdatabaseName -DropOriginalDatabase $DropOriginalDatabase -Source $Source -Inactive $Inactive

 }
 else {
 if ($idiom -eq "english") {
 write-host "`nSkipping database $databaseName since it was informed on the '-DBnotToRestore' variable`n"
 "$databaseName,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 write-host "`nIgnorando o banco de dados $databaseName já que este foi informado na variável '-DBnotToRestore'`n"
 "$databaseName,$serverName,$restoreToSQLInstance,$null,Ignored" | out-file $LogFileName -encoding ASCII -append
 }
 }
 }

if (($idiom -eq "english") -and ($mail_subject -eq $null)){
 Sendmail -subject "Restore of databases on server $Servername" -idiom $idiom
 }
 else {
 if (($idiom -ne "english") -and ($mail_subject -eq $null)){
 Sendmail -subject "Restore de bancos de dados no servidor $Servername" -idiom $idiom
 }
 else {
 Sendmail -subject $mail_subject -idiom $idiom
 }
 }

if ($idiom -eq "english") {
 write-host "Disconnecting from server $dpmServerName"
 }
 else {
 write-host "Desconectando do servidor $dpmServerName"
 }
 Disconnect-DPMServer $dpmServerName

 if ($dpmServerUsedToProtectClient.Length -gt 0) { #we have been provided the dpmserver for protection - so repoint
 RepointDpmAgent $dpmServerUsedToProtectClient $restoreToServerName
 }
}
function RestoreSingleDatabase
{
 Param(
 [string] $databaseName,
 [string] $restoreToServerName,
 [string] $restorePathMdf,
 [string] $restorePathLog,
 [bool] $doTheRestore = $false,
 [string] $dateOfRestoreToUse = $null,
 [string] $restoreToSQLInstance,
 [string] $NewdatabaseName = $null,
 [bool] $DropOriginalDatabase = $false,
 [String] $Source = $null,
 [bool] $Inactive = $false
 )

#find the data-source for the database-name on the server requested
 if($Inactive -eq $false) {
 $sqlDataSource = Get-DataSource -ProtectionGroup $sqlProtectionGroup | where-object { $_.name -eq $databaseName -and $_.Instance -eq $serverName}
 }
 else
 {
 $sqlDataSourceTemp = Get-DataSource -DPMServerName $dpmServerName -Inactive

foreach($databaseInstanceTemp in $sqlDataSourceTemp) {
 if($databaseInstanceTemp.Instance -eq $serverName -and $databaseInstanceTemp.Name -eq $databaseName) {
 $sqlDataSource = $databaseInstanceTemp
 }
 }
 }

if ($sqlDataSource -ne $null)
 {
 if ($idiom -eq "english") {
 Write-Host "`nThe Data Source: $sqlDataSource was found!`n"
 }
 else {
 Write-Host "`nO Data Source: $sqlDataSource foi encontrado!`n"
 }

$sqlDs = [Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.SQLDataSource]$sqlDataSource;
 #find a recoverypoint that is not incremental - not sure why incremental restores bomb! (full express backups)
 #allowed searching for backups in media and disk since
 $recoveryPoints = Get-Recoverypoint -DataSource $sqlDs | where-object { $_.HasFastRecoveryMarker -eq "Fast" -and $_.IsRecoverable -and $Source -match $_.datalocation} | sort-object $_.datalocation #list disk backups first

if ($recoveryPoints -eq $null) {
 if ($idiom -eq "english") {
 Write-Host "A recovery point for the database: $databaseName on server: $serverName was not found in the protection group $($sqlProtectionGroup.FriendlyName)`n" -ForegroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,A recovery point was not found in the protection group $($sqlProtectionGroup.FriendlyName)" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 Write-Host "nao foi encontrado um recovery point para o banco de dados: $databaseName no servidor: $serverName no protection group $($sqlProtectionGroup.FriendlyName)`n" -ForegroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,nao foi encontrado um recovery point no protection group $($sqlProtectionGroup.FriendlyName)" | out-file $LogFileName -encoding ASCII -append
 }

return
 }
 else {
 if ($idiom -eq "english") {
 Write-Host "A recovery point for the database: $databaseName on server: $serverName was found in the protection group $($sqlProtectionGroup.FriendlyName)`n"
 }
 else {
 Write-Host "Foi encontrado um recovery point para o banco de dados: $databaseName no servidor: $serverName no protection group $($sqlProtectionGroup.FriendlyName)`n"
 }
 }

if ($startDate -ne $null) #range has been provided - lets find a recovery point within the date range
 {
 $recoveryPoints = $recoveryPoints | Where-Object {$_.RepresentedPointInTime -ge $startDate -and $_.RepresentedPointInTime -lt $endDate
 };

 if ($recoveryPoints -ne $null) {
 if ($idiom -eq "english") {
 Write-Host "A recovery point for the specified date: $startDate was found. Recovery Point date: $($recoveryPoints.RepresentedPointInTime)"
 }
 else {
 Write-Host "Um recovery point para a data especificada: $startDate foi encontrado. Data do Recovery Point: $($recoveryPoints.RepresentedPointInTime)"
 }
 }
 else {
 if ($idiom -eq "english") {
 Write-Host "A recovery point for the database: $databaseName on server: $serverName was not found in the protection group $sqlProtectionGroupName within the daterange: $startDate to $endDate. Restore cannot proceed!" -ForegroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,A recovery point was not found in the protection group $sqlProtectionGroupName within the daterange: $startDate to $endDate." | out-file $LogFileName -encoding ASCII -append
 }
 else {

[string] $Data_Inicio = $StartDate.ToString("dd/MM/yyyy hh:mm:ss")
 [string] $Data_Fim = $EndDate.ToString("dd/MM/yyyy hh:mm:ss")

Write-Host "Um recovery point para o banco de dados: $databaseName no servidor: $serverName nao foi encontrado no protection group $sqlProtectionGroupName dentro do periodo: $Data_Inicio ate $Data_Fim. O Restore nao pode ser realizado!" -ForegroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,Um recovery point nao foi encontrado no protection group $sqlProtectionGroupName dentro do periodo: $Data_Inicio ate $Data_Fim." | out-file $LogFileName -encoding ASCII -append
 }

return
 }
 }
 if ($recoveryPoints.Count) { #check if we got back an array
 $recoveryPointToUse = $recoveryPoints[-1]; #array - select the latest
 }
 else {
 $recoveryPointToUse = $recoveryPoints;
 }

 if ($recoveryPointToUse -eq $null) {
 if ($idiom -eq "english") {
 Write-Host "A recovery point for the database: $databaseName on server: $serverName was not found in the protection group" $($sqlProtectionGroup.FriendlyName) -ForegroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,A recovery point was not found in the protection group $($sqlProtectionGroup.FriendlyName)" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 Write-Host "nao foi encontrado um recovery point para o banco de dados $databaseName no servidor: $serverName no protection group $($sqlProtectionGroup.FriendlyName)" -ForegroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,nao foi encontrado um recovery point no protection group $($sqlProtectionGroup.FriendlyName)" | out-file $LogFileName -encoding ASCII -append
 }

return
 }
 else {
 if ($idiom -eq "english") {
 write-host "`nIt was possible to select a recovery point for the database: $databaseName on server: $serverName in the protection group $($sqlProtectionGroup.FriendlyName)"
 }
 else {
 write-host "`nFoi encontrado um recovery point para o banco de dados: $databaseName no servidor: $serverName no protection group $($sqlProtectionGroup.FriendlyName)"
 }
 }

 $length = $recoveryPointToUse.PhysicalPath.Length; #Length = num files (eg: mdf and log = 2)

if ($idiom -eq "english") {
 Write-Host "`nThe number of files to recover on the database: $databaseName is $Length"
 }
 else {
 Write-Host "`nO número de arquivos a recuperar do banco de dados: $databaseName é $Length"
 }

#lets setup the alt.database details.
 $alternateDatabaseDetails = New-Object -TypeName Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.AlternateDatabaseDetailsType;
 $LocationMapping = New-Object Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.FileLocationMapping[] $length;
 $alternateDatabaseDetails.LocationMapping = $LocationMapping

 $i = 0;
 $a = $null;

while($i -lt $length)
 {
 $alternateDatabaseDetails.LocationMapping[$i] = New-Object -TypeName Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.FileLocationMapping;
 $alternateDatabaseDetails.LocationMapping[$i].FileName = $recoveryPointToUse.FileSpecifications[$i].FileSpecification;
 $alternateDatabaseDetails.LocationMapping[$i].SourceLocation = [Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.OMCommon.PathHelper]::GetParentDirectory($recoveryPointToUse.PhysicalPath[$i]);
 if ($alternateDatabaseDetails.LocationMapping[$i].FileName.ToLower().EndsWith(".ldf")) {
 $alternateDatabaseDetails.LocationMapping[$i].DestinationLocation = $restorePathLog
 }
 else {
 $alternateDatabaseDetails.LocationMapping[$i].DestinationLocation = $restorePathMdf
 }
 $i++;
 }
 $alternateDatabaseDetails.InstanceName = $restoreToSQLInstance;

#verify if the database should be restored with a different name
 if ($NewdatabaseName -ne $null -and $NewdatabaseName -ne "") {
 $alternateDatabaseDetails.DatabaseName = $NewdatabaseName

if($NewdatabaseName -ne "master" -and $NewdatabaseName -ne "model" -and $NewdatabaseName -ne "msdb" -and $NewdatabaseName -ne "distribution" -and $NewdatabaseName -ne "tempdb") {
 #drop the database if it already exists - else DPM fails on the restore
 DropDatabase $restoreToSQLInstance $NewdatabaseName;
 }
 else {
 if ($idiom -eq "english") {
 Write-Host "`nCan't drop system database $NewdatabaseName! Database won't be restored!"
 }
 else {
 Write-Host "`nImpossível remover banco de dados de sistema $NewdatabaseName! Banco de dados nao será restaurado!"
 }

return;
 }

if ($DropOriginalDatabase -eq $true ) { #-and $databaseName -ne $NewdatabaseName)

if($databaseName -ne "master" -and $databaseName -ne "model" -and $databaseName -ne "msdb" -and $databaseName -ne "distribution" -and $databaseName -ne "tempdb") {
 #drop the database if it already exists - else DPM fails on the restore
 DropDatabase $restoreToSQLInstance $databaseName;
 }
 else
 {
 if ($idiom -eq "english") {
 Write-Host "`nCan't drop original database $databaseName since it is a system database! Proceeding to restore!"
 }
 else {
 Write-Host "`nImpossível remover banco de dados original $databaseName já que ele é um banco de dados de sistema! Continuando com o restore!"
 }
 }
 }
 }
 else {
 $alternateDatabaseDetails.DatabaseName = $databaseName

if($databaseName -ne "master" -and $databaseName -ne "model" -and $databaseName -ne "msdb" -and $databaseName -ne "distribution" -and $databaseName -ne "tempdb") {
 #drop the database if it already exists - else DPM fails on the restore
 DropDatabase $restoreToSQLInstance $databaseName;
 }
 else
 {
 if ($idiom -eq "english") {
 #Write-Host "`nCan't drop original database $databaseName since it is a system database! Database won't be restored!"
 Write-Host "`nAn attempt to restore the system database $databaseName will be done, but it may fail if the database already exists!"
 }
 else {
 #Write-Host "`nImpossível remover banco de dados original $databaseName já que ele é um banco de dados de sistema! Banco de dados nao será restaurado!"
 Write-Host "`nUma tentativa de restore do banco de dados de sistema $databasename será feita, mas ela pode falhar caso o banco de dados já exista!"
 }
 }
 }

#made changes to the end of this command, adding information about the library
 #$recoveryOption = New-RecoveryOption -TargetServer $restoreToServerName -RecoveryLocation OriginalServerWithDBRename -SQL -RecoveryType Recover -AlternateDatabaseDetails $alternateDatabaseDetails -DPMLibrary $LIB[0]
 $recoveryOption = New-DPMRecoveryOption -TargetServer $restoreToServerName -RecoveryLocation OriginalServerWithDBRename -SQL -RecoveryType Recover -AlternateDatabaseDetails $alternateDatabaseDetails -DPMLibrary $LIB[0]

$dbsize = ($recoveryPointToUse.Size / (1gb)).ToString(".##");

if ($idiom -eq "english") {
 Write-Host "restoring database: $($alternateDatabaseDetails.DatabaseName)`n"`
 " with backup from $($recoveryPointToUse.RepresentedPointInTime)`n to SQL Server: $($alternateDatabaseDetails.InstanceName)`n DB size: $dbsize GB .....`n"
 }
 else {
 Write-Host "Restaurando o banco de dados: $($alternateDatabaseDetails.DatabaseName)`n"`
 " com o backup de $($recoveryPointToUse.RepresentedPointInTime)`n para o SQL Server: $($alternateDatabaseDetails.InstanceName)`n Tamanho do BD: $dbsize GB .....`n"
 }

 if ($doTheRestore) {
 $restoreJob = Recover-RecoverableItem -RecoverableItem $recoveryPointToUse -RecoveryOption $recoveryOption;

if ($idiom -eq "english") {
 Write-Host "Restore Status: $($restoreJob.Status)`n HasCompleted: $($restoreJob.HasCompleted)`n Start: $($restoreJob.StartTime)"
 }
 else {
 Write-Host "Status do Restore: $($restoreJob.Status)`n Concluído: $($restoreJob.HasCompleted)`n Início: $($restoreJob.StartTime)"
 }

$waitTime = 3; #initial wait time
 while ($restoreJob -ne $null -and $restoreJob.HasCompleted -eq $false)
 {
 Write-Host "." -NoNewline;
 Start-Sleep -Seconds $waitTime;
 $waitTime = 3;
 }

 Write-Host ""

 if($restoreJob.Status -ne "Succeeded")
 {
 if ($idiom -eq "english") {
 Write-Host "Restore Status: $($restoreJob.Status)`n Start: $($restoreJob.StartTime)`n End: $($restoreJob.EndTime)" -ForeGroundColor Red
 }
 else {
 Write-Host "Status do Restore: $($restoreJob.Status)`n Início: $($restoreJob.StartTime)`n Fim: $($restoreJob.EndTime)" -ForeGroundColor Red
 }
 }
 else
 {
 if ($idiom -eq "english") {
 Write-Host "Restore Status: $($restoreJob.Status)`n Start: $($restoreJob.StartTime)`n End: $($restoreJob.EndTime)" -ForeGroundColor DarkGreen
 }
 else {
 Write-Host "Status do Restore: $($restoreJob.Status)`n início: $($restoreJob.StartTime)`n Fim: $($restoreJob.EndTime)" -ForeGroundColor DarkGreen
 }
 }
 if ($idiom -eq "english") {
 "$databaseName,$serverName,$restoreToSQLInstance,$($recoveryPointToUse.RepresentedPointInTime),$($restoreJob.Status)" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 "$databaseName,$serverName,$restoreToSQLInstance,$($recoveryPointToUse.RepresentedPointInTime.ToString("dd/MM/yyyy hh:mm:ss")),$($restoreJob.Status)" | out-file $LogFileName -encoding ASCII -append
 }

 $td = (New-Timespan -Start $restoreJob.StartTime -end $restoreJob.EndTime)

if ($idiom -eq "english") {
 Write-Host "Elapsed time: Hours: $($td.Hours) Minutes:$($td.Minutes) Seconds:$($td.Seconds) MSecs:$($td.Milliseconds)"
 }
 else {
 Write-Host "Duração do Restore: Horas: $($td.Hours) Minutos:$($td.Minutes) Segundos:$($td.Seconds) MSecs:$($td.Milliseconds)"
 }

}
 else {
 if ($idiom -eq "english") {
 Write-Host "DoTheRestore is set to false - restore is not being performed!" -BackgroundColor Red
 }
 else {
 Write-Host "DoTheRestore está configurado como false - restore nao está sendo executado!" -BackgroundColor Red
 }
 }
 }
 else {
 if ($idiom -eq "english") {
 Write-Host "Database $databaseName on $serverName was not found" -ForeGroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,Database $databaseName on $serverName was not found" | out-file $LogFileName -encoding ASCII -append
 }
 else {
 Write-Host "O Banco de dados $databaseName nao foi encontrado no servidor $serverName" -ForeGroundColor Red
 "$databaseName,$serverName,$restoreToSQLInstance,$null,O Banco de dados $databaseName nao foi encontrado no servidor $serverName" | out-file $LogFileName -encoding ASCII -append
 }
 }
}
function DropDatabase([string] $restoreToServerName, [string] $databaseName)
{

if ($idiom -eq "english") {
 Write-Host "`nChecking if database $databaseName needs to be dropped"
 }
 else {
 Write-Host "`nVerificando se o banco de dados $databaseName precisa ser removido"
 }

[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | out-null
 $sqlServerSmo = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server ($restoreToServerName)
 if ($sqlServerSmo.databases[$databaseName] -ne $null) {

if ($idiom -eq "english") {
 Write-Host "Dropping database $databaseName on server $restoreToServerName"
 }
 else {
 Write-Host "Removendo o banco de dados $databaseName no servidor $restoreToServerName"
 }

$sqlServerSmo.KillAllProcesses($databaseName)
 $sqlServerSmo.databases[$databaseName].drop()

if ($idiom -eq "english") {
 Write-Host "Database $databaseName on server $restoreToServerName has been dropped`n"
 }
 else {
 Write-Host "O Banco de dados $databaseName foi removido do servidor $restoreToServerName`n"
 }
 }
 else {
 if ($idiom -eq "english") {
 Write-Host "Database $databaseName does not exist on server $restoreToServerName`n"
 }
 else {
 Write-Host "Banco de dados $databaseName nao existe no servidor $restoreToServerName`n"
 }
 }
}

function RepointDpmAgent([string] $dpmServerName, [string] $dpmClient)
{
 #add this to avoid manual entry of password
 $Username = 'domain\user'
 $Password = 'Password'
 $pass = ConvertTo-SecureString -AsPlainText $Password -Force
 $Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass

if ($idiom -eq "english") {
 Write-Host "Setting the DPMServer for $dpmClient to $dpmServerName"
 }
 else {
 Write-Host "Configurando o servidor $dpmServerName como DPMServer para $dpmClient"
 }

Invoke-Command -ComputerName $dpmClient -ArgumentList $dpmServerName -authentication credssp -Credential $Cred -ScriptBlock {param($serverName) $cmd = "C:\Program Files\Microsoft Data Protection Manager\DPM\bin\SetDpmServer.exe"; & $cmd -dpmServerName $serverName;}
}
function sendMail([string] $subject, [string] $idiom)
{

if ($idiom -eq "english") {
 Write-Host "Sending Email"
 }
 else {
 Write-Host "Enviando Email"
 }

$a = "<style>"
 $a = $a + "BODY{background-color:white;}"
 $a = $a + "TABLE{border-width: 2px;border-style: solid;border-color: black;border-collapse: collapse;}"
 $a = $a + "TH{border-width: 2px;padding: 5px;border-style: solid;border-color: black;background-color:gray}"
 $a = $a + "TD{border-width: 2px;padding: 5px;border-style: solid;border-color: black;}"
 $a = $a + "</style>"

#SMTP server name
 $smtpServer = "smtp.domain.com"

#Creating a Mail object
 $msg = new-object Net.Mail.MailMessage

#Creating SMTP server object
 $smtp = new-object Net.Mail.SmtpClient($smtpServer)

#Email structure
 $msg.From = "dpm@domain.com"
 $msg.ReplyTo = "your_email@domain.com"
 $msg.To.Add("your_email@domain.com")
 $msg.subject = "$Subject"
 $msg.isBodyHTML = $true;
 $msg.Body = (Import-csv $LogFileName | convertTo-HTML -head $a)

#Sending email
 $smtp.Send($msg)
}

####Explicando os parâmetros (PT-BR)

#dpmServerName - nome do servidor DPM que está gerenciando os backups
#sqlProtectionGroupName - Nome do protection group que possui os backups
#serverName - nome do servidor\instância original SQL Server, onde os backups foram executados
#databaseName - nome do banco de dados que será restaurado
#restoreToServerName - nome do servidor que hospeda a instância SQL Server onde os backups serao restaurados
#restorePathMdf - caminho dos arquivos MDF/NDF no servidor destino
#restorePathLog - caminho dos arquivos LDF no servidor destino
#doTheRestore = - flag que define se o restore será realmente executado ou se será executada apenas uma simulação. É opcional e por padrão é falso
#dateOfRestoreToUse - data do backup que desejamos restaurar - É opcional e por padrão será utilizado o backup mais recente
#dpmServerUsedToProtectClient - servidor DPM utilizado para proteger o cliente - É opcional e por padrão será utilizado o mesmo servidor DPM dos backups
#restoreToSQLInstance - instância SQL Server onde serão restaurados os backups
#NewdatabaseName - Novo nome do banco de dados que será utilizado no restore - É opcional, por padrão é utilizado o nome original do banco de dados
#DropOriginalDatabase - flag que define se o banco de dados original será removido ou nao - É opcional e por padrão FALSE
#Source - define se a rotina deve procurar por backups em disco (Disk), fita (Media) ou ambos
#allDatabases - flag que define se devem ser restaurados TODOS os bancos de dados do protection group ou nao - É opcional e por padrão FALSE
#SystemDatabases - flag que define se os bancos de dados de sistema, caso hajam no protection group, devem ser restaurados ou nao - É opcional e por padrão FALSE
#idiom - idioma no qual serão exibidas as mensagens (english ou null (português) ) - É opcional, por padrão exibe as mensagens em inglês
#mail_subject - Mensagem que será utilizada no assunto do e-mail enviado ao final da execução do script. - É opcional, caso não seja definida será utilizada a mensagem padrão.
#DBnotToRestore - Banco de dados que será ignorado (não será restaurado)
#LogFileName - Nome e caminho do arquivo de log que será gerado. Caso não seja informado será utilizado o nome padrão C:\DPM.TXT
#DBtoStartAT - Nome do primeiro banco de dados a ser restaurado. Qualquer outro banco de dados cujo nome for menor que o informado nessa variável não será restaurado.
#DBtoFinishAT - Nome do último banco de dados a ser restaurado. Qualquer outro banco de dados cujo nome for maior que o informado nessa variável não será restaurado.
#ResetLogFile - Define se o arquivo de log deverá ser "resetado" ou não, sendo útil principalmente quando utilizamos as opções DBtoStartAT e DBtoFinishAT para realizar uma operação de Restore em momentos diferentes, mas que precisam do mesmo log.
#Inactive - Informa que os backups a serem restaurados estao em um Protection Group inativo, sem proteção, mas cujos arquivos cotinuam disponíveis. Caso ativado, o parametro sqlProtectionGroupName sera ignorado

####Explaining parameters (EN-US)

#dpmServerName - name of the DPM Server that is managing the backups
#sqlProtectionGroupName - Name of the protection group where the backup is at
#serverName - Original SQL Server server\instance where backups were executed
#databaseName - name of the database that is going to be restored
#restoreToServerName - name of the server that hosts the SQL Server instance where backups will be restored
#restorePathMdf - Path where MDF/NDF files will be restored at on destination server
#restorePathLog - Path where LDF files will be restored at on destination server
#doTheRestore = - flag that defines if restore is going to be executed or if it is going to simulate only. Optional, false is default
#dateOfRestoreToUse - date of the backup that we wish to restore - Optional, default is to restore the most recent backup
#dpmServerUsedToProtectClient - DPM server used to protect the client server where backups will be restored - Optional, default is to use the same server that protects the server
#restoreToSQLInstance - SQL Server instance where backups are going to be restored to
#NewdatabaseName - New database name that will be used on restore - Optional, default is to use original database name
#DropOriginalDatabase - flag that defines if original database will be removed or not - Optional, default is FALSE
#Source - Defines if the script should look for Disk or Media (Tape) backups or both
#allDatabases - flag that defines if ALL databases should be restored - Optional, default is FALSE
#SystemDatabases - flag that defines if system databases, in case they exist in protection group, should be restored - Optional, default is FALSE
#idiom - Idiom on which messages will be desplayed (english or null (brazilian portuguese) ) - Optional, default is english
#mail_subject - Message that will be used on the e-mail's subject after the script execution has finished. - Optional, default message will be used in this case.
#DBnotToRestore - Database that will be ignored (will not be restored)
#LogFileName - Name and path for the log file. If this is not informed the default value of C:\DPM.TXT wil be used
#DBtoStartAT - Name of the first database to be restored. Any other database whose name is smaller than the value of this variable will not be restored.
#DBtoFinishAT - Name of the last database to be restored. Any other database whose name is bigger than the value of this variable will not be restored.
#ResetLogFile - Defines if the log file will be reseted or not, being useful mainly when we use the options DBtoStartAT and DBtoFinishAT to execute a restore in different moments, but having the same log.
#Inactive - Used when restoring a backup from an inactive Protection Group, no longer protected, but with its files still available. When used, makes the option sqlProtectionGroupName to be ignored

####calling the function

#cls;
Publicado em Artigos, Powershell, Virtual PASS BR | Deixe um comentário

Segue uma boa dica de utilização da cláusula “GO”

Anderson Silva

Interessante como a cada dia se descobre algo novo, e particularmente motivador quando identificamos na novidade algo interessante para facilitar a vida.

Existe um comando no MS-SQLServer chamado ‘GO‘, normalmente utilizado para encerrar um determinado escopo de código.

Ex.:

SELECT * FROM PESSOA;

GO

Descobri nos últimos dias que o comando GOtambém pode ser utilizado para designar loops acrescentando simplesmente a quantidade de vezes que deseja que um escopo seja executado no MS-SQLServer.

Imagine que você queira executar um comando para atualizar 200 linhas em uma tabela, mas que de acordo com a regra de négocio, este  procedimento não poderá ser executado em massa (todos de uma vez). É necessário utilizar-se de um laço de repetição, normalmente codificado com o auxílio do comando WHILE.

Para exemplificar essa utilização, vou colocar abaixo a mesma tarefa utilizando as duas abordagens para elucubrar o que estou falando e a…

Ver o post original 182 mais palavras

Publicado em Não categorizado | Deixe um comentário

Controlando seu failover cluster com a propriedade AntiAffinityClassNames

Quando configuramos um failover cluster é comum que utilizemos a propriedade de “preferred owners” para definir onde queremos que cada cluster group “resida” em caso de failover, seguindo uma lista sequencial dos “donos” preferenciais.

Preferred Owners

Preferred Owners

Veja na figura acima que, utilizando essa propriedade, podemos definir a ordem dos nós preferenciais através dos botões “Up” e “Down”, além de selecionar os nós na listagem.

O “problema” dessa configuração é que ela não previne, por exemplo, que dois serviços façam um failover para o mesmo nó, tornando o mesmo sobrecarregado, mesmo havendo um outro nó ainda “vago”.

Para tal situação podemos utilizar a propriedade “AntiAffinityClassNames”. Essa propriedade é uma lista de strings que por padrão vem com o valor NULL. Ela pode conter nenhum ou vários valores, definidos pelo usuário e funciona da seguinte forma.

Definimos para cada cluster group o valor de “AntiAffinityClassNames” e, no momento de um failover, os valores dessa propriedades do cluster group que tenta o failover para o nó e dos cluster groups que já estão no nó são comparados. Caso pelo menos um dos valores dessa propriedade coincidam, o cluster considera esses cluster groups “sem afinidade” e não permite que “residam” no mesmo nó, levando então o cluster group a tentar um outro nó, de acordo com a sequência definida em “preferred owners“.

Sendo assim, principalmente em ambientes onde temos N cluster groups e N+1 nós, ou seja, onde temos um nó de “hot spare“, podemos utilizar a propriedade “AntiAffinityClassNames” para garantir que um cluster group migre para o nó vazio ou preferencial, sem termos que controlar manualmente os failovers.

Imagine então um ambiente node temos três nós, N1, N2 e N3, e temos 2 cluster groups de SQL Server que vamos chamar de SQL1 e SQL2. Vamos considerar que o valor de “preferred owners” de cada cluster group esteja configurado dessa forma.

SQL1 – NÓ1, NÓ2, NÓ3

SQL2 – NÓ2, NÓ3, NÓ1

Vamos também considerar que SQL1 está em NÓ1 e que SQL2 está em NÓ2.

Para impedir que SQL1 e SQL2 residam no mesmo nó e, automaticamente migrem para o nó que estiver vazio, definiremos o valor de “AntiAffinityClassNames” de ambos os cluster groups para “SQL”.

Para definir o valor de “AntiAffinityClassNames” precisamos utilizar uma ferramenta de linha de comando, seja o cluster.exe, ou seja via powershell. No nosso caso o comando ficaria da seguinte forma:

cluster group “SQL1″ /Prop AntiAffinityClassNames=”SQL”
cluster group “SQL2″ /Prop AntiAffinityClassNames=”SQL”

Caso queira consultar o valor de “AntiAffinityClassNames” basta utilizar o comando

cluster group “nome do cluster group” /Prop

e seus valores ficam armazenados na chave de registro “Reg_Multi_SZ” em “HKEY_LOCAL_MACHINE\Cluster\Groups\Guid\AntiAffinityClassNames” como pode ser visto na figura abaixo:

AntiAffinityClassNames no Registro

AntiAffinityClassNames no Registro

Com esses valores configurados imagine então que haja um problema no NÓ1 fazendo com que o cluster group SQL1 que está hospedado no mesmo execute um failover.

Como o mesmo está no NÓ1 que é o primeiro NÓ de sua lista de “preferred owners“, ele vai então tentar migrar para o próximo da lista, NÓ2.

Ao tentar o failover para NÓ2, ele compara o valor de “AntiAffinityClassNames” do cluster group que deseja ir para tal nó (SQL1) e o valor da propriedade dos cluster groups que já estão em tal nó (SQL2).

Nesse exemplo, o cluster verá que há strings em ambos que coincidem, definindo então que não há afinidade entre os cluster groups e, então, tentará realizar o failover de SQL1 no próximo NÓ da lista de “preferred owners” deste, no caso, NÓ3.

Como não há serviço em NÓ3 o SQL1 migrará para o mesmo.

Agora imagine que haja um problema em NÓ2 e que o cluster group SQL2 precise também fazer um failover. Nesse caso, seguindo a sequência de “preferred owners“, ele irá tentar primeiro o NÓ3. Novamente, ao comparar os valores de “AntiAffinityClassNames”, ele verá que os cluster groups são incompatíveis e irá então migrar SQL2 para NÓ1, caso esse esteja disponível.

Ai vem uma característica importante! Caso NÓ1 não esteja disponível, “AntiAffinityClassNames” não impede que os dois cluster groups possam residir no mesmo nó, isso acontece apenas caso haja algum outro nó disponível para o failover. Sendo assim, em nosso exemplo, caso NÓ1 não estivesse disponível, SQL2 iria SIM para NÓ3.

Outra característica importante é que, imagine em nosso exemplo que NÓ1 e NÓ2 não estão disponíveis e SQL1 e SQL2 estão em NÓ3. Nesse caso temos 2 cluster groups “sem afinidade” residindo no mesmo nó. Caso um nó “com afinidade” para um ou ambos os cluster groups volte a funcionar, um destes irá AUTOMATICAMENTE realizar um failover para tal nó, evitando assim que cluster groups “sem afinidade” residam no mesmo nó.

Obs1: a propriedade “AntiAffinityClassNames” pode ser utilizada inclusive para controlar a localização de VMs em um ambiente de cluster Hyper-V!

Obs2: esse ambiente com N cluster groups e N+1 nós configurado da forma que fizemos também é conhecido como “floating hot spare“.

Artigos relacionados:

Publicado em Artigos, Virtual PASS BR | 1 Comentário