Thursday, August 20, 2009

Obteniendo las tablas con mas registros.

Que tal,

Recientemente estaba tratando de ver las tablas que mas consumían espacio en todas las bases de datos, encontré un script en un blog que mostraba el total de registros en la base de datos, pero yo quería que fuera para todas las bases de datos.

Así que le hice unos pequeños cambios para que funcionara con todas las bases de datos.

Les comparto el script, esta probado en SQL 2005 aunque debería funcionar en SQL 2008.

El script no hace un count(*) a todas las tablas, sino que va directamente a las tablas de sistema:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[BPSQL_Select_big_tables]
AS
BEGIN
SET NOCOUNT ON;

DECLARE @dbname2 varchar(40);
declare @id varchar(30);

DECLARE dbname CURSOR FOR select name,database_id from master.sys.databases where database_id>4

create table #tablas(
base_de_datos varchar(128),
tabla varchar(128),
total int
)

OPEN dbname
FETCH NEXT FROM dbname
INTO @dbname2,@id

WHILE @@FETCH_STATUS = 0
BEGIN

exec('SET NOCOUNT ON
use [' + @dbname2 + ']
insert #tablas

SELECT b.name,OBJECT_NAME(object_id) AS [Table Name], SUM(Rows) AS [Row Count]
FROM sys.partitions,sys.databases b
WHERE index_id < 2
and OBJECT_NAME(object_id) not like ''%sys%'' and OBJECT_NAME(object_id) not like ''%queue%''
and OBJECT_NAME(object_id) <> ''dtproperties'' and b.database_id='+@id+'
GROUP BY object_id,b.name
ORDER BY SUM(Rows) DESC')

FETCH NEXT FROM dbname INTO @dbname2,@id
END

CLOSE dbname
DEALLOCATE dbname

select top 100 *
from #tablas
order by 3 desc
drop table #tablas

END

Tuesday, July 28, 2009

Revisando si existen permisos directos a los usuarios.

Una de las best practices en la administración de bases de datos, es asignar los permisos solo a los roles y no a los users o grupos, esto hace que las migraciones, planes de DRP, etc sean más sencillas.

Una de las formas de asegurar que se cumple con esta política en SQL 2005 es correr un script que indique si existen permisos directos a los users o grupos, en lugar de los roles.

Les comparto un script que hice, en el que se revisan en todas las bases de datos del servidor si se asignaron permisos directos, en caso de ser así, regresará la base de datos, el user, el tipo de permiso y la tabla, SP, etc.

Espero les sirva, yo lo corro diariamente en todos los servidores.


/* Revisa si existen usarios que tengan permisos directos en cada base de datos */
DECLARE @dbname2 varchar(40);
declare @id varchar(30);

DECLARE dbname CURSOR FOR select name,database_id from master.sys.databases;

CREATE TABLE #Permisos_Users(
[base_de_datos] [varchar](128) NULL,
[usuario] [varchar](128) NULL,
[tipo_permiso] [varchar](128) NULL,
[objeto] [varchar](128) NULL
)

OPEN dbname
FETCH NEXT FROM dbname
INTO @dbname2,@id

WHILE @@FETCH_STATUS = 0
BEGIN
exec('SET NOCOUNT ON;
use [' + @dbname2 + ']
INSERT #Permisos_Users
select d.name,b.name,a.permission_name,c.name from sys.database_permissions a inner join sys.database_principals b on a.grantee_principal_id=b.principal_id
inner join sys.objects c on a.major_id=c.object_id,sys.databases d where b.type_desc in (''SQL_USER'',''WINDOWS_USER'',''WINDOWS_GROUP'') and b.default_schema_name is not null
and b.name <> ''dbo'' and b.name <> ''guest'' and a.permission_name <> ''CONNECT'' and convert(varchar,d.database_id)='+@id+'')

FETCH NEXT FROM dbname INTO @dbname2,@id
END

CLOSE dbname
DEALLOCATE dbname

select * from #Permisos_Users

drop table #Permisos_Users

Friday, July 17, 2009

Planes de mantenimiento en SQL

Muchas veces me he topado, que en diferentes empresas tienen planes de mantenimiento de SQL en el que, por ejemplo, una vez a la semana hacen un reorganize, rebuild y despues un "Update Statistics" a todas las bases de datos operativas.

Lo he visto configurado así y me sorprendo que así lo tengan, es común verlo en las que el DBA está por accidente.

Este tipo de mantenimientos no deberían de estar al mismo tiempo, ya que es redundante, es un desperdicio de recursos y en ocasiones hasta puede salir contraproducente.

Lo que idealmente debería pasar es estar monitoreando el nivel de fragmentacion de los indices, si los indices tienen menos de un 30% de fragmentacion, entonces aplicaría un reorganize y si es mas alto entonces un rebuild, un rebuild es mucho mas costoso en performance pero es más efectivo, si se tiene la versión Enterprise de SQL 2005, se puede hacer Online y de esta manera no se afecta a los usuarios, excepto aquellas tablas que tengan columnas LOB, idealmente se debería hacer sobre demanda en las tablas que lo necesiten.

Esto es idealmente, si tienes una base de datos en las que se hacen muchos cambios (INSERT, DELETE, UPDATE) entonces lo recomendable es que se haga solamente un rebuild, en el momento mas oportuno, cuando no afecte la operación, si tienes la versión Enterprise de SQL 2005/2008 entonces se puede hacer Online.

Tambien se podría hacer un reorganize O un update statistics diario y aunque no son tan efectivos como el rebuild tambien ayudan a mejorar el performance y no tienen afectación a los usuarios.

Voy a estar compartiendo mas informacion sobre este tema más adelante.

Apuntes sobre Tomcat en Windows.

Recientemente estuve trabajando con Tomcat corriendo sobre Windows y después de estar trabajando con el por unos días tengo algunos tips que les pueden ser útiles.

Las versiones que utilizé fueron Tomcat 6.0.16 con Java 6 Update 7, pero podrían aplicar sobre otras versiones.

Aqui van algunos tips:

En el server.xml modificar la parte de logueo, ya que por default está deshabilitado y configurarlo para que no haga un resolve:

Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="combined" resolveHosts="false"


Si se van a tener contextos en los que constantemente se van a estar actualizando las aplicaciones, en produccion, por ejemplo, entonces hay que cambiar el archivo context.xml con las opciones antiResourceLocking="true" antiJARLocking="true" reloadable="true", modificarlo de:

Context

a

Context path="" antiResourceLocking="true" antiJARLocking="true" reloadable="true" cachingAllowed="false"


Al hacer esto todas las aplicaciones se copiarán al folder temp y desde ahí se van a servir y automaticamente se hara un deploy en caso de ser necesario. Esto es necesario porque en Windows los archivos se queden "lockeados" y las aplicaciones no se "recargan" completamente.


Si se configura de esta manera y tambien se tienen archivos estáticos, hay que configurar el contexto en el server.xml para excluirlo, de otra manera el contenido nuevo nunca se va a mostrar (a menos que se esté reiniciando el servicio de Tomcat).

Así quedaría:

Context path="/mifolder" antiResourceLocking="false" antiJARLocking="false" reloadable="false" override="true"



Tambien es recomendable configurar la memoria RAM que puede utilizar el tomcat, desde el archivo tomcatw.exe (en el folder bin), aquí se puede configurar la RAM mínima y máxima que puede utilizar el Tomcat.


Espero les sirvan estos tips.

Friday, June 26, 2009

Clonando servidores a través de los discos.

Que tal,

Tal vez alguna vez han estado en una situación en la que se necesita "clonar servidores", pero no se tiene una herramienta como Altiris, Ghost, etc, si los servidores son del mismo modelo, mismos discos (velocidad, capacidad, etc) y misma tarjeta controladora PERC es posible hacer un clonado de los discos sin instalar nada y se tienen servidores identicos.

Esto lo acabo de hacer con servidores Dell con RAID 1, controladora PERC, 2 discos en espejo.

Aqui van los pasos:

1. Sacar del dominio (en caso de estar en alguno) al servidor, de preferencia tener instalado el OpenManage Server Administrator.
2. Apagar el servidor y sacarle uno de los discos.
3. Al nuevo servidor apagarlo tambien y sacarle uno de los discos, de preferencia del mismo lado.
4. Encender el equipo y entrar a la controladora de la PERC (Ctrl+M), navegar en la configuracion hasta la parte donde vienen los discos duros y marcar el disco original (no el que le acabamos de poner) como offline, lo que hará esto, es marcar el disco como degradado y ya no lo tomará en cuenta para bootear ni nada.
5. Reiniciar el equipo, al iniciar entrar al Server Administrator, navegar hasta el disco marcado como degradado y forzar un rebuild, con esto se empezará a reconstruir el disco que ya tenia el servidor con la nueva información.
6. Ahora, con el disco que habiamos sacado del servidor, meterselo de nuevo al servidor y hacer el mismo procedimiento, encender el servidor, entrar a la controladora, marcar el disco que acabamos de poner como degragado y encender el equipo.
7. Entrar al OpenManage Server Administrator y forzar un rebuild.


Con esto se tienen 2 servidores iguales, sin hacer nada de configuracion, puede sonar un poco complicado y riesgoso, pero es muy efectivo.

Friday, June 19, 2009

Getting the file share resources properties from a Windows 2003 cluster.

Recently I was asked to do a script that generate a script to recreate all the file share resources from a Windows 2003.

I'm sharing the script I did, I'm using PowerShell, to run it needs the cluster name to connect to, then via WMI will get all the shares and using the tool cluster.exe (Available if you have the Administrative Tools) will get all the resource properties.

I run it every day in all my clusters, it creates a folder with the day it run, so you can run it everyday and have all the historical changes.


# Process to generate the script to re-create the file share resources from a Windows 2003 cluster.
# It needs the cluster name as a parameter and it generates the script with the configuration from each resource.
# The new cluster name will be NEWCLUSTER so you need to change it before run it.

param([string]$ServerOp) # Cluster Server Name.
$mydate = get-date -uformat "%Y%m%d"
$FolderScripts = "d:\DRCluster\scripts\$mydate\" # It will create this folder, so we have a folder by date.
$FileLog="d:\DRCluster\logs\DrCluster_$mydate.log" # Log File Name

clear-host
if (!$ServerOp){ throw "You must specify the cluster name" }

$f = [System.IO.Path]::Combine($FolderScripts, $ServerOp + "_shares.txt")
[string]$hora=get-date -uformat "[%r]"
out-file -filepath $FileLog -inputobject "#############################################################################################################################################" -append
out-file -filepath $FileLog -inputobject " " -append
out-file -filepath $FileLog -inputobject " PROCESS TO GENERATE THE SCRIPT TO RECREATE THE CLUSTER: $hora " -append
out-file -filepath $FileLog -inputobject " " -append
out-file -filepath $FileLog -inputobject "#############################################################################################################################################" -append
out-file -filepath $FileLog -inputobject " " -append

[string]$hora=get-date -uformat "[%r]"
if (!(Test-Path $FolderScripts)) {
out-file -filepath $FileLog -inputobject "$hora The folder $FolderScripts is created" -append
md $FolderScripts | Out-Null
if($? -ne $True) {
out-file -filepath $FileLog -inputobject "$hora ERROR: Couldn't create the folder: $FolderScripts" -append
throw "The folder $FolderScripts could not be created"
}
}

$f = [System.IO.Path]::Combine($FolderScripts, $ServerOp + "_shares.txt")
out-file -filePath $f -inputobject "REM ######################################################################################################## " -Encoding ASCII
out-file -filePath $f -inputobject "REM SCRIPT TO RECREATE THE SHARES IN ANOTHER CLUSTER" -Append -Encoding ASCII
out-file -filePath $f -inputobject "REM VERIFY DRIVES AND CLUSTER NAME BEFORE RUN IT! " -Append -Encoding ASCII
out-file -filePath $f -inputobject "REM ######################################################################################################## " -Append -Encoding ASCII
out-file -filePath $f -inputobject " " -Append -Encoding ASCII
out-file -filePath $f -inputobject " " -Append -Encoding ASCII

[string]$hora=get-date -uformat "[%r]"
out-file -filepath $FileLog -inputobject "$hora Getting the shares..." -append
$shares = Get-WMIObject Win32_Share -Computer $ServerOp
if($? -ne $True) {
out-file -filepath $FileLog -inputobject "$hora ERROR: There was an error getting the shares" -append
break
}else{
[string]$hora=get-date -uformat "[%r]"
out-file -filepath $FileLog -inputobject "$hora Done.." -append
}
out-file -filepath $FileLog -inputobject " " -append
[string]$hora=get-date -uformat "[%r]"
out-file -filepath $FileLog -inputobject "$hora Begin processing every share to get the configuration" -append
foreach ($sharefolder in $shares){
if (0 -eq $sharefolder.Type){

$Groups = Get-WmiObject -class MSCluster_ResourceGroupToResource -namespace "root\mscluster" -computername $ServerOp
$name=$sharefolder.Name

$Group = $Groups | Where-Object { $_.PartComponent -eq "MSCluster_Resource.Name="""+$name+"""" }
if ($Group -ne $null){
[string]$mygrp = $Group.GroupComponent
$Groupshare = $mygrp.Substring($mygrp.IndexOf(""""),$mygrp.Length-$mygrp.IndexOf(""""))
[string]$mystring="cluster /cluster:NEWCLUSTER resource "+"""$name"""+ " /create /group:"+$Groupshare+ " /type:""File Share"""
out-file -filepath $f -inputobject $mystring -append -Encoding ASCII
[string]$mystring="cluster /cluster:NEWCLUSTER resource "+"""$name"""+ " /priv path="""+$sharefolder.Path+""""
out-file -filepath $f -inputobject $mystring -append -Encoding ASCII
[string]$mystring="cluster /cluster:NEWCLUSTER resource "+"""$name"""+ " /priv Sharename="+$sharefolder.name
out-file -filepath $f -inputobject $mystring -append -Encoding ASCII
[string]$mystring="cluster /cluster:NEWCLUSTER resource "+"""$name"""+ " /priv Remark="""+$sharefolder.Description+""""
out-file -filepath $f -inputobject $mystring -append -Encoding ASCII
[string]$mystring="cluster /cluster:NEWCLUSTER resource "+"""$name"""+ " /prop Description="""+$sharefolder.Description+""""
out-file -filepath $f -inputobject $mystring -append -Encoding ASCII

$Access = @();
$SD = (Get-WMIObject -Class Win32_LogicalShareSecuritySetting -Computer $ServerOp -Filter "Name='$($name)'").GetSecurityDescriptor().Descriptor
$SD.DACL | %{ $Trustee = $_.Trustee.Name
If ($_.Trustee.Domain -ne $Null) { # The account has a domain.
$Trustee = "$($_.Trustee.Domain)\$Trustee"
$Access+= New-Object System.Security.AccessControl.FileSystemAccessRule($Trustee, $_.AccessMask, $_.AceType)
} elseif ( $_.Trustee.Name -ne $Null ) { # The account doesn't have a domain, but still valid, e.g. Everyone
$Access+= New-Object System.Security.AccessControl.FileSystemAccessRule($Trustee, $_.AccessMask, $_.AceType)
}else{ # The account doesn't have a domain nor valid.
Write-Host "Warning: Found an account in the share that could not be resolved, its possible that doesnt exists in AD"
$sharetemp1 = $sharefolder.Name
out-file -filepath $FileLog -inputobject "Found an account in the share that could not be resolved, its possible that doesnt exists in AD" -append
}
}
foreach($permission in $Access){
if ( $permission.FileSystemRights -eq "ReadAndExecute,Synchronize"){ $perm = "R" }
elseif ( $permission.FileSystemRights -eq "Modify,Synchronize"){ $perm ="RC" }
elseif ( $permission.FileSystemRights -eq "FullControl") { $perm = "F" }
if ($permission.AccessControlType -eq "Allow") {
[string]$mystring="cluster /cluster:NEWCLUSTER resource "+"""$name"""+ " /priv Security="""+$permission.IdentityReference+""""+",grant,"+$perm+":security"
out-file -filepath $f -inputobject $mystring -append -Encoding ASCII
}
}
$cmd = cluster.exe $ServerOp res $name /listdep | select-string "Online"
if($? -ne $True) {
out-file -filepath $FileLog -inputobject "$hora ERROR: THERE WAS AN ERROR GETTING THE DEPENDENCIES" -append
}
$gtp = " " + $Groupshare.substring(1,$Groupshare.length-2) + " "
foreach ($myline in $cmd){
[string]$valor=$myline;
$dep=($valor.substring(0,$valor.indexOf($gtp,$gtp.length-1))).trimend()
[string]$mystring="cluster /cluster:NEWCLUSTER resource "+"""$name"""+ " /AddDep:"""+$dep+""""
out-file -filepath $f -inputobject $mystring -append -Encoding ASCII
}

[string]$mystring="cluster /cluster:NEWCLUSTER resource "+"""$name"""+ " /On"
out-file -filepath $f -inputobject $mystring -append -Encoding ASCII
out-file -filepath $f -inputobject " " -append -Encoding ASCII
}
}
}
[string]$hora=get-date -uformat "[%r]"
out-file -filepath $FileLog -inputobject "$hora Done getting the shares properties" -append
out-file -filePath $FileLog -inputobject " " -Append

out-file -filepath $FileLog -inputobject "#############################################################################################################################################" -append
out-file -filepath $FileLog -inputobject " " -append
out-file -filepath $FileLog -inputobject " END OF THE PROCESS: $hora " -append
out-file -filepath $FileLog -inputobject " " -append
out-file -filepath $FileLog -inputobject "#############################################################################################################################################" -append



This will generate a file that will look like this:
cluster /cluster:NEWCLUSTER resource "Backup" /create /group:"mygroup" /type:"File Share"
cluster /cluster:NEWCLUSTER resource "Backup" /priv path="t:\mypath"
cluster /cluster:NEWCLUSTER resource "Backup" /priv Sharename=MYSHARE
cluster /cluster:NEWCLUSTER resource "Backup" /priv Remark="comments of my share"
cluster /cluster:NEWCLUSTER resource "Backup" /prop Description="comments of my share"
cluster /cluster:NEWCLUSTER resource "Backup" /priv Security="BUILTIN\Administrators",grant,F:security
cluster /cluster:NEWCLUSTER resource "Backup" /priv Security="MYDOMAIN\MYGROUP",grant,F:security
cluster /cluster:NEWCLUSTER resource "Backup" /priv Security="MYDOMAIN\MYACCOUNT",grant,F:security

Revisar permisos directos en SQL.

Es muy frecuente que se establezca como política (o como un Best Practice) que los permisos a los objetos de una base de datos se den solamente a roles y no a usuarios.

Esto es muy importante, es mucho mas recomendable asignar permisos a través de roles que a usuarios, de tal manera, que en caso de migrar una base de datos a un servidor diferente, solamente se tengan que recrear los usuarios, y no recrear los usuarios y tener que volver a asignar permisos.

Les paso un script que utilizo para detectar si se tienen permisos directos a los usuarios de una base de datos, en caso de ser así, se listan los objetos y los permisos, yo lo utiliza dentro de un script de PowerShell que me indica en caso de obtener algun registro que se asignaron mal los permisos.

El script recorre todas las bases de datos y revisa si asignaron permisos directos.

Espero les sirva:


SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[BPSQL_Revisa_Permisos_Users]
AS

DECLARE @dbname2 varchar(40);
declare @id varchar(30);

DECLARE dbname CURSOR FOR select name,database_id from master.sys.databases;

CREATE TABLE #Permisos_Users(
[base_de_datos] [varchar](128) NULL,
[usuario] [varchar](128) NULL,
[tipo_permiso] [varchar](128) NULL,
[objeto] [varchar](128) NULL
)

OPEN dbname
FETCH NEXT FROM dbname
INTO @dbname2,@id

WHILE @@FETCH_STATUS = 0
BEGIN
exec('SET NOCOUNT ON;
use [' + @dbname2 + ']
INSERT #Permisos_Users
select d.name,b.name,a.permission_name,c.name from sys.database_permissions a inner join sys.database_principals b on a.grantee_principal_id=b.principal_id
inner join sys.objects c on a.major_id=c.object_id,sys.databases d where b.type_desc in (''SQL_USER'',''WINDOWS_USER'',''WINDOWS_GROUP'') and b.default_schema_name is not null
and b.name <> ''dbo'' and b.name <> ''guest'' and a.permission_name <> ''CONNECT'' and convert(varchar,d.database_id)='+@id+'')

FETCH NEXT FROM dbname INTO @dbname2,@id
END

CLOSE dbname
DEALLOCATE dbname

select * from #Permisos_Users

drop table #Permisos_Users

Wednesday, May 27, 2009

Verificando Integridad de Backup Devices.

Recientemente modifique el script que había publicado anteriormente para detectar si en el servidor de respaldo existen todos los backup devices del servidor operativo, usando PowerShell.

El script es muy simple, solamente basta conectarnos a cada servidor SQL usando SMO y guardar en un arreglo la lista de backup devices, despues recorrer los arreglos del servidor operativo y verificar si están en el de respaldo.

Aqui va el script:

[void][reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
$srv=New-Object "Microsoft.SqlServer.Management.Smo.Server" SERVIDOROPERATIVO

$disp=@()
foreach($backup in $srv.backupdevices) {
$disp = $disp + $backup.Name
}



$srv2=New-Object "Microsoft.SqlServer.Management.Smo.Server" SERVIDORBACKUP
$disp2=@()
foreach($backup2 in $srv2.backupdevices) {
$disp2 = $disp2 + $backup2.Name
}

foreach ($dev in $disp) {
if ($disp2 -notcontains $dev){
Write-Host "No existe el backup " $dev "en el servidor de respaldo"
}
}


Solamente imprimirá si faltan backup devices en el servidor backup, pero se le puede agregar que lo escriba en un log, que mande un mail, etc.

Este script es parte de un plan de recuperacion de desastres, puesto que el servidor backup tiene que tener siempre los mismos backup devices del servidor operativo.

Espero les sirve.

Thursday, April 9, 2009

Restaurando la BD model en SQL.

Hace unos días, se hizo un restore de la base de datos model en el servidor backup por error, el problema fue que se hizo con la opción "With norecovery", esto ocasionó que se detuviera el servicio de SQL y ya no se pudiera levantar.

Buscando en internet, solamente encontré un artículo de Microsoft, explicando el problema y como solucionarlo el problema es que ese artículo no aplica para SQL 2005. Tambien encontré en un newsgroup de Microsoft una persona que le sucedió lo mismo y le explicaron un procedimiento y si no funcionaba se tenia que hacer un rebuild de la instalación.

Despues de estar intentando de diferentes maneras hacer levantar el servicio, al final pude hacerlo siguiente este procedimiento:

1. Levantar el servicio en single-user, con las opciones -c -m -T3608
2. Conectarse al servidor usando el comando osql
3. Hacer un detach de la base de datos:
use master
go
sp_detach_db 'model'
go
4. Copiar los archivos model.mdf y modellog.ldf a las mismas rutas del servidor (tienen que ser de otro servidor que tenga el mismo Service Pack de SQL)
5. Hacer el attach:
use master
go
sp_attach_db 'model','c:\Ruta\del\archivo_mdf','c:\ruta\del\archivo_ldf'
6. Detener el servicio de SQL y levantarlo normalmente.

Espero les sirva.

Tuesday, March 31, 2009

Monitoreo de Event Viewers de servidores remotos en Windows 2003.

Que tal,

Les comparto como poder monitorear event viewers de servidores Windows 2003 sin necesidad de permisos administrativos.

1. Obtener el SID del grupo o usuario al que se le quieren asignar los permisos, para esto correr el comando wbinfo -n "dominio\cuenta" en un servidor linux que tenga samba y que esté integrado al dominio, se puede obtener tambien desde Windows, registrando el archivo acctinfo.dll (regsvr32 acctinfo.dll) que viene incluido en el Resource Kit de Windows 2003, abriendo la consola de Active Directory Users and Computers y entrando a las propiedades de la cuenta o grupo.

2. Formar la cadena de permisos de la siguiente manera:

(;;;;;)

Por ejemplo:

(A;;0x1;;;S-1-2-21-1283441307-3045887142-1639236238-20210)

Con esta cadena se asignan permisos (A) de lectura (0x1) al grupo: midominio\grupo (1283441307-3045887142-1639236238-20210).

3. Asignar los permisos en los event viewers correspondientes, para esto hay que agregar la cadena obtenida en el paso anterior en la llave CustomSD que correspondan a los event viewers, la ruta en donde se encuentra este parámetro (en el registry) es:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Eventlog\\CustomSD

Es importante que se agreguen los permisos, no se sustituya la llave existente, porque ocasionaría serios problemas.
Espero que les sirva.

Thursday, March 12, 2009

Finding folders with specific dates old using the name.

Hi,
Recently, I had to do a script that finds all folders in a directory who have a certain number of days, each folder has a date, with format YYYYMMDD, and I had to find all folders that were X numbers of days old to move it to another folder.

After spending some time figuring out how can I could that, I will share with you the main function that I used and that is working right now.

The most tricky part was to validate that the folder was a valid date.
Well this is how I did it:

$dtout=New-Object datetime
$GciFiles = get-childitem $Path | where-object { $_.PsIsContainer -and $_.Name -match '^20[0-9][0-9][0-1][0-9][0-3][0-9]$' -and ([datetime]::tryParseExact($_.Name,"yyyyMMdd",[System.Globalization.CultureInfo]::EnglishName,[System.Globalization.DateTimeStyles]::None,[ref]$dtOut)) } | Where-Object { ((get-date) - [datetime]::parseexact($_.Name,"yyyyMMdd",$null)).days -ge $days } | Select-Object name


First, I declared a variable named dtout, so I could use it later, then I use the variable GciFiles to store the return of the gci(I know I could do that in a single line, but I wanted that way):

$GciFiles = get-childitem $Path

Then, the result of the gci, I filter only the folders and the folders whose name had the correct format, and using the function tryParseExact, I can validate that the date.

$GciFiles = get-childitem $Path | where-object { $_.PsIsContainer -and $_.Name -match '^20[0-9][0-9][0-1][0-9][0-3][0-9]$' -and ([datetime]::tryParseExact($_.Name,"yyyyMMdd",[System.Globalization.CultureInfo]::EnglishName,[System.Globalization.DateTimeStyles]::None,[ref]$dtOut)) }

Then, after I validated the date and the date was valid, I obtain the number of days from the folder's day to today and if that number is greater or equal the $days parameter the name is selected:

$GciFiles = get-childitem $Path | where-object { $_.PsIsContainer -and $_.Name -match '^20[0-9][0-9][0-1][0-9][0-3][0-9]$' -and ([datetime]::tryParseExact($_.Name,"yyyyMMdd",[System.Globalization.CultureInfo]::EnglishName,[System.Globalization.DateTimeStyles]::None,[ref]$dtOut)) } | Where-Object { ((get-date) - [datetime]::parseexact($_.Name,"yyyyMMdd",$null)).days -ge $days } | Select-Object name

Wednesday, February 4, 2009

Tips para actualizar el SP3 de SQL 2005.

Que tal,
Hace poco terminé de actualizar mi servidores SQL 2005 al SP3 CU1, así que les compartiré algunos tips, para aquellos que aún no lo han hecho.

En general, este Service Pack no da problemas, bueno eso, si el folder C:\windows\installer se mantiene intacto, de lo contrario puede dar algunos problemas y ser mas problematico. Los errores que me han tocado y que he visto son de que al momento de la instalación falle en la instalación del SP en algun componente, pero nunca he visto, que deje el servidor "tocado", o que ya nunca levante. En posts anteriores en este blog hablo de algunos de ellos.

Yo recomendaría que actualizaran al SP3 y de una vez al Cumulative Update 1, ya que los fixes que tiene, valen la pena, son solo 15 minutos mas en el procedimiento.

Aqui comparto algunos tips:
1. Si ya tienes instalado el SP2 en el servidor, descomprime el ejecutable del SP2 (con el 7-zip) y verifica el tamaño que tiene el archivo que viene en el folder hotfixtools, despues verifica que exista un archivo con el mismo tamaño en el folder C:\windows\installer, si existe es que no habrá problema, si no, ya sabes que en ese punto te puede fallar, si tienes un cluster, el archivo solamente estará en el servidor en donde se realizó la instalación.
2. Existe un bug en el cual, la cuenta con la que corre el servicio de SQL no debe ser domain admin, así que ya sabes, si así es, primero sacala del grupo domain admins.
3. Al momento de la instalación, si es un cluster, verifica que no haya alguien logueado por Remote Desktop en el otro nodo.
4. La instalación del SP en un cluster, es un poco tardada, dependiendo del número de nodos y de otras cosas, pero en mi caso, en servidores de 2 nodos, se tarda en total aprox. 40 minutos, así que hay que abrir una ventana de mantenimiento lo suficientemente holgada, a esto hay que añadir lo que se tardan los restarts de los nodos y los failovers. El CU1 se tarda aprox. entre 15 y 20 minutos.
5. Haz un backup completo de todas las bases de datos, nunca sabes que puede pasar y es mejor estar preparado, esto le aumenta tiempo en la instalación, pero es mejor prevenir, que lamentar.
6. De preferencia, antes de que lo vayas a instalar, instala el Windows Installer 4.5.
7. Se tiene que correr el instalador en el nodo en donde está el servicio de SQL funcionando.
8. Al momento de la instalación en cluster, de repente se queda en el mensaje: "Awaiting first complete passive cluster node SQLSERVER", a mi se me tardó en este punto, aprox. 10 minutos, pero despues avanzó, para que no se preocupen y no cancelen la instalación.
9. Revisa bien la estrategia de Disaster Recovery de tus servidores y preparate para el peor escenario, es mejor, estar siempre bien preparados y contar una buena estrategia de recuperacion de datos, si no existe, este es el momento de empezar.

Estos son los pasos que yo realize en cluster active-passive de 2 nodos:
1. Hacer failover de SQL hacia la instancia en donde hize la instalacion.
2. Correr script para deshabilitar jobs. Este script se puede generar utilizando mi script que tengo en otro de mis posts.
3. Correr script para matar conexiones y poner bds en read-only.
4. Correr script para backup de bases de datos.
5. Correr instalador del SP3.
6. Reiniciar el segundo nodo.
7. Hacer failover de SQL al segundo nodo.
8. Reinciar el primer nodo.
9. Instalar el CU1.
10. Reiniciar el segundo nodo.
11. Hacer failover de SQL hacia el segundo nodo.
12. Reiniciar el primer nodo.
13. Correr script para poner bases de datos en read-write.
14. Correr script para habilitar jobs.
15. Verificar aplicaciones que se conectan al servidor, conectividad, delegación, etc.

En teoría no es necesario reiniciar los nodos y hacer failovers despues de aplicar el CU1, pero yo lo hize para estar bien seguro de que no habrá problemas.

Si alguien quiere hacer algun comentario o hacerme preguntas acerca de algo, con todo gusto le ayudaré.

Verifying logins integrity between SQL 2005 servers with PowerShell.

Hi,
Recently I had to verify that every login in my production servers were in my backup servers, so I did a script using PowerShell to do that. This script is useful if you want to make sure that all your backup servers have the production logins.

The script takes 2 parameters, the production server and the backup server, all the logins in the production server must be in the backup server, in case it finds someone is missing, it writes it in a log and displays the login in the console.

This is the script:
param (
[string]$ServerOp ,
[string]$ServerBackup)

$LogFile = "C:\Intlogins\logs\Int_LOGINS_$nombre.log"

out-file -filepath $LogFile -inputobject ("#############################################################################################################################################")
out-file -filepath $LogFile -inputobject (" ") -append
out-file -filepath $LogFile -inputobject " START PROCESS TO VERIFY LOGINS INTEGRITY " -append
out-file -filepath $LogFile -inputobject (" ") -append
out-file -filepath $LogFile -inputobject ("#############################################################################################################################################") -append
out-file -filepath $LogFile -inputobject (" ") -append

$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server=$serverOp;Database=master;Integrated Security=True"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = "select name from master.sys.server_principals where is_disabled=0 and principal_id>258 and name <>'##MS_AgentSigningCertificate##'"
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object "System.Data.DataSet" "Table1"
$result = $SqlAdapter.fill($DataSet)

$SqlConnection2 = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection2.ConnectionString = "Server=$serverBackup;Database=master;Integrated Security=True"
$SqlCmd2 = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd2.CommandText = "select name from master.sys.server_principals where is_disabled=0 and principal_id>258 and name <>'##MS_AgentSigningCertificate##'"
$SqlCmd2.Connection = $SqlConnection2
$SqlAdapter2 = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter2.SelectCommand = $SqlCmd2
$DataSet2 = New-Object "System.Data.DataSet" "Table1"
$result2 = $SqlAdapter2.fill($DataSet2)

foreach ($row in $dataset.Tables[0].rows){
$exist=0
foreach ($row2 in $Dataset2.Tables[0].rows){
if([string]::Compare($row2.name,$row.name,$True) -eq 0){
# Write-Host "The login" $row.name "exists in backup server"
$exist=1
break
}
}
if ($exist -eq 0){
Write-Host "The login" $row.name "does NOT exist in backup server"
$var = "$hora ERROR: The login ["+ $row.name+ "] does NOT exist in backup server: $ServerBackup"
out-file -filepath $LogFile -inputobject $var -append

}
}

out-file -filepath $LogFile -inputobject (" ") -append
out-file -filepath $LogFile -inputobject (" ") -append
out-file -filepath $LogFile -inputobject ("#############################################################################################################################################") -append
out-file -filepath $LogFile -inputobject (" ") -append
out-file -filepath $LogFile -inputobject (" END PROCESS TO VERIFY LOGINS INTEGRITY") -append
out-file -filepath $LogFile -inputobject (" ") -append
out-file -filepath $LogFile -inputobject ("#############################################################################################################################################") -append

Wednesday, January 21, 2009

Mas del SP3 de SQL 2005.

Había comentado en el blog anterior que uno de mis servidores nunca pude actualizar el Client Components, pues bien, en uno de los foros de Microsoft, encontré que la solución estaba en tomar el archivo faltante del SP2, no del SP3, como le había hecho antes.

Pues bien, despues de sustituir el archivo faltante del folder Windows Installer, pude terminar la instalación del SP3 y del CU1.

Friday, January 16, 2009

Instalando el SP3 de SQL 2005.

Que tal,
Desde hace unos días empezé a instalar el SP3 de SQL 2005 en los servidores, hasta el momento llevo 2/3 que funcionan a la primera.

El día de ayer intente instalarlo en un servidor que es backup de los operativos, tiene 2 instancias de SQL 2005, la primera vez que corrí el instalador no se instaló en el database engine default y de la instancia y el client components, todo lo demás si se actualizó.

Hice varias pruebas, pero el resultado fue igual, revisando el log de instalación, encontré unas líneas con el error, indicando que faltaba un archivo: " Couldn't find local patch 'C:\WINDOWS\Installer\ce61b.msp'. Looking for it at its source.", en un blog de msdn encontré como corregir el problema, basicamente lo que hay que hacer es descomprimir el archivo de instalación, (con el 7-zip, por ejemplo) buscar el archivo que hace referencia un poco mas adelante en el log (en este caso era: sqlrun_tools.msp) y copiarlo en el folder C:\windows\installer, renombrado con el nombre que dice ahí ce62b.msp, después de hacer eso volví a correr el instalar y ahora me falló pero con el archivo C:\WINDOWS\Installer\ce62b.msp y volví a copiar el archivo.

Después de hacer eso, volvió a fallar, pero ahora avanzaba un poco más y ya no marcaba ese error, ahora el error en los logs era : "Error 2902. The installer has encountered an unexpected error. The error code is 2902. Operation ixfAssemblyCopy called out of sequence.", para este error encontré un artículo indicando que era un bug en el Windows Installer 3.1 y que ya había sido corregido en el Windows Installer 4.5, así que instalé el Windows Installer 4.5 en el servidor y ya pudo terminar la instalación en las instancias, en los Client Components nunca pudo terminar, siempre estuvo marcando error.

Despues de eso, instalé el Acumulativo 1 en el servidor y se instaló sin problemas.

Así que lo haré a partir de ahora será actualizar primero el Windows Installer a la 4.5 antes de instalar el SP3.

Esta es la liga del blog que les decía:
http://blogs.msdn.com/heaths/archive/2006/11/30/rebuilding-the-installer-cache.aspx

Espero que esto les sirva.

Esta es la liga del blog que les decía:
http://blogs.msdn.com/heaths/archive/2006/11/30/rebuilding-the-installer-cache.aspx