Coisas que só o Powershell ISE faz por você

Tenho alguns clusters que estão em Multi-site, usando um Quorum em File Share para melhorar a disponibilidade.

Como o “Cluster Core” conta como voto na contagem do cluster, tenho preferencia na localização do recurso, já que todos os nós estão votando ele trabalha como voto de desempate.

Eu precisava monitorar um evento bem particular do cluster, quando o “Cluster Core Resource” alterasse de site ele deveria alarmar pelo SCOM, até ai tranquilo,,, faço um script no POSH o pessoal coloca para monitorar e pronto….


$API = new-object -comObject "MOM.ScriptAPI"
$bag = $api.CreatePropertyBag()
$resultado = (@(Get-ClusterGroup "Cluster Group" | Where-Object {$_.OwnerNode -like 'SERVIDOR*'}).count -eq 1)

if ($resultado -eq $true)
{
$bag.AddValue("State","Good")}
else
{
$bag.AddValue("State","Bad")}

$API.Return($bag)

Enquanto testava o script percebi que o Powershell ISE estava retornando mensagem de erro, fiquei revendo argumento, variável, comObject, mas não encontrei o erro…

Powershell ISE SCOM Script Error
Powershell ISE SCOM Script Error

Exception calling “Return” with “1” argument(s): “The handle is invalid. (Exception from HRESULT: 0x80070006 (E_HANDLE))”
At line:15 char:1
+ $API.Return($bag)
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation

A solução é bem simples:

  • Salve o arquivo em script e execute ele… o ISE não consegue interpretar o retorno do comObject.

O retorno é alguma coisa como:

<DataItem type=”System.PropertyBagData” time=”2015-06-24T12:18:40.1270337-03:00″ sourceHealthServiceId=”68A1F050-F975-9EE7-E0F3-C2CDE3445FD3″><Property Name=”State” VariantType=”8″>Bad</Property></DataItem>

Gerar DACPAC usando Powershell v2

Para quem leu meu post anterior de Gerar DACPAC usando Powershell percebeu que é um código muito simples, passível de erros como:

  • digitar o nome do banco errado,
  • símbolos nos nomes dos bancos,
  • não escolher um local ou o nome do servidor ele gera erro no script…

coisas bem primárias…

Dessa vez fiz algumas melhorias,,, como:

  • Tela mais simpática
  • conecta no servidor de banco e lista as bases
  • multi seleção de bases
  • não deixa você continuar se não selecionar um destino e colocar o nome do servidor
  • Não fecha sozinho
  • deixa na tela do posh o código para, caso precise, você copie e execute novamente
  • coloca o mouse como ampulheta
  • abre o diretório de destino quando acaba a tarefa
  • e localiza o executável do gerador de dacpac,,, desde que esteja no C:\

Uma coisa muito importante, o código é livre para modificação, sinta-se a vontade de fazer qualquer modificação que queira,, se achar que sua versão ficou melhor, ou achou algum problema e corrigiu, mande o código, vamos melhorar,,,,

Existe um detalhe para rodar esta versão sem problemas,,, você precisa ter o Windows atualizado além do Windows Management Framework 4.0 e do .Net Frame Work 4.5.2.


cls
$data = get-date -format &quot;_yyyyMMdd&quot;
$WindowTitle = &quot;Gerador de DACPAC&quot;
$LabelPath = &quot;Caminho:&quot;
$LabelServer = &quot;Servidor:&quot;

$aplicacao = (Get-ChildItem -Path &quot;C:\Program Files (x86)\Microsoft SQL Server&quot; -Include &quot;SqlPackage.exe&quot; -Recurse | % { $_.FullName } | Select-Object -First 1)

$x = @()

[void] [System.Reflection.Assembly]::LoadWithPartialName(&quot;System.Windows.Forms&quot;)
[void] [System.Reflection.Assembly]::LoadWithPartialName(&quot;System.Drawing&quot;)

$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = $WindowTitle
$objForm.Size = New-Object System.Drawing.Size(290,270)
$objForm.StartPosition = &quot;CenterScreen&quot;
$objForm.FormBorderStyle = 'FixedSingle'

$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq &quot;Escape&quot;)
{$objForm.Close()}})

$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,215)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = &quot;OK&quot;

$OKButton.Add_Click(
{$i=0
if ( $textbox2.Text -eq '' )
{

}

else
{
foreach ($objItem in $objListbox.SelectedItems)
{
#[System.Windows.Forms.Application]::UseWaitCursor=$true
$objForm.Cursor=[System.Windows.Forms.Cursors]::WaitCursor
$local = $textbox2.Text
$servidor = $textBoxServerName.Text
Write-Progress -Activity &quot;Gerando DACPAC&quot; -status &quot;Gerando DACPAC para $objItem&quot; -percentComplete ($i++ / ($objListbox.SelectedItems).Count*100)
$exec = (&quot;'$aplicacao'&quot; + &quot; /a:Extract /ssn:&quot;+$servidor +&quot; /sdn:&quot;+$objItem +&quot; /tf:&quot;+&quot;'$local'&quot;+&quot;\&quot;+$objItem+$data+&quot;.dacpac&quot;)
#$exec = (C:\&quot;Program Files (x86)&quot;\&quot;Microsoft SQL Server&quot;\110\DAC\bin\SqlPackage.exe /a:Extract /ssn:$servidor /sdn:$objItem /tf:$local\$objItem$data+.dacpac)
write-host $exec
Invoke-expression &quot;&amp; $exec&quot;
#[System.Windows.Forms.Application]::UseWaitCursor=$false
$objForm.Cursor=[System.Windows.Forms.Cursors]::Default
}
#Invoke-Item &quot;$local&quot;
}
if ( $checkbox1.Checked -eq $true -and $textbox2.Text -ne '')
{
Invoke-Item &quot;$local&quot;
}

else
{

}

#$objForm.Close()
})

$objForm.Controls.Add($OKButton)

$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,215)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = &quot;Fechar&quot;
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)

# create your checkbox
$checkbox1 = new-object System.Windows.Forms.checkbox
$checkbox1.Location = new-object System.Drawing.Size(260,66)
$checkbox1.Size = new-object System.Drawing.Size(250,50)
$checkbox1.Checked = $true
$objForm.Controls.Add($checkbox1)

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,120)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = &quot;Selecione a base que deseja extrair o DACPAC:&quot;
$objForm.Controls.Add($objLabel)

$objListbox = New-Object System.Windows.Forms.Listbox
$objListbox.Location = New-Object System.Drawing.Size(10,140)
$objListbox.Size = New-Object System.Drawing.Size(260,20)
$objLabel2 = New-Object System.Windows.Forms.Label
$objLabel2.Location = New-Object System.Drawing.Size(10,10)
$objLabel2.Size = New-Object System.Drawing.Size(280,20)
$objLabel2.Text = &quot;Servidor:&quot;
$objForm.Controls.Add($objLabel2)

# Create textbox for the Server name
$textBoxServerName = New-Object System.Windows.Forms.TextBox
$textBoxServerName.DataBindings.DefaultDataSourceUpdateMode = 0
$textBoxServerName.Location = New-Object System.Drawing.Size(10,30)
$textBoxServerName.Name = &quot;ServerName&quot;
$textBoxServerName.Size = New-Object System.Drawing.Size(100,10)
$textBoxServerName.TabIndex = 0
$objForm.Controls.Add($textBoxServerName)
$objLabel2 = New-Object System.Windows.Forms.Label
$objLabel2.Location = New-Object System.Drawing.Size(10,60)
$objLabel2.Size = New-Object System.Drawing.Size(280,20)
$objLabel2.Text = &quot;Destino:&quot;
$objForm.Controls.Add($objLabel2)

# Create textbox used to file destination
$textBox2 = New-Object System.Windows.Forms.TextBox
$textBox2.DataBindings.DefaultDataSourceUpdateMode = 0
$textBox2.Location = New-Object System.Drawing.Size(10,80)
$textBox2.Name = &quot;textBox2&quot;
$textBox2.Size = New-Object System.Drawing.Size(150,10)
$textBox2.TabIndex = 0
$objForm.Controls.Add($textBox2)

$app = New-Object -ComObject Shell.Application
$destino_OnClick=
{
#TODO: Place custom script here

try {
$browseForFolderOptions = 0
if ($NoNewFolderButton) { $browseForFolderOptions += 512 }

$folder = $app.BrowseForFolder(0, $Message, $browseForFolderOptions, $InitialDirectory)
#if ($folder) { $selectedDirectory = $folder.Self.Path } else { $selectedDirectory = '' }
#[System.Runtime.Interopservices.Marshal]::ReleaseComObject($app) &gt; $null
$textbox2.Text = $folder.Self.Path #$selectedDirectory #.FileName.ToString()

}
catch {
[System.Windows.Forms.MessageBox]::Show(
$_.Exception.Message,
&quot;Error&quot;,
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Error
)
}

}
$conectar_OnClick=
{
#TODO: Place custom script here

if ( $textBoxServerName.Text -eq '' )
{

}

else
{
$objForm.Cursor=[System.Windows.Forms.Cursors]::WaitCursor
$dataSource = $textBoxServerName.Text

$database = &quot;master&quot;
$connectionString = &quot;Server=$dataSource;Database=$database;Integrated Security=True;&quot;

$query = &quot;select name from sys.databases where database_id &gt; 4&quot;

$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
$connection.Open()
$command = $connection.CreateCommand()
$command.CommandText = $query
$result = $command.ExecuteReader()
$table = new-object “System.Data.DataTable”
$table.Load($result)
#$table
$objListbox.datasource=$table.Item(0)
$connection.Close()
$objForm.Cursor=[System.Windows.Forms.Cursors]::Default

}

}

$conectar = New-Object System.Windows.Forms.Button
$conectar.Location = New-Object System.Drawing.Size(200,30)
$conectar.Size = New-Object System.Drawing.Size(60,20)
#$button2.TabIndex = 1
$conectar.Text = &quot;Conectar&quot;
$conectar.UseVisualStyleBackColor = $True
$conectar.add_Click($conectar_OnClick)
$objForm.Controls.Add($conectar)
$destino = New-Object System.Windows.Forms.Button
$destino.Location = New-Object System.Drawing.Size(200,80)
$destino.Size = New-Object System.Drawing.Size(50,20)
#$button2.TabIndex = 1
$destino.Text = &quot;...&quot;
$destino.UseVisualStyleBackColor = $True
$destino.add_Click($destino_OnClick)
$objForm.Controls.Add($destino)

$objListbox.SelectionMode = &quot;MultiExtended&quot;

$objListbox.Height = 70
$objForm.Controls.Add($objListbox)
$objForm.Topmost = $True

$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()

$x

[polldaddy poll=8777809]

Gerar DACPAC usando PowerShell

O ciclo de vida da maioria dos bancos envolve desenvolvedores e DBA´s compartilhando scripts para atualização de objetos e atividades de manutenção.

Existem algumas formas de fazer isso:

  • gerar scripts de objetos
  • Proporcionar acesso as partes interessadas (piada… eu sei…) para gerar os scripts
  • DACPAC´s
  • Backup/Restore
  • etc..

Um pouco de contexto antes,,,

Um DAC (aplicativo da camada de dados) é uma entidade lógica de gerenciamento de banco de dados que define todos os objetos do SQL Server, como tabelas, exibições e objetos de instância, incluindo logons, associados a um banco de dados de usuário.

Todos tem seus prós e contras… não é a intenção deste post tratar isso…

Para quem gosta de gerar scripts dos objetos lembra que tem um outro post que faz isso…

A vantagem do DACPAC é que você pode usa-lo para comparação entre um projeto do TFS e esse pacote extraído da produção ou, caso você seja o DBA, você pode comparar esse pacote contra a sua base de produção ou uma base em homologação. Gerar os scripts para igualar os ambientes ou apenas conseguir ter ideia das diferenças.


Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Windows.Forms

$data = get-date -format &quot;_yyyyMMdd&quot;
$WindowTitle = &quot;Gerador de DACPAC&quot;
$LabelPath = &quot;Caminho:&quot;
$LabelServer = &quot;Servidor:&quot;

# Create the Label.
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Size(10,20)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.AutoSize = $true
$label.Text = $LabelPath

# Create the TextBox used to capture the user's text.
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Size(10,40)
$textBox.Size = New-Object System.Drawing.Size(575,200)
$textBox.AcceptsReturn = $true
$textBox.AcceptsTab = $false
$textBox.Multiline = $true
$textBox.ScrollBars = 'Both'
$textBox.Text = $DefaultText

# Create textbox used to file destination
$textBox2 = New-Object System.Windows.Forms.TextBox
$textBox2.DataBindings.DefaultDataSourceUpdateMode = 0
$textBox2.Location = New-Object System.Drawing.Size(65,20)
$textBox2.Name = &quot;textBox2&quot;
$textBox2.Size = New-Object System.Drawing.Size(150,10)
$textBox2.TabIndex = 0
# Create the Label.
$label2 = New-Object System.Windows.Forms.Label
$label2.Location = New-Object System.Drawing.Size(420,20)
$label2.AutoSize = $true
$label2.Text = $LabelServer

# Create textbox for the Server name
$textBoxServerName = New-Object System.Windows.Forms.TextBox
$textBoxServerName.DataBindings.DefaultDataSourceUpdateMode = 0
$textBoxServerName.Location = New-Object System.Drawing.Size(480,20)
$textBoxServerName.Name = &quot;textBox2&quot;
$textBoxServerName.Size = New-Object System.Drawing.Size(100,10)
$textBoxServerName.TabIndex = 0

# Create the OK button.
$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Size(415,250)
$okButton.Size = New-Object System.Drawing.Size(75,25)
$okButton.Text = &quot;OK&quot;
$okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() })

# Create the Cancel button.
$cancelButton = New-Object System.Windows.Forms.Button
$cancelButton.Location = New-Object System.Drawing.Size(510,250)
$cancelButton.Size = New-Object System.Drawing.Size(75,25)
$cancelButton.Text = &quot;Cancel&quot;
$cancelButton.Add_Click({ $form.Close() })

$app = New-Object -ComObject Shell.Application
$button2_OnClick=
{
try {

$browseForFolderOptions = 0
if ($NoNewFolderButton) { $browseForFolderOptions += 512 }
$folder = $app.BrowseForFolder(0, $Message, $browseForFolderOptions, $InitialDirectory)
$textbox2.Text = $folder.Self.Path #$selectedDirectory #.FileName.ToString()
}
catch {
[System.Windows.Forms.MessageBox]::Show(
$_.Exception.Message,
&quot;Error&quot;,
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Error
)
}

}
$button2 = New-Object System.Windows.Forms.Button
$button2.Location = New-Object System.Drawing.Size(215,20)
$button2.Size = New-Object System.Drawing.Size(50,20)
$button2.Text = &quot;...&quot;
$button2.UseVisualStyleBackColor = $True
$button2.add_Click($button2_OnClick)
# Create the form.
$form = New-Object System.Windows.Forms.Form
$form.Text = $WindowTitle
$form.Size = New-Object System.Drawing.Size(610,320)
$form.FormBorderStyle = 'FixedSingle'
$form.StartPosition = &quot;CenterScreen&quot;
$form.AutoSizeMode = 'GrowAndShrink'
$form.Topmost = $True
$form.AcceptButton = $okButton
$form.CancelButton = $cancelButton
$form.ShowInTaskbar = $true

# Add all of the controls to the form.
$form.Controls.Add($label)
$form.Controls.Add($label2)
$form.Controls.Add($textBox)
$form.Controls.Add($okButton)
$form.Controls.Add($cancelButton)
$form.Controls.Add($textBox2)
$form.Controls.Add($textBoxServerName)
$form.Controls.Add($button2)

# Initialize and show the form.
$form.Add_Shown({$form.Activate()})
$form.ShowDialog() &gt; $null

$srv = $textBoxServerName.Text
$local = $textbox2.Text
$bases = $textBox.Text.Split(&quot;`n&quot;)|%{$_.trim()}
$i=0
foreach ($s in $bases)
{
Write-Progress -Activity &quot;Gerando DACPAC&quot; -status &quot;Gerando DACPAC para $s&quot; -percentComplete ($i++ / $bases.count*100)
$GoBigSon= (C:\&quot;Program Files (x86)&quot;\&quot;Microsoft SQL Server&quot;\110\DAC\bin\SqlPackage.exe /a:Extract /ssn:$srv /sdn:$s /tf:$local\$s$data.dacpac )
}

Como parece de praxe,,, tem uns pequenos bugs:

  • Quando clicar no “…” para mapear o caminho, se não aparecer uma janela pop-up, movimenta a janela do app um pouco para o lado, ela ficou atrás da janela inicial,,, isso é um bug que acontece de vez em quando,,,
  • se vc clicar diversas vezes no “…” ele vai ficar abrindo diversas vezes…
  • ainda não estou tratando outros tipos de entrada no campo texto,,,, você deve colocar um banco abaixo do outro,,,
  • e sim,,, ele fecha a janela quando você clica em OK,,,

Ele ficou até que bem simpático…

 

Com os campos preenchidos

 

E tem até barra de status… olha que chique…

 

Powershell, Excel e SQL uma combinação excelente

Uma coisa muito legal em automatizar tarefas é a dificuldade que temos em planejar o que vai ser executado, a forma que vai ser executado e manter da forma mais genérica possível para garantir que vai funcionar na maior parte dos ambientes sem precisar sofrer muita alteração…

Agora imagina uma rotina qualquer em que você precisa ficar pegando resultados colocando no excel para fazer alguma graça interessante para alguém…

O posh abaixo faz exatamente isso. Ele conecta em um servidor que você definir, pega o nome dos bancos e executa a mesma query em cada um dos bancos,,,

até aí nenhuma novidade, certo?

O legal é que o resultado já vai para o excel, formatado e com auto filtro… e cada guia é a resposta de um banco deste servidor.

Você pode alterar a query para executar o que quiser, fragmentação de índice? blz…. um único select de uma única base? sem problemas,,, ou verificar estimativa de compactação de tabelas e índices usando compactação PAGE? é para isso que estamos aqui….


$servers = &quot;SERVIDOR&quot;

#lista as bases de dados para entrar no looping
$databases =
@'
SELECT
name
FROM sys.sysdatabases
where dbid&gt;4
'@

$resultsDB = (invoke-sqlcmd -ServerInstance $servers -Query $databases).name

#query para ser executada em cada base do looping
$query2 =
@'
declare @scanupd TABLE
(
table_name sysname NULL
,index_name sysname NULL
,partition int NULL
, index_id int NULL
,index_type nvarchar(12) NULL
,percent_scan bigint NULL
,percent_update bigint NULL
)

DECLARE @CompressionSavingsEstimate table
(
SchemaName sysname NOT NULL,
ObjectName sysname NOT NULL,
IndexName sysname NOT NULL,
IndexType nvarchar(60) NOT NULL,
PartitionNum int NOT NULL,
CompressionType nvarchar(10) NOT NULL,
[size_with_current_compression_setting (KB)] bigint NOT NULL,
[size_with_requested_compression_setting (KB)] bigint NOT NULL,
[sample_size_with_current_compression_setting (KB)] bigint NOT NULL,
[sample_size_with_requested_compression_setting (KB)] bigint NOT NULL,
percent_scan bigint NULL,
percent_update bigint NULL
PRIMARY KEY (SchemaName, ObjectName, IndexName, IndexType, PartitionNum, CompressionType)
);
DECLARE @ProcResult table
(
[object_name] sysname NOT NULL,
[schema_name] sysname NOT NULL,
[index_id] int NOT NULL,
[partition_number] int NOT NULL,
[size_with_current_compression_setting (KB)] bigint NOT NULL,
[size_with_requested_compression_setting (KB)] bigint NOT NULL,
[sample_size_with_current_compression_setting (KB)] bigint NOT NULL,
[sample_size_with_requested_compression_setting (KB)] bigint NOT NULL
);
DECLARE @SchemaName sysname;
DECLARE @ObjectName sysname;
DECLARE @IndexID int;
DECLARE @IndexName sysname;
DECLARE @IndexType nvarchar(60);
DECLARE @PartitionNum int;
DECLARE @CompTypeNum tinyint;
DECLARE @CompressionType nvarchar(60);

SET NOCOUNT ON;

DECLARE CompressedIndex INSENSITIVE CURSOR FOR
SELECT s.name AS SchemaName,
o.name AS ObjectName,
i.index_id AS IndexID,
COALESCE(i.name, '&lt;HEAP&gt;') AS IndexName,
i.type_desc AS IndexType,
p.partition_number AS PartitionNum
FROM sys.schemas AS s
INNER JOIN sys.objects AS o
ON s.schema_id = o.schema_id
INNER JOIN sys.indexes AS i
ON o.object_id = i.object_id
INNER JOIN sys.partitions AS p
ON o.object_id = p.object_id
AND
i.index_id = p.index_id
WHERE o.type_desc IN ('USER_TABLE','VIEW')
AND p.data_compression_desc NOT IN ('PAGE','ROW');

OPEN CompressedIndex;

WHILE 1 = 1
BEGIN
FETCH NEXT FROM CompressedIndex
INTO @SchemaName, @ObjectName, @IndexID, @IndexName, @IndexType, @PartitionNum;

IF @@FETCH_STATUS &lt;&gt; 0
BREAK;

SELECT @CompTypeNum = 2;
WHILE @CompTypeNum &lt;= 2
BEGIN
SELECT @CompressionType = CASE @CompTypeNum
WHEN 0 THEN 'NONE'
WHEN 1 THEN 'ROW'
WHEN 2 THEN 'PAGE'
END;

DELETE FROM @ProcResult;

-- RAISERROR('Estimating compression savings using &quot;%s&quot; compression for object &quot;%s.%s&quot;, index &quot;%s&quot;, partition %d...', 10, 1, @CompressionType, @SchemaName, @ObjectName, @IndexName, @PartitionNum);

INSERT INTO @ProcResult
EXEC sp_estimate_data_compression_savings @schema_name = @SchemaName,
@object_name = @ObjectName,
@index_id = @IndexID,
@partition_number = @PartitionNum,
@data_compression = @CompressionType;

INSERT INTO @CompressionSavingsEstimate
(
SchemaName,
ObjectName,
IndexName,
IndexType,
PartitionNum,
CompressionType,
[size_with_current_compression_setting (KB)],
[size_with_requested_compression_setting (KB)],
[sample_size_with_current_compression_setting (KB)],
[sample_size_with_requested_compression_setting (KB)]
)
SELECT [schema_name],
[object_name],
@IndexName,
@IndexType,
[partition_number],
@CompressionType,
[size_with_current_compression_setting (KB)],
[size_with_requested_compression_setting (KB)],
[sample_size_with_current_compression_setting (KB)],
[sample_size_with_requested_compression_setting (KB)]
FROM @ProcResult;

SELECT @CompTypeNum += 1;
END;
END;

CLOSE CompressedIndex;
DEALLOCATE CompressedIndex;
insert into @scanupd(table_name, index_name, partition,index_id,index_type,percent_scan,percent_update)
SELECT o.NAME AS [Table_Name]
,x.NAME AS [Index_Name]
,i.partition_number AS [Partition]
,i.index_id AS [Index_ID]
,x.type_desc AS [Index_Type]
,i.range_scan_count * 100.0 / (i.range_scan_count + i.leaf_insert_count + i.leaf_delete_count + i.leaf_update_count + i.leaf_page_merge_count + i.singleton_lookup_count) AS [Percent_Scan]
,i.leaf_update_count * 100.0 / (i.range_scan_count + i.leaf_insert_count + i.leaf_delete_count + i.leaf_update_count + i.leaf_page_merge_count + i.singleton_lookup_count) AS [Percent_Update]
FROM sys.dm_db_index_operational_stats(db_id(), NULL, NULL, NULL) i
JOIN sys.objects o ON o.object_id = i.object_id
JOIN sys.indexes x ON x.object_id = i.object_id
AND x.index_id = i.index_id
WHERE (i.range_scan_count + i.leaf_insert_count + i.leaf_delete_count + leaf_update_count + i.leaf_page_merge_count + i.singleton_lookup_count) != 0
AND objectproperty(i.object_id, 'IsUserTable') = 1
ORDER BY [Percent_Scan] DESC
SELECT CompressionType,
IndexName,
IndexType,
ObjectName,
PartitionNum,
CASE WHEN t.[percent scan] &gt;100 then 100 else [percent scan] END AS [percent scan],
CASE WHEN t.[percent update] &gt;100 then 100 else [percent update] END AS [percent update],
SchemaName,
[size_with_current_compression_setting (KB)],
[size_with_requested_compression_setting (KB)],
([size_with_requested_compression_setting (KB)]/NULLIF([size_with_current_compression_setting (KB)],0)*100) AS [X percent compact],
CASE WHEN (([size_with_requested_compression_setting (KB)]/NULLIF([size_with_current_compression_setting (KB)],0)*100) &lt;=80 OR ([size_with_requested_compression_setting (KB)]/NULLIF([size_with_current_compression_setting (KB)],0)*100) IS NULL) AND [t].[percent scan] &gt;=t.[percent update] THEN 'Compactar' ELSE 'Provavelmente Nao' END as [Z result]
FROM (
SELECT CompressionType,
IndexName,
IndexType,
ObjectName,
PartitionNum,
sum(s.percent_scan) AS [percent scan],
sum(s.percent_update) AS [percent update],
SchemaName,
cast(AVG([size_with_current_compression_setting (KB)]) AS money) AS [size_with_current_compression_setting (KB)],
cast(AVG([size_with_requested_compression_setting (KB)]) AS money) AS [size_with_requested_compression_setting (KB)]
FROM @CompressionSavingsEstimate CSE
INNER JOIN @scanupd AS s
ON CSE.ObjectName = S.table_name
GROUP BY GROUPING SETS (
(CompressionType),
(SchemaName, ObjectName, IndexName, IndexType, PartitionNum, CompressionType)
)
)AS t
ORDER BY SchemaName, ObjectName, IndexName, IndexType, PartitionNum, CompressionType DESC;

SET NOCOUNT OFF;
'@

# abre o excel
$xl = new-object -comobject excel.application
$wb = $xl.Workbooks.Add()
#$ExcelWorkSheet = $wb.Worksheets.Add()
$xl.Visible = $true
#$rowCount = 2

foreach ($s in $resultsDB)
{
$s #pode ser removido
$ExcelWorkSheet = $wb.Worksheets.Add()
$ExcelWorkSheet.Name = $s #usa o nome do banco como nome da planilha
$rowCount = 2
$results = invoke-sqlcmd -ServerInstance $servers -Query $query2 -Database $s -querytimeout ([int]::MaxValue) #timeout nunca !!!

foreach ($r in $results)
{
$r #pode ser removido
$ColCount = 1
$ExcelWorkSheet.Cells.Item($rowCount,$ColCount).Value2 = $servers
$ColCount = 2
for ($i=1; $i -le $r.ItemArray.Count; $i ++)
{
$ExcelWorkSheet.Cells.Item($rowCount,$ColCount).Value2 = $r.ItemArray[$i-1]
$ColCount ++
}
$rowCount ++
}
# adiciona o cabeçalho das colunas
$headers = $results | Get-Member -Membertype property
$ExcelWorkSheet.Cells.Item(1,1).Value2 = 'server'
$h = 2

foreach ($header in $headers)
{
$ExcelWorkSheet.Cells.Item(1,$h).Value2 = $header.name
$h ++
}
## Formata a planilha do Excel
$listObject = $ExcelWorkSheet.ListObjects.Add([Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange, $ExcelWorkSheet.UsedRange, $null,[Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes,$null)
$listObject.Name = &quot;User Table&quot;
$listObject.TableStyle = &quot;TableStyleLight10&quot;
## ajusta o tamanho das colunas
$ExcelWorkSheet.UsedRange.Columns.Autofit() | Out-Null

}

PS.: ainda estou tentando entender qual a birra do powershell em querer colocar o resultado das colunas em ordem alfabética,,, assim que resolver este detalhe atualizo o código,,,

SQL Saturday #325 – São Paulo

E aí pessoal !!!

O conteúdo da palesta sobre backup parcial e restauração parcial e online está disponível.

O Slide está disponível para download ou visualização abaixo

[slideshare id=40045338&doc=sqlsaturday325-141008203837-conversion-gate01]

O link para o download dos scripts e da apresentação está aqui.

Quero, novamente, agradecer pela oportunidade de ter palestrado no evento e agradecer a todos que tiveram paciência de assistir minha palestra.

O link para o acesso a agenda do evento é:

https://www.sqlsaturday.com/325/schedule.aspx

Snapshot

Esse script não tem nada de complexo, ele serve mais para ajudar a criar de uma forma dinâmica o SnapShot de uma base.

Montei ele para funcionar da forma mais genérica possível, para bases com 1 arquivo até bases com 1000 arquivos.

ele vai renomear a extensão dos arquivos e vai criá-los no mesmo local dos arquivos originais.

DECLARE @ExecStr nvarchar(max)
, @DB sysname
, @Name sysname
, @Physical_Name sysname

set @DB = '' --Base que terá o SnapShot criado

DECLARE FileListCursor CURSOR FAST_FORWARD FOR
SELECT Name
, Physical_Name
FROM sys.master_files
where database_id = DB_ID(@DB)
 and type &lt;&gt; 1

OPEN FileListCursor

FETCH NEXT FROM FileListCursor INTO @Name, @Physical_Name

SELECT @ExecStr = N'CREATE DATABASE ' + @DB + '_SS ' +
N' ON ( NAME = ''' + @Name + N''' , FILENAME = ''' + + replace(replace(@Physical_Name, '.mdf', '.ss'),'.ndf', '.ss') + + N''')
 '

FETCH NEXT FROM FileListCursor INTO @Name, @Physical_Name

WHILE @@FETCH_STATUS &lt;&gt; -1
BEGIN
 SELECT @ExecStr = @ExecStr + N', ( NAME = ''' + @Name
 + ''' , FILENAME = ''' + replace(replace(@Physical_Name, '.mdf', '.ss'),'.ndf', '.ss') + ''')
 '
 FETCH NEXT FROM FileListCursor INTO @Name, @Physical_Name
END

SELECT @ExecStr = @ExecStr + N' AS SNAPSHOT OF [' + @DB +']'

--exec (@ExecStr) --executa o TSQL
select (@ExecStr) --gera o TSQL para execuçao manual

DEALLOCATE FileListCursor
GO

Essa semana tive que usar esta solução para aquela feature meia boca (feita pela metade) do AlwaysOn no SQL 2012.

Quando o link ou o SQL do site primário apresenta problemas a base secundária cai junto,,, esse Always (nem sempre) On no 2012 tem esse problema que, em teoria, foi corrigido no 2014.

Como a migração de SQL está descartada, surgiram algumas alternativas mas o melhor cenário foi:

  • Criar uma base oca;
  • Criar sinônimos;
  • Criar SnapShot.

Agora tenho um Job que fica monitorando a DM do AlwaysOn e garante que os sinônimos da base oca estão apontando para a base que está sendo replicada. Quando ocorrer uma falha, ele gera um SnapShot dessa base e altera os sinônimos para a base SnapShot. E fica monitorando o ambiente para inverter a situação quando a base replicada for restabelecida.

Esse POG funciona… e por incrível que pareça é bem rápido… nos testes a criação do SnapShot da base com 4TB demorou 12 seg e a troca dos sinônimos foi instantânea.

 

T-SQL Tuesday #57 – SQL Family and community

Para este mês o anfitrião do T-SQL Tuesday #57 é o Sr. Jeffery Verheul  (B|T)

Quando começamos como DBA´s, começamos lendo alguns livros, vendo alguns materiais na internet, participando de alguns treinamentos, etc… No começo acaba sendo bem assim,,, simples,,,

Aí você se depara com algum problema um pouco mais complicado,,, ou precisa fazer uma feature mais específica funcionar,,, e começa a perceber que existem mais pessoas mundo a fora que também mexem com essa coisa de SQL…

Ai descobre os BLOG´s,  fóruns de discussão, a hash #SQLHelp e vê que existem não mais,,, mas muito mais pessoas interessadas nessa coisinha chamada SQL… que o mundo não é só você com seu banquinho, mas com uma infinidade de pessoas precisando de ajuda e muitas outras dispostas a ajudar…

Quando você achou que já sabia de tudo que podia existir no SQL sempre vem alguém com mais e mais informação ou melhor forma ou solução para coisas que talvez você nem tivesse ideia que poderia acontecer…

Quantas vezes você não passou a noite em claro tentando resolver problemas e depois de colocar a pergunta no Twitter com a hashtag #SQLHelp teve a resposta em menos de 5 min? vinda de um cara que está provavelmente na Nova Zelândia que uma vez já passou por esse problema e fez um post em um blog  qualquer com a solução?

Após um tempo você percebe que existem várias fomas de interação, Ex.:

  • SQL Saturday – palestras presenciais gratuitas,
  • 24h de SQL – 24 horas de palestras online gratuitas
  • WebCasts – 1h30 sobre um assunto específico
  • #SQLHelp – ajuda
  • SQLPASS – Principal evento presencial (pago) da comunidade, só tem feras…
  • Fórum – a forma mais básica de P/R
  • Sites e mais sites… – olha você aqui lendo no meu site… 😀

E o melhor de tudo, tirando o SQLPASS e o twitter, tudo em português! Não tem nem como reclamar…

Quer começar a fazer parte? acesse o SQLPass faça um cadastro e veja as comunidades que existem na sua região… é de graça…

Isso é comunidade, pessoas ajudando pessoas. Não para massagear egos, mas pelo grande prazer em criar um lugar melhor, fazer as coisas melhores.

PS: uma coisa muito importante: Seja educado e agradeça! Se alguém gastou tempo para te ajudar a resolver algum problema ou para te dar um rumo para alguma coisa o mínimo da sua parte é agradecer.

Contar VLF

Esse script é bem simples, ele vai acessar todas as bases da instância e vai contar quantos VLF´s existem em cada base.
Nada muito complexo.
Qual a importância disso? Muitos VLF´s podem acarretar em um restore muito lento, ou uma inicialização da base muito demorada.
Isso acontece, em muitos casos, por crescimento desordenado do transaction log (crescimento em porcentagem, ou em tamanhos pequenos).
Podemos colocar da seguinte forma: Falta de planejamento e atenção.
Quer saber mais sobre VLF? esse vídeo é bem legal.
Existe alguma forma de corrigir isso? Claro… Não é bonito…
Vamos fazer um quiz, coloquem um comentário com a maior quantidade de VLF´s que vocês tem no ambiente…


CREATE TABLE #temp (
 RecoveryUnitID VARCHAR(255) --essa coluna só existe a partir do 2012, comente esta linha se for executar do 2008r2 para traz
 , FileID VARCHAR(255)
 , FileSize VARCHAR(255)
 , StartOffset VARCHAR(255)
 , FSeqNo VARCHAR(255)
 , [Status] VARCHAR(255)
 , Parity VARCHAR(255)
 , CreateLSN VARCHAR(255)
 )

CREATE TABLE #DBCCResults (
 ServerName VARCHAR(255)
 , DBName VARCHAR(255)
 , VLF bigint
 )

EXEC master.dbo.sp_MSforeachdb
 @command1 = 'USE ? INSERT INTO #temp EXECUTE (''DBCC LOGINFO'')'
 , @command2 = 'INSERT INTO #DBCCResults SELECT @@SERVERNAME, ''?'', count(1) FROM #temp'
 , @command3 = 'TRUNCATE TABLE #temp'

 SELECT DISTINCT
 ServerName
 , DBName
 , VLF
 FROM #DBCCResults
 ORDER BY 3 DESC

 DROP TABLE #temp, #DBCCResults;

Testar Porta

Vocês sabem que se quiser testar uma porta TCP um dos métodos mais simples é basicamente um telnet Nome/IP porta.
Se o prompt sumir e o cursor ficar piscando a porta está respondendo (claro,, tirando todas as implicações de liberação de firewall e blá blá blá)
Eu precisava ficar fazendo um teste mais dinâmico, já que o telnet estabelece a conexão e espera uma intervenção para continuar eu queria apenas saber se a porta esta aberta ou não, estávamos tentando identificar uma falha se era no serviço ou na rede.
o script abaixo fica estabelecendo uma comunicação em um intervalo definido usando o socket TCP/IP estão podemos testar TCP e UDP bem no nível da camada e não da aplicação.
ele é bem simples, em qualquer momento que a porta não responda ele coloca a cor de fundo como vermelho.

function TestPort
{
    Param(
        [parameter(ParameterSetName='ComputerName', Position=0)]
        [string]
        $ComputerName,

        [parameter(ParameterSetName='IP', Position=0)]
        [System.Net.IPAddress]
        $IPAddress,

        [parameter(Mandatory=$true , Position=1)]
        [int]
        $Port,

        [parameter(Mandatory=$true, Position=2)]
        [ValidateSet(&quot;TCP&quot;, &quot;UDP&quot;)]
        [string]
        $Protocol
        )

    $RemoteServer = If ([string]::IsNullOrEmpty($ComputerName)) {$IPAddress} Else {$ComputerName};

    If ($Protocol -eq 'TCP')
    {
        $test = New-Object System.Net.Sockets.TcpClient;
        Try
        {
            Write-Host &quot;Connecting to &quot;$RemoteServer&quot;:&quot;$Port&quot; (TCP)..&quot;;
            $test.Connect($RemoteServer, $Port);
            Write-Host &quot;Connection successful&quot; -BackgroundColor Green;
        }
        Catch
        {
            Write-Host &quot;Connection failed&quot; -BackgroundColor Red;
        }
        Final
        {
            $test.Dispose();
        }
    }

    If ($Protocol -eq 'UDP')
    {
        $test = New-Object System.Net.Sockets.UdpClient;
        Try
        {
            Write-Host &quot;Connecting to &quot;$RemoteServer&quot;:&quot;$Port&quot; (UDP)..&quot;;
            $test.Connect($RemoteServer, $Port);
            Write-Host &quot;Connection successful&quot; -BackgroundColor Green;
        }
        Catch
        {
            Write-Host &quot;Connection failed&quot; -BackgroundColor Red;
        }
        Final
        {
            $test.Dispose();
        }
    }
}

A forma de testar ele é bem simples:

TestPort -ComputerName Nome/IP -Port 1433 -Protocol TCP 

Legal né? só que eu precisava ficar fazendo testes direto e reto e da forma acima ele não é um looping…
logo, para fazer da forma mais simples que conheço ficou assim:

$servidor = &quot;Nome/IP&quot;
while (1) {
    get-date #só pra saber quando executou
    TestPort -ComputerName $servidor -Port 1433 -Protocol TCP 
    sleep -seconds 1 #tempo de espera entre as execuções
} 

let's make things better

Exit mobile version