Wednesday, December 31, 2008

Script to migrate databases with PowerShell.

Hi,
In the last months, I've been writing some scripts in PowerShell to administer SQL Servers, this time, I will share my personal script to migrate database from one server to another.

You can test this script in your environment and see if it works, this is the scripts that I have used to migrate databases easily and quickly.

First, you have to kill all the processes in the databases and change them to read only, in order to guarantee that the data will not change during the migration.

The script read a txt file with the list of all databases to migrate, then it connects to source server, then, for every database it checks whether if it should migrate them, and takes a full backup, also it generates the scripts to re-create the database users, the script to add every user to its respective role and checks if there is any schema that its owner is different than dbo.

Then, it copies the backups to the backup server, restore it, change it to read_write, run the scripts to recreate users and add them to the roles.

One important thing is that it considers that if the data and log files are going to be different between the servers, it lets you specfy a new route, with some parameters.

The scripts take these parameters:
1. A txt file with the databases name to migrate. (for example: d:\databases.txt)
2. Path in the soruce server where the source server will write the backup (for example: d:\backups)
3. Path in the backup server where the backup server will take the backup (for example: d:\backups)
4. Path where will be written the scripts.
5. Source database server name.
6. Backup database server name.

For example:
# ./migradbs.ps1 d:\databases.txt d:\backups\ h:\backups\ d:\scripts "MYCURRENTSERVER" "MYNEWSERVER"

param (
[string]$filename=$(throw 'The parameter: $filename is required.'),
[string]$BackupPath=$(throw 'The parameter: $BackupPath is required.'),
[string]$RestorePath=$(throw 'The parameter: $RestorePath is required.'),
[string]$ScriptsPath=$(throw 'The parameter: $ScriptsPath is required.'),
[string]$OriginServer=$(throw 'The parameter: $OriginServer is required.'),
[string]$DestinyServer=$(throw 'The parameter: $DestinyServer is required.')
)
# MODIFY THIS PARAMETERS TO CHANGE THE PATHS FROM THE DATA AND LOGS.
# IF YOU LET IT WRONG, THE RESTORE WILL FAIL Y THE SCRIPT WILL EXIT WITH SOMETHING LIKE:
# Exception calling "SqlRestore" with "1" argument(s): "Restore failed for Server ?????'. "
# At :line:197 char:22 $restore.SqlRestore( <<<< $srv2)
[string]$path_data_source="d:\sql\"
[string]$path_log_source="d:\sql\"
[string]$path_data_backup="d:\sql\"
[string]$path_log_backup="d:\sql\"

if (! (test-path $filename)) {
throw "$($filename) is not a valid file!"
}

if (! (test-path $ScriptsPath)) {
throw "$($ScriptsPath) is not a valid path!"
}

$bds=Get-Content $filename | where {$_ -ne ""}
function okMigrate([string]$a) {
foreach ($bd in $bds){
if ([string]::Compare($a, $bd, $True) -eq 0){
return $true
break
}
}
return $false
}
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | out-null
$MyScripter=new-object ("Microsoft.SqlServer.Management.Smo.Scripter")
$srv=New-Object "Microsoft.SqlServer.Management.Smo.Server" $OriginServer
$MyScripter.Server=$srv

if (!$BackupPath.EndsWith("\")){ $BackupPath += "\" }
if (!$RestorePath.EndsWith("\")){ $RestorePath += "\" }
if (!$ScriptsPath.EndsWith("\")){ $ScriptsPath += "\" }
Write-Host "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
Write-Host " START SCRIPT TO MIGRATE DATABASES "
Write-Host "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
Write-Host "Backup path:" $BackupPath
Write-Host "Restore path:" $RestorePath
Write-Host "Scripts path:" $ScriptsPath
Write-Host "Source server:" $OriginServer
Write-Host "backup server:" $DestinyServer
Write-Host "DBS to migrate:" $bds
$mytime=get-date -uformat "%Y/%m/%d %H:%M:%S"
Write-Host "Current date and time:" $mytime
Write-Host "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"

foreach($database in $srv.databases) {
if (okMigrate($database.Name) -and $database.Name -ne "master" -and $database.Name -ne "model" -and $database.Name -ne "msdb" -and $database.Name -ne "tempdb"){
$backup=new-object "Microsoft.SqlServer.Management.Smo.Backup"
$backup.Action='Database'
$backup.Initialize='true'
$backup.Checksum='true'
$fil=new-object "Microsoft.SqlServer.Management.Smo.BackupDeviceItem"
$fil.DeviceType='File'
$fil.Name=[System.IO.Path]::Combine($BackupPath, $database.Name + ".bak")
$backup.Devices.Add($fil)
$backup.Database=$database.Name
write-host "Start backup of"$database.Name
$backup.SqlBackup($srv)
write-host "Finished backup of"$database.Name
#write-host "Getting the script to recreate the users of" $database.Name
$f = [System.IO.Path]::Combine($ScriptsPath, "Users_" + $database.Name + ".sql")
$MyScripter.options.IncludeDatabaseContext = $false
$e = "USE " + $database.Name
out-file -filePath $f -inputobject $e
out-file -filePath $f -inputobject "GO" -Append
foreach ($User in $database.Users) {
if ($User.Name -ne "sys" -and $User.Name -ne "dbo" -and $User.Name -ne "INFORMATION_SCHEMA" -and $User.Name -ne "guest") {
$MyScripter.Options.IncludeIfNotExists = $true
$MyScripter.Options.ScriptDrops = $true
$MyScripter.Script($User) | Out-file $f -append
}
if ($User.Name -ne "sys" -and $User.Name -ne "dbo" -and $User.Name -ne "INFORMATION_SCHEMA" -and $User.Name -ne "guest") {
$MyScripter.options.IncludeDatabaseContext = $false
$MyScripter.Options.ScriptDrops = $false
$MyScripter.Script($User) | Out-file $f -append
out-file -filePath $f -inputobject "`n" -append
}
}
#write-host "Getting the script to add every user to its roles..."
$f = [System.IO.Path]::Combine($ScriptsPath, "roles_" + $database.Name + ".sql")
$e = "USE " + $database.Name
out-file -filePath $f -inputobject $e
out-file -filePath $f -inputobject "GO" -Append
foreach ($role in $database.Roles) {
if ($role.Name -ne "public") {
foreach ($member in $role.EnumMembers()) {
if ($member -ne "dbo") {
$var = "sys.sp_addrolemember @rolename = N'" + $role.Name+"', @membername = N'"+$member+"'"
out-file -filePath $f -inputobject $var -append
out-file -filePath $f -inputobject "GO" -append
}
}
}
}

#Write-Host "Getting the scripts to change the paths during restore"
ForEach ($fg in $database.FileGroups){
foreach ($var1 in $fg.Files){
$movedata = $var1.Name + "," + ($var1.FileName.ToLower()).Replace($path_data_source,$path_data_backup)
}
}
ForEach ($lf in $database.LogFiles){
$movedata_log = $lf.Name + "," + ($lf.FileName.ToLower()).Replace($path_log_source,$path_log_backup)
}
$f = [System.IO.Path]::Combine($ScriptsPath, "Relocate_" + $database.Name + ".relocate")
out-file -filePath $f -inputobject $movedata
$f = [System.IO.Path]::Combine($ScriptsPath, "Relocate_" + $database.Name + "_log.relocate")
out-file -filePath $f -inputobject $movedata_log

$SqlConnection1 = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection1.ConnectionString = "Server=$OriginServer;Database=master;Integrated Security=True"
$SqlConnection1.open()
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = "select * from $database.sys.schemas where name not in ('dbo','sys','guest','INFORMATION_SCHEMA','db_accessadmin','db_backupoperator','db_datareader','db_datawriter','db_ddladmin','db_denydatareader','db_denydatawriter','db_owner','db_securityadmin') and principal_id<>1"
$SqlCmd.Connection = $SqlConnection1
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object "System.Data.DataSet" "Table2"
$resultado = $SqlAdapter.fill($DataSet)
if ($resultado -gt 0) {
write-host "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
write-host "WARNING: There are schemas which its owner its not dbo!"
write-host "To fix the problem: Change the schema owner, BEFORE migrate"
write-host "To change the owner run: ALTER AUTHORIZATION ON SCHEMA:: TO dbo"
write-host "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
}
$DataSet.clear()
$SqlConnection1.close()
}
}
Write-Host "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
write-host " DATABASE BACKUP FINISHED "
Write-Host "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"

if ($OriginServer.Contains("\")){
$mypath1 = $OriginServer.Substring(0,$OriginServer.IndexOf("\"))
} elseif ($OriginServer.Contains(",")){
$mypath1 = $OriginServer.Substring(0,$OriginServer.IndexOf(","))
} else {
$mypath1 =$OriginServer
}
$tempSourceDir = "\\" + $mypath1+ "\" + ($BackupPath -replace(":","$"))

if ($DestinyServer.Contains("\")){
$mypath2 = $DestinyServer.Substring(0,$DestinyServer.IndexOf("\"))
} elseif ($DestinyServer.Contains(",")){
$mypath2 = $DestinyServer.Substring(0,$DestinyServer.IndexOf(","))
} else {
$mypath2 =$DestinyServer
}
$tempTargetDir = "\\" + $mypath2 + "\" + ($RestorePath -replace (":","$"))

$srv2=New-Object "Microsoft.SqlServer.Management.Smo.Server" $DestinyServer
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server=$DestinyServer;Database=master;Integrated Security=True"
$SqlConnection.Open()

foreach ($myfile in [System.IO.Directory]::GetFiles($tempSourceDir,"*.bak")) {
$mydbname = ($myfile.Remove(0,$tempSourceDir.Length)) -replace (".bak","")
if (okMigrate($mydbname) -and $database.Name -ne "master" -and $database.Name -ne "model" -and $database.Name -ne "msdb" -and $database.Name -ne "tempdb"){
Write-Host "Start copying of" $myfile "to" $tempTargetDir
Copy-Item $myfile -Destination $tempTargetDir
Write-Host "Finished copying"
$restore=new-object "Microsoft.SqlServer.Management.Smo.Restore"
$restore.Action='Database'
$fil2=new-object "Microsoft.SqlServer.Management.Smo.BackupDeviceItem"
$fil2.DeviceType='File'
$fil2.Name=[System.IO.Path]::Combine($RestorePath,$mydbname+".bak")
$restore.Database=$mydbname
$restore.ReplaceDatabase='True'
$restore.Checksum='True'
$restore.Devices.Add($fil2)
#$restore.RelocateFiles.Clear()
$relocate_data=Get-Content ($ScriptsPath + "Relocate_" + $mydbname + ".relocate")
$DataFile=$relocate_data.Split(",")
$relocateDataFile=new-object("Microsoft.SqlServer.Management.Smo.RelocateFile")($DataFile[0],$DataFile[1])
$restore.RelocateFiles.Add($relocateDataFile) | Out-Null
$relocate_log=Get-Content ($ScriptsPath + "Relocate_" + $mydbname + "_log.relocate")
$LogFile=$relocate_log.Split(",")
$relocateLogFile=new-object("Microsoft.SqlServer.Management.Smo.RelocateFile")($LogFile[0],$LogFile[1])
$restore.RelocateFiles.Add($relocateLogFile) | Out-Null
Write-Host "Empieza el restore de"$mydbname
$restore.SqlRestore($srv2)
Write-Host "Termina el restore de"$mydbname
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.Connection = $SqlConnection
Write-Host "Changing" $mydbname "to Read_Write"
$SqlCmd.CommandText ="ALTER DATABASE $mydbname SET READ_WRITE"
$SqlCmd.ExecuteNonQuery() | Out-Null
Write-Host "Recreating users of"$mydbname
[diagnostics.process]::start("sqlcmd.exe","-S$DestinyServer -E -i`"" + $ScriptsPath + "Users_" + $mydbname + ".sql`"").WaitForExit()
Write-Host "Adding users of"$mydbname "to its roles"
[diagnostics.process]::start("sqlcmd.exe","-S$DestinyServer -E -i`"" + $ScriptsPath + "roles_" + $mydbname + ".sql`"").WaitForExit()
$srv2.Refresh()
}
Write-Host "Finished restore of"$mydbname
}
$SqlConnection.Close()
Write-Host "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
Write-host " END OF SCRIPT "
Write-Host "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
$mytime=get-date -uformat "%Y/%m/%d %H:%M:%S"
Write-Host "Current date and time:" $mytime

Monday, December 15, 2008

Error al levantar servicio de cluster de sql.

Que tal,
Esta semana con los parches de Microsoft, tuve un incidente un poco raro. Al instalar los parches en uno de los nodos, el servicio de cluster nunca levantó.

En el event viewer se registraba lo siguiente:

Cluster node MYCLUSTER failed to make a connection to the node over network 'Hearbeat'. The error code was 5.

The node cannot join the server cluster because it cannot communicate with node MYOTHERNODE over any network configured for internal server cluster communication. Check the network configuration of the node and the server cluster.

Después de estar por unos minutos reintando que levantara el servicio y justo cuando estabamos pensando en restaurar el system state del servidor, dimos con este artículo de la KB de microsoft: http://support.microsoft.com/kb/317232

El artículo aplica solo para servidores Windows NT y 2000 y mis servidores son Windows 2003, de igual manera seguimos la recomendación, que simplemente era reiniciar el servicio de cluster en el nodo que estaba funcionando, despues de hacer esto, el servidor pudo arrancar el servicio de cluster.

Cabe mencionar que hace un par de semanas, la cuenta con la que corre el servicio de cluster se le cambió su password, que fue cambiado, por cierto, usando el comando cluster, aquella vez no hubo problemas, estas son algunas de las cosas raras que pasan.

Monday, November 24, 2008

Configuracion de memoria en instancias de SQL.

Hace poco leí un artículo muy interesante en el blog Paul Randal acerca de configurar la memoria de varias instancias en servidores SQL 2005.

Basicamente lo que hay que hacer si se tiene un servidor SQL 2005-2008 con varias instancias de SQL que corren en diferentes máquinas, es asignar como memoria mínima el 40% del total de la memoria, este tip, me pareció muy interesante, porque hasta ahora todos los artículos que había visto en internet, decían que habia que poner como máximo el 40% de la memoria, para que en caso de estar las 2 instancias en el mismo nodo, no hubiera problemas.

Hay que recordar que esta memoria solo es la del buffer pool memory, no es la memoria que está consumiendo la instancia de SQL.

Para más información se puede ver este link

Monday, November 10, 2008

Viendo los permisos de los roles.

Muchas veces, nos topamos con que necesitamos obtener los permisos que tiene una base de datos en partícular, pero no podemos obtener todos los permisos rápidamente o los necesitamos exportar a excel, o queremos hacer una auditoría de permisos...

Para estas ocasiones aquí está un script que obtiene los permisos de una base de datos a todos los roles...

SELECT a.name as OBJETO,c.name as Rol, 'INSERT' as Permiso
FROM SYS.SYSOBJECTS A INNER JOIN sys.database_permissions B ON A.ID=B.MAJOR_ID INNER JOIN sys.database_principals c on b.grantee_principal_id=c.principal_id
where b.permission_name='INSERT' and a.name<>'dtproperties'
union
SELECT a.name as OBJETO,c.name as Rol, 'UPDATE' as Permiso
FROM SYS.SYSOBJECTS A INNER JOIN sys.database_permissions B ON A.ID=B.MAJOR_ID INNER JOIN sys.database_principals c on b.grantee_principal_id=c.principal_id
where b.permission_name='UPDATE' and a.name<>'dtproperties'
union
SELECT a.name as OBJETO,c.name as Rol, 'DELETE' as Permiso
FROM SYS.SYSOBJECTS A INNER JOIN sys.database_permissions B ON A.ID=B.MAJOR_ID INNER JOIN sys.database_principals c on b.grantee_principal_id=c.principal_id
where b.permission_name='DELETE' and a.name<>'dtproperties'
union
SELECT a.name as OBJETO,c.name as Rol, 'EXECUTE' as Permiso
FROM SYS.SYSOBJECTS A INNER JOIN sys.database_permissions B ON A.ID=B.MAJOR_ID INNER JOIN sys.database_principals c on b.grantee_principal_id=c.principal_id
where b.permission_name='EXECUTE' and a.name<>'dtproperties'
union
SELECT a.name as OBJETO,c.name as Rol, 'SELECT' as Permiso
FROM SYS.SYSOBJECTS A INNER JOIN sys.database_permissions B ON A.ID=B.MAJOR_ID INNER JOIN sys.database_principals c on b.grantee_principal_id=c.principal_id
where b.permission_name='SELECT' and a.name<>'dtproperties'
union
SELECT a.name as OBJETO,c.name as Rol, 'VIEW DEFINITION' as Permiso
FROM SYS.SYSOBJECTS A INNER JOIN sys.database_permissions B ON A.ID=B.MAJOR_ID INNER JOIN sys.database_principals c on b.grantee_principal_id=c.principal_id
where b.permission_name='VIEW DEFINITION' and a.name<>'dtproperties'

Lo pueden correr tranquilamente en sus servidores sin problemas de performance, este script lo utilizo regularmente para hacer auditorías de los permisos...

Obtiene un resultado parecido a este:
Objeto Rol Permiso
Tabla1 rol_users_rh select

Solamente se incluyen los permisos de select, insert, update, delete, execute y view definition, pero se le pueden agregar todos los permisos....

Espero les sirva.

Tuesday, October 28, 2008

Grupos anidados en SQL 2005

Recientemente, necesitaba agregar un grupo local en mi servidor SQL, para esto tenía un grupo local en mi dominio y en ese grupo local, existia otro grupo global de otro dominio, esto que pretendía hacer, era para facilitar la administración, ya que si en el grupo del otro dominio agregaban o quitaban personas, yo no tenía que hacer ningun cambio.

Bueno, despues de las pruebas que estuve haciendo, llegué a la conclusión que no se pueden tener grupos anidados en SQL 2005, si se necesita agregar un grupo en el servidor, es necesario que en ese grupo estén los miembros, no se pueden estar anidadando grupos, porque simplemente no funciona y la verdad, no creo que en el futuro, vaya a funcionar.

En fin, al final de cuentas, tuve que agregar a los usuarios al grupo de mi dominio.

Wednesday, October 8, 2008

Instalando instancias de SQL 2005 en diferentes ips.

Hace unos días, tenía que instalar una segunda instancia de SQL 2005 en un servidor backup, lo interesante era, que esta instancia tenía que tener otra ip diferente, debido a que este servidor backup era el respaldo de 2 servidores, uno con la instancia default y otro con instancia.

Esto con SQL 2000 no fuera posible, pero ya en SQL 2005 es posible, quedando la instancia default con una ip y la otra instancia con otra ip, estos son los pasos que realizé:

1. Instalar la instancia usando el setup de SQL 2005.
2. Agregar la segunda ip del servidor.
3. Abrir la consola del SQL Server Configuration Manager, en la sección SQL Server 2005 Network Configuration, abrir los protocols para la instancia default.

En el tab Protocol, la sección Listen All: No
En el tab IP Addresses, seleccionar la ip al que quedará bindeada la instancia default, y deberá quedar así:
Active: Yes
Enabled: Yes
IP Address: (la ip a bindear)
TCP Dynamic Ports (en blanco)
TCP Port: 1433

Al final en IPALL:
TCP Dynamic Ports (en blanco)
TCP Port: 1433

Ahora configurar la ip para la otra instancia:
En la sección TCP/IP de Protocols, configurar lo siguiente:
En el tab Protocol, la sección Listen All: No
En el tab IP Addresses, seleccionar la ip al que quedará bindeada la otra instancia y deberá quedar así:
Active: Yes
Enabled: Yes
IP Address: (la ip a bindear)
TCP Dynamic Ports (en blanco)
TCP Port: 1433

Al final en IPALL:
TCP Dynamic Ports (en blanco)
TCP Port: 1433

Después de hacer esto hay que reiniciar los servicios de SQL de la instancia default y de la otra instancia.

Como esta segunda ip no se está registrando en los DNS, puede ser necesario crear un registro host en los DNS asignandole un nombre y crear un c-name apuntando al host.

Es importante que el servicio SQL Server Browser esté encendido para que se pueda direccionar correctamente al usuario a la instancia que le corresponde.

Para saber que esto está configurado correctamente se puede correr el siguiente comando desde un Command Prompt:

netstat -an | find "LISTEN" | find "143"

Que deberá dar como resultado, algo parecido a esto:
TCP 192.168.0.1:1433 0.0.0.0:0 LISTENING
TCP 192.168.0.2:1433 0.0.0.0:0 LISTENING
TCP 127.0.0.1:1434 0.0.0.0:0 LISTENING

Lo cual indica que cada ip tiene asignada una instancia.

Tuesday, September 23, 2008

Scripting role members in every database using PowerShell.

Hi,
Starting this post I will be writing in English, in order to reach the users from the PowerShell Community.

Previously, I shared some scripts written in PowerShell to generate automatically some configurations (like users, jobs, linked servers, etc) in SQL Server 2005 using SMO with PowerShell.

In this post, I wrote a script to generate the users in every database, that script had an error, the script generated needed a GO command before the instruction to add the user to their respective role.

So, I created this script to generate a script with the sys.sp_addrolemember instruction for every role,it is helpful if you want to restore any database in other server (you would need first to recreate the users with the other script) or in case a Disaster Recovery scenario.

So, this is the script:

param (
[string] $serverName
)
$Scripts = "c:\scripts\"

[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | out-null
$MyScripter=new-object ("Microsoft.SqlServer.Management.Smo.Scripter")
$srv=New-Object "Microsoft.SqlServer.Management.Smo.Server" $serverName

foreach($db in $srv.databases) {
if ($db.Name -ne "master" -and $db.Name -ne "model" -and $db.Name -ne "msdb" -and $db.Name -ne "tempdb") {
$f = [System.IO.Path]::Combine($Scripts, $srv.Name +"_roles_" + $db.Name + ".sql")
out-file -filePath $f -inputobject "USE $db"
out-file -filePath $f -inputobject "GO" -Append
foreach ($role in $db.Roles) {
if ($role.Name -ne "public") {
foreach ($member in $role.EnumMembers()) {
if ($member -ne "dbo") {
$var = "sys.sp_addrolemember @rolename = N'" + $role.Name+"', @membername = N'"+$member+"'"
out-file -filePath $f -inputobject $var -append
out-file -filePath $f -inputobject "GO" -append
}
}
}
}
}
}


To run this script, you just need to specify the sql server and it will generate in c:\scripts a file for every database containing the script to add the members for every user, just like this:

USE [MyDB]
GO
sys.sp_addrolemember @rolename = N'HR', @membername = N'user1'
GO
sys.sp_addrolemember @rolename = N'sales', @membername = N'user2'
GO

Wednesday, September 10, 2008

Mas de los esquemas en SQL 2005.

En un post anterior comentaba acerca de lo recomendable que era que los owners de los esquemas no debían ser los mismo usuarios, para evitar problemas en futuras migraciones.

Pues bien, según una entrada del blog de Microsoft ISV Program Management Team, lo mejor que puedes hacer después de haber migrado a SQL 2005 es cambiar el default esquema de los usuarios a dbo, esto te dará un aumento de hasta un 15% en el throughput del servidor, esto, si en las llamadas que se hacen a los objetos no se incluye "dbo", que es un best practice, por cierto.

Se puede cambiar el default esquema de los usuarios fácilmente con el comando ALTER USER WITH DEFAULT_SCHEMA=dbo

Se puede utilizar este script para obtener como respuesta el script que se tiene que correr en cada base de datos:

select 'alter user '+ name + ' with DEFAULT_SCHEMA=dbo;'
from sys.database_principals
where default_schema_name is not null and name<>'dbo' and name <>'guest' and default_schema_name<>'dbo'

Esto se corre en cada base de datos, en la que se desea hacer y se obtiene de resultado el script con el que se cambian todos los usuarios rápidamente.

Una vez que se cambió el default esquema de los usuarios, lo recomendable es eliminar los esquemas de los usuarios, con este script se pueden obtener el script para eliminar los esquemas por cada base de datos:

select 'DROP SCHEMA '+ name + ';'
from sys.schemas
where schema_id between 5 and 16383

Wednesday, September 3, 2008

Detectando logins huérfanos.

Un script que puede ser útil para quienes tienen servidores SQL 2005 y que necesitan saber rápidamente si tienen en sus servidores logins que ya nadie los ocupa, es decir, no están como usuarios en ninguna base de datos, no están como local user en ningún linked server, no pertenecen a ningún rol de servidor y están habilitados.

Aquí está el código:

DECLARE @dbname2 varchar(40);
DECLARE dbname CURSOR FOR select name from master.sys.databases';

OPEN dbname
FETCH NEXT FROM dbname
INTO @dbname2

create table #nombres(
nombre varchar (128))

WHILE @@FETCH_STATUS = 0
BEGIN
exec('SET NOCOUNT ON;
use [' + @dbname2 + ']
INSERT #nombres
select b.name
from sys.database_principals a inner join sys.server_principals b on a.sid=b.sid
where a.type_desc <> ''DATABASE_ROLE'' and a.name <> ''dbo''
and a.name <> ''guest'' and a.name <> ''INFORMATION_SCHEMA'' and a.name <> ''sys'' ')
FETCH NEXT FROM dbname INTO @dbname2
END

CLOSE dbname
DEALLOCATE dbname

DECLARE users CURSOR FOR select distinct(nombre) from #nombres order by nombre;
declare @usuario varchar(128);

OPEN users
FETCH NEXT FROM users
INTO @usuario

create table #sids(
sid varbinary(256),
nombre varchar(128) )

WHILE @@FETCH_STATUS = 0
BEGIN
exec('SET NOCOUNT ON;
INSERT #sids
select suser_sid('''+ @usuario +'''),'''+@usuario+'''')
FETCH NEXT FROM users INTO @usuario
END

CLOSE users
DEALLOCATE users

select a.name as login
from sys.server_principals a left join #sids b on a.sid=b.sid
where b.sid is null and a.type_desc <>'CERTIFICATE_MAPPED_LOGIN' and a.type_desc <>'SERVER_ROLE' and a.name <> 'sa'
and a.name <>'BUILTIN\Administrators' and a.name <>'NT AUTHORITY\SYSTEM' and a.principal_id<>259
and a.principal_id not in (select distinct(local_principal_id) from sys.linked_logins) and a.is_disabled=0
and a.principal_id not in (select member_principal_id from sys.server_role_members)
order by a.name

drop table #nombres
drop table #sids

Friday, August 22, 2008

SQL 2005 y los esquemas.

Para los que hicieron su migración de bases de datos de SQL 2000 a SQL 2005, usando el tradicional método de backup/restore, tienen que tomar en cuenta que al hacer la migración se crearon esquemas por cada rol y usuario en cada base de datos.

Esto podría estar bien para aquellos entornos en que manejen así la seguridad, pero podría crear ciertos "problemas" para los que no. Uno de ellos es que cada user al migrarse es el owner de su esquema y no se puede eliminar por lo mismo.

El error que marca es el siguiente: Msg 15138, Level 16, State 1, Line 1
The database principal owns a schema in the database, and cannot be dropped.

Por lo que es necesario que antes de querer eliminar el user, se cambie el owner del esquema o bien se elimine el esquema.

Cambiar el owner de un esquema es muy sencillo, solamente hay que ejecutar esto:
ALTER AUTHORIZATION ON SCHEMA::[nombre_del_esquema] TO DBO;

Es recomendable hacerlo, para evitar en una futura migración, en la que se borran y vuelven a crear los usuarios.

Un script que se puede utilizar para generar scripts que hagan el cambio de owner de todos los esquemas es el siguiente:

select 'ALTER AUTHORIZATION ON SCHEMA::['+ name + '] TO DBO;'
from sys.schemas where name not in ('dbo','sys','guest','INFORMATION_SCHEMA','db_accessadmin','db_backupoperator','db_datareader','db_datawriter','db_ddladmin','db_denydatareader','db_denydatawriter','db_owner','db_securityadmin')
and principal_id<>1 order by schema_id

Solamente hay que estar en cada una de las bases de datos y correr el script. Una vez que se haya hecho el cambio de owner en los esquemas, los esquemas se pueden eliminar o bien dejarlos en la base de datos.

Monday, August 18, 2008

Actualizando firmware de Smart Array E200i

Hace poco estaba actualizando el firmware en un servidor Proliant BL460c G1 de HP.

En una de esas actualizaciones, actualizé el firmware de la tarjeta controladora de los arreglos (Smart Array E200i Controller) de la versión 1.66 a la versión 1.78, lo hice usando el exe que viene para servidores Windows 2003 a 32 bits, todo transcurrió normalmente, me conecté por la ILO y me di cuenta que al iniciar el servidor marcó unos mensaje y no encontró los discos duros y se pasó a la parte del PXE, así que le di un reboot nuevamente y ya pudo iniciar Windows.

Pero, al iniciar marcaba este mensaje: "A firmware update is needed for the physical drive with product ID GD072ABAB3 connected to array controller [Embedded]. The recommended minimum firmware revision should be HPDA." Obviamente no podía dejar el servidor así, ya que se iba a reinstalar y no quería que esto ocasionara problemas en el futuro.

Así que bajé el archivo .iso de la página de HP, con todos los firmware, utilizé el 7-zip para descomprimir el iso (y así no tener que quemar un CD), puse los archivos del iso en el servidor, ejecuté el el archivo CDBrowser, dentro del folder /compaq/CDBrowser, mediante la página del HomePage del servidor, chequé el modelo de los discos duros, en este caso DG072ABAB3, y en la sección de Device ahi estaba el modelo (SAS 72 GB 10K drives DG072ABAB3), desde la misma herramienta ejecuté el instalador y reinicié el servidor.

Cuando se estaba reiniciando el servidor detectó que estaba pendiente hacer un upgrade de versión de los discos y me presentaba 3 opciones, actualizar el porcentaje del update, dejar el update para el siguiente restart o cancelarlo, yo dejé que solo se actualizara y al cabo de unos 2 minutos, el update terminó y desaparecieron los mensajes de Warning.

Esto para tener en cuenta en un upgrade de versión de firmware de la tarjeta controladora de HP, la versión puede quedar muy arriba que puede ser que sea necesario actualizar los disco, lo que yo recomendaría sería primero actualizar el firmware de los discos duros, para evitar este tipo de sorpresas.

Tuesday, August 12, 2008

Mas scripts de SMO-SQL 2005 y PowerShell.

En el último post expuse algunos scripts de PowerShell para obtener diferentes propiedades de los servidores SQL 2005. En este post voy a continuar explicando algunos otros que también son muy útiles en una política de Disaster Recover de SQL 2005.

El primer script servirá para obtener el script para recrear los usuarios de cada base de datos y agregarlos en su rol correspondiente.

param ( [string] $serverName )
$RutaScripts = "c\scripts\"
[void][reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
$MyScripter=new-object ("Microsoft.SqlServer.Management.Smo.Scripter")
$srv=New-Object "Microsoft.SqlServer.Management.Smo.Server" $serverName
$MyScripter.Options.FileName = $f
$MyScripter.Options.AppendToFile = $true
$MyScripter.Server=$srv


write-host "Obteniendo el script para recrear los usuarios..."
foreach($db in $srv.databases) {
$f = [System.IO.Path]::Combine($RutaScripts, $nombre + "_Usuarios_" + $db.name + ".sql")
if ($db.Name -ne "master" -and $db.Name -ne "model" -and $db.Name -ne "msdb" -and $db.Name -ne "tempdb") {
$MyScripter.options.IncludeDatabaseContext = $true
foreach ($User in $db.Users) {
if ($User.Name -ne "sys" -and $User.Name -ne "dbo" -and $User.Name -ne "INFORMATION_SCHEMA" -and $User.Name -ne "guest") {
$MyScripter.Options.IncludeIfNotExists = $true
$MyScripter.Options.ScriptDrops = $true
$MyScripter.Script($User) | Out-file $f -append
}
if ($User.Name -ne "sys" -and $User.Name -ne "dbo" -and $User.Name -ne "INFORMATION_SCHEMA" -and $User.Name -ne "guest") {
$MyScripter.options.IncludeDatabaseContext = $false
$MyScripter.Options.IncludeDatabaseRoleMemberships = $true
$MyScripter.Options.ScriptDrops = $false
$MyScripter.Script($User) | Out-file $f -append
out-file -filePath $f -inputobject "`n" -append
}
}
}
}

El siguiente script sirve para obtener un script con los permisos de cada base de datos:
param ( [string] $serverName )
$RutaScripts = "c\scripts\"
[void][reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
$MyScripter=new-object ("Microsoft.SqlServer.Management.Smo.Scripter")
$srv=New-Object "Microsoft.SqlServer.Management.Smo.Server" $serverName
$MyScripter.Options.FileName = $f
$MyScripter.Options.AppendToFile = $true
$MyScripter.Server=$srv


write-host "Obteniendo el script de permisos de cada base de datos..."
foreach($db in $srv.databases) {
if ($db.Name -ne "master" -and $db.Name -ne "model" -and $db.Name -ne "msdb" -and $db.Name -ne "tempdb") {
$f = [System.IO.Path]::Combine($RutaScripts, $nombre +"_permisos_" + $db.Name + ".sql")
out-file -filePath $f -inputobject "USE $db `nGO`n"
foreach ($permiso in $db.EnumObjectPermissions()| where {$_.Grantee.ToString() -ne 'public'} ) {
$var=[string]$permiso.PermissionState+" "+$permiso.PermissionType+" ON "+$permiso.Grantor+"."+$permiso.ObjectName+" TO "+$permiso.Grantee
out-file -filePath $f -inputobject $var -append
out-file -filePath $f -inputobject "GO" -append
}
}
}

En los siguientes posts estaré dando más tips para establecer políticas de Disaster Recovery para servidores SQL 2000 o 2005.

Friday, August 8, 2008

Utilizando PoweShell con SQL 2005 SMO.

Ultimamente he estado utilizando PowerShell para ir creando un procedimiento para un Disaster Recovery Plan de SQL. En las siguientes entradas iré posteando información sobre como ir haciendo pequeños scripts que nos ayudarán para la administración de servidores SQL y como una herramienta para un DR.

Primeramente es necesario bajar de la página de Microsoft el PowerShell 1.0 y tener instaladas las herramientas de SQL 2005.

Es recomendable bajar la documentación del PowerShell de la misma página de Microsoft, la página que tiene The Mak en Database Journal es muy útil para ir aprendiendo de SQL 2005 con PowerShell, les estaré dando algunos ejemplos prácticos de cómo se puede utilizar como una herramienta para asegurar que se tiene un servidor backup de SQL listo para entrar en operación.

Una de las cosas que se tienen que asegurar siempre en un servidor es tener un respaldo de la configuración del servidor y de las bases de datos, así que voy a empezar con algunos ejemplos para obtener scripts de los servidores SQL.

Para correr los scripts solamente hay que guardar los scripts siguientes como archivo .ps1 y pasar de parámetro el nombre del servidor SQL con el quieren trabajar, por ejemplo: .\script1 "myserver".

Script para Recrear todos los jobs configurados en el servidor:

param (
[string] $serverName
)
$RutaScripts = "c:\scripts\"
void][reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
$MyScripter=new-object ("Microsoft.SqlServer.Management.Smo.Scripter")
$MyScripter.Options.FileName = $f
$MyScripter.Options.AppendToFile = $true
$MyScripter.Server=$srv
$jobs=$srv.jobserver.jobs

write-host "Obteniendo el script para recrear los jobs..."
$f = [System.IO.Path]::Combine($RutaScripts, $nombre + "_jobs.sql")
out-file -filePath $f -inputobject "USE MASTER `nGO`n"
foreach ($job in $srv.jobserver.jobs) {
$MyScripter.Script($job) >> $f
out-file -filePath $f -inputobject "`n" -append
}

Con este script se obtiene en un archivo de texto todos los jobs configurados en el servidor, con este script se puede configurar un servidor de respaldo en segundos (solamente hay que cambiar la variable Enabled a 0)


Script para recrear todos los logins:
param (
[string] $serverName
)
$RutaScripts = "c:\scripts\"
void][reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
$MyScripter=new-object ("Microsoft.SqlServer.Management.Smo.Scripter")
$MyScripter.Options.FileName = $f
$MyScripter.Options.AppendToFile = $true
$MyScripter.Server=$srv
write-host "Obteniendo el script para recrear los logins..."
$f = [System.IO.Path]::Combine($RutaScripts, $nombre + "_logins.sql")
out-file -filePath $f -inputobject "USE MASTER `nGO`n"
foreach($login in $srv.logins) {
$MyScripter.options.IncludeDatabaseContext = $true
$MyScripter.Options.IncludeIfNotExists = $true
$MyScripter.Options.ScriptDrops = $true
$MyScripter.Script($login) | out-file $f -append

$MyScripter.Options.IncludeIfNotExists = $true
$MyScripter.Options.ScriptDrops = $false
$MyScripter.Script($login) | out-file $f -append
out-file -filePath $f -inputobject "`n" -append
}

Este script es muy útil también cuando se está configurando un servidor de respaldo, obtiene todos los logins configurados, solamente hay que sustituir el password por el correcto y quitar la línea del disable, ya que por default SQL 2005 crea los logins deshabilitados.

Script para recrear todos los backup devices:
param (
[string] $serverName
)
$RutaScripts = "c:\scripts\"
void][reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
$MyScripter=new-object ("Microsoft.SqlServer.Management.Smo.Scripter")
$MyScripter.Options.FileName = $f
$MyScripter.Options.AppendToFile = $true
$MyScripter.Server=$srv

out-file -filepath $ArchivoLog -inputobject "$hora Obteniendo el script para recrear los Backup Devices..." -append
write-host "Obteniendo el script para recrear los Backup Devices..."
$f = [System.IO.Path]::Combine($RutaScripts, $nombre + "_Backup_devices.sql")
out-file -filePath $f -inputobject "USE MASTER `nGO`n"

foreach($backup in $srv.backupdevices) {
$MyScripter.Script($backup) | out-file $f -append
out-file -filePath $f -inputobject "`n" -append
}

Con este script como se indica en la descripción se obtiene el script de todos los backup devices, con este script puedes instalar el servidor en segundos.

Script para recrear los linked servers:
param (
[string] $serverName
)
$RutaScripts = "c:\scripts\"
void][reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
$MyScripter=new-object ("Microsoft.SqlServer.Management.Smo.Scripter")
$MyScripter.Options.FileName = $f
$MyScripter.Options.AppendToFile = $true
$MyScripter.Server=$srv

write-host "Obteniendo el script para recrear los linked servers..."
$f = [System.IO.Path]::Combine($RutaScripts, $nombre + "_linked.sql")
out-file -filePath $f -inputobject "USE MASTER `nGO`n"

foreach($linked in $srv.linkedservers) {
$MyScripter.options.IncludeDatabaseContext = $true
$MyScripter.Options.IncludeIfNotExists = $true
$MyScripter.Options.ScriptDrops = $true
$MyScripter.Script($linked) | out-file $f -append

$MyScripter.Options.IncludeIfNotExists = $true
$MyScripter.Options.ScriptDrops = $false
$MyScripter.Script($linked) | out-file $f -append
out-file -filePath $f -inputobject "`n" -append
}
Con este script se genera un script con el que se pueden crear todos los linked server, ojo, hay que recteclear el password y una vez creados cambiar la seguridad, ya que por default SQL los crea con: "Be made using the login's current security context".

En las siguientes posts iré publicando otros scripts que son útiles tambien para recrear usuarios, permisos y otras cosas.

Primer Post.

Que tal,
En este blog estaré hablando de algunas cosas que suceden en el área de Informática, como puede ser algo de consejos para hacer tal o cual cosa con determinada herramienta o tal vez algo que haya aprendido recientemente y que quiera compartir con el mundo.

Principalmente estaré hablando de tecnología utilizada en las empresas en general, pero enfocada en servidores Windows y Linux.

Espero poco a poco ir aportando ideas y conocimiento en esta área, en la que nunca dejas de aprender cosas nuevas.