Check/Fix Usuários Órfãos após Restore com Powershell


Pessoal,

No artigo que sairá no simple-talk, fiz uma pequena alteração para contemplar usuários órfãos depois de um restore e o que coloquei aqui não tem, então segue o código.

Meu cenário na época era o seguinte.

Eu tinha aquele problema que alguns usuários das empresas que cuidávamos podiam alterar/Dropar, por contrato, os logins. Nunca entrei no mérito de saber porque,não era da mina conta isso. Eu tinha que fazer a coisa funcionar até se o Papa fosse o usuário principal.

Meu problema era que o pessoal ,muito bem capacitado não posso dizer o contrário, infelizmente não se preocupava com os usuários.

E isso era constante. Tínhamos muitos servidores deste gigante cliente. Digamos que eu fazia esta checagem todo dia.

Resolvi centralizando um script que coletava estes usuários órfãos em todos os servidores. Após isso o User ou era dropado ou criávamos o login.

Mas também tínhamos o caso do restore. Todo domingo restaurávamos por JOBs, bases de produção pra desenvolvimento.

Mas acontecia que o Servidor de Produção X tinha N bases a serem restauradas pro servidor de desenvolvimento Y, O servidor H mais N bases pra desenvolvimento.

Definimos que os usuários que não possuíam login seriam dropados após o restore e os outros mapeados.

Uma padronização que tínhamos : Os logins criados possuíam o mesmo nome de usuários. Pelo menos !!!!

Vamos lá. Primeira coisa é criar um TXT chamado ServersRestored.txt (ou qq outro nome..podemos passar o nome do script) que terá o nome dos servidores que terão o restore e logo a frente os bancos separados por vírgula.

Usaremos o Powershell 2.0, pois trabalharemos agora não mais com arquivo de funções e sim com Windows Powershell Script Module , Advanced Functions e Tratamento de Erros baseados em Try Catch.

Um Windows Powershell Script Module é um módulo de funções (Extensão .PSM1) que contém Advanced Functions e pode ser facilmente adicionado ao seu Arquivo de Profiler. As Advanced Functions são funções que possuem comandos e características avançadas, podendo assim ser colocadas num .ps1, no seu Arquivo de Profiler, ou digitadas em linha de comando mesmo.

Quer saber mais ?

Huddled Masses

You can do more than breathe for free…

  A guide to Advanced Functions

 

Voltando a nosso script, o txt será assim:

5

Colocamos na nossa pasta de scripts C:\PS\Servers o Script FixLoginsByrestore.ps1

  1: 
  2: <#	
  3: 
  4: 	$FileListName = Name of FileList that contains servers and db will be restored
  5: 	Server1,db2,db3
  6: 	Server2,db1,db2
  7: 	Server3\Instace1,db2,db3,db4,db5,db6
  8: 	
  9: 	$DropUserNotLogin = Flag to Drop a user withou login. default = true
 10: 	
 11: 	$PathServerList = Path that have a $FileListName
 12: 	
 13: 	$PathLogFile  = Path to log error file
 14: 	$PathFunctionFile = Path constains Function File
 15: 
 16: #>
 17: 
 18: 
 19: param 	(	[String]$FileListName,
 20: 			[bool]$DropUserNotLogin = $true,
 21: 			[string]$PathServerList = "C:\PS\servers",
 22: 			[string]$PathLogFile = "C:\PS\logs",
 23: 			[string]$PathFunctionFile = "C:\PS\PSScripts"
 24: 			
 25: 		)
 26: 
 27: 
 28: [reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | out-null 
 29: 
 30: 
 31: 
 32: $Error.Clear()
 33: 
 34: if ($FileListName -eq $null -or $FileListName -eq "") 
 35: {
 36: 	Write-Host "Missing Parameter Information !!!"
 37: 	break;
 38: }
 39: 
 40: [string] $VFileListName = ""
 41: [string] $VFunctionFile  =""
 42: 
 43: 
 44: $VFileListName = $PathServerList + "\" + $FileListName
 45: if (!(Test-Path -path $VFileListName))
 46: {
 47: 	Write-Host "Path File Server not found !!!"
 48: 	break;
 49: }	
 50: 
 51: $VFunctionFile = $PathFunctionFile + "\Functions.psm1"
 52: #try load functions file, if does not exists break code with message
 53: try {
 54: 	Import-Module -Name $VFunctionFile -WarningAction SilentlyContinue  -ErrorAction Stop 
 55: } 
 56: catch {
 57: 		Write-Host "Functions File not found !!!"
 58: 		break;
 59: }
 60: 
 61: 	
 62: 	
 63: 
 64: $TodayDate = get-date -format "yyyy-MM-dd hh:mm:ss" 
 65: $TodayDateLog = get-date -format "yyyyMMddhhmmss" 
 66: 
 67: 
 68: $PathLogFile = $PathLogFile + "\Log"
 69: 
 70: 
 71: foreach ($svr in get-content $VFileListName -ErrorAction Stop )
 72: {
 73: 	try {
 74: 			$ServerDBList = $svr.split(",")
 75: 			[Object] $SvrObject = $ServerDBList[0]
 76: 
 77: 			$TotalDB = ($ServerDBList.count) -1
 78: 			$Count = 1
 79: 			
 80: 			$Server= New-Object "Microsoft.SqlServer.Management.Smo.Server" "$SvrObject" -ErrorAction  stop
 81: 			for(;$Count -le $TotalDB;$Count++)
 82: 			{
 83: 			
 84: 				$Db = $Server.databases[$ServerDBList[$Count]] 
 85: 				$DbName = $Db.name
 86: 				$db.users  | Where-Object {!$_.IsSystemObject -and $_.Login -eq ""} | foreach {
 87: 					$ExistLogin =  Get-Login $Server $_.name
 88: 					if (!($ExistLogin) -and ($DropUserNotLogin))
 89: 					{
 90: 						try {
 91: 							$_.drop()
 92: 							$Msg = "User " + $User.name + " Dropped"
 93: 							Save-Log "OrphanedUSersRestore_MSG" "$SvrObject" "$DbName" "$msg" "$PathLogFile" "$TodayDateLog"
 94: 							
 95: 						}
 96: 						catch
 97: 						{
 98: 							$Err = $_.Exception.Message
 99: 							Save-Log "OrphanedUSersRestore_Err" "$SvrObject" "$DbName" "$Err" "$PathLogFile" "$TodayDateLog"
100: 							$Error.Clear()
101: 							continue;
102: 						}
103: 						
104: 					}
105: 					elseif (!$ExistLogin)
106: 					{
107: 						$Msg = "User Without Login : User " + $User.name
108: 						Save-Log "OrphanedUSersRestore_MSG" "$SvrObject" "$DbName" "$msg" "$PathLogFile" "$TodayDateLog"
109: 
110: 					}
111: 					elseif ($ExistLogin)
112: 					{
113: 						try {
114: 							$LoginName = $_.name
115: 							map-userlogin "$SvrObject" "$DbName" "$LoginName" 
116:     						$Msg = "Success Map User " + $user
117: 							Save-Log "OrphanedUsersRestore_Ok" "$Server" "$DbName" "$Msg" "$PathLogFile" "$TodayDate"
118: 						}
119: 						catch {
120: 								$Err = $_.Exception.Message + " Can not map user " + $user
121: 								Save-Log "OrphanedUSersRestore_ERR" "$SvrObject" "$DbName" "$Err" "$PathLogFile" "$TodayDateLog" 
122: 								$Error.Clear()
123: 								continue;
124: 						}		
125: 					
126: 					}	
127: 				}
128: 			}
129: 		}
130: 		catch {
131: 
132: 				$Err = $_.Exception.Message
133: 				Save-Log "OrphanedUSersRestore_ERR" "$SvrObject" "$DbName" "$Err" "$PathLogFile" "$TodayDateLog" 
134: 				$Error.Clear()
135: 				continue;
136: 		}
137: }		
138: 
139: 

Como podemos ver no script, temos os parâmetros :

[String]$FileListName,

        Nome do TXT a ser lido com a lista dos servidores e bancos que serão restaurados.

[bool]$DropUserNotLogin = $true,

        Flag que informa se o user vai ser dropado se não tiver o Login

[string]$PathServerList = "C:\PS\servers",

        Path que está a lista

[string]$PathLogFile = "C:\PS\logs",

        Path que será gerado o LOG. Neste script TUDO é logado. Se mapeou, dropou o login ou mensagens de erro

[string]$PathFunctionFile = "C:\PS\PSScripts"

        Path que está o modulo de funções (.PSM1)

Vamos colocar o modulo de funções :

  
  1: 
  2: Function Save-Log () 
  3: <#
  4: ----------------------------------------------------------
  5: Save  log in file
  6: ----------------------------------------------------------
  7: 
  8: File Name     			= $NamePS1
  9: Server name   			= $Server 
 10: Message To Log		    = $Message
 11: Path to generate file 	= $PathFileLog 
 12: Date to Log				= $TodayDate
 13: #>
 14: 
 15: 
 16: {
 17: 
 18: 	[CmdletBinding()]
 19: 	
 20: 	Param (
 21: 		[Parameter(Mandatory = $true )][String] $NamePS1,
 22: 		[Parameter(Mandatory = $true )][String] $Server,
 23: 		[Parameter(Mandatory = $true )][String] $DatabaseName,
 24: 		[Parameter(Mandatory = $true )][String] $Message,
 25: 		[Parameter(Mandatory = $true )][String] $PathFileLog,
 26: 		[Parameter(Mandatory = $true )][String] $TodayDate
 27: 		)
 28: 	process 
 29: 	{
 30: 	
 31: 		#test if path wich will contains the error file exists. if not create 
 32: 	
 33: 	if (!(Test-Path -path $PathFileLog))
 34: 	{
 35: 		try {
 36: 			New-Item $PathFileLog -itemtype directory -ErrorAction  Stop
 37: 		}
 38: 		catch {
 39: 			Write-Host "Can not create log file path"
 40: 			break;
 41: 		}
 42: 	} 
 43: 	
 44: 	
 45: 	$NameFileFull = $PathFileLog + "\" + $NamePS1 + $TodayDate + ".log" 
 46: 	
 47: 	$TDate = $TodayDate.Substring(0,4) + "-" + $TodayDate.Substring(4,2) + "-" + $TodayDate.Substring(6,2) 
 48: 	
 49: 	"Server : " + $Server,"Database : " + $DatabaseName,"Date : " + $TDate ,"Message: " + $Message | Out-file  $NameFileFull -append 
 50: 	} 
 51: }
 52: 
 53: Function Get-Login ()	
 54: <#
 55: ----------------------------------------------------------
 56: Verify login exists
 57: ----------------------------------------------------------
 58: Server Object = $Server
 59: Login Name = $LoginName
 60: Returns True/False if login exists
 61: 
 62: #>
 63: {
 64: 
 65: 	[CmdletBinding()]
 66: 	
 67: 	PARAM ( 
 68: 	[Parameter(Mandatory=$true )][object] $Server,
 69: 	[Parameter(Mandatory=$true )][String] $LoginName
 70: 	)
 71: 	process
 72: 	{
 73: 	
 74: 		$Collect = $Server.logins | where-object {$_.isdisabled -eq $False -and $_.IsSystemObject -eq $False -and $_.IsLocked -eq $False -and $_.name -eq $LoginName }  
 75: 		return  !($Collect -eq $Null)
 76: 	}
 77: }
 78: 
 79: Function Map-UserLogin ()
 80: {
 81: <#
 82: ----------------------------------------------------------
 83: Map USer and Logins
 84: ----------------------------------------------------------
 85: Server Name			= $Server
 86: Database Name		= $Database
 87: USer Name			= $USer
 88: 
 89: #>
 90: 
 91: 	[CmdletBinding()]
 92: 	
 93: 	Param ( 
 94: 			[Parameter(Mandatory = $true )][string] $Server,
 95: 			[Parameter(Mandatory = $true )][String] $Database,
 96: 			[Parameter(Mandatory = $true )][String] $USer
 97: 			)
 98: 			
 99: 	process
100: 	{
101: 	
102: 		$SqlConn = New-Object System.Data.SqlClient.SqlConnection
103: 		$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
104: 		
105: 		try{
106: 			$SqlConn.ConnectionString = "Server=" + $Server+ ";Database=" + $Database + ";Integrated Security=True"
107: 			$sqlconn.Open()
108: 			$SqlCmd.Connection = $SqlConn
109: 			$SqlCmd.CommandText = "ALTER USER " + $USer + " WITH LOGIN = " + $USer
110: 			$SqlCmd.ExecuteNonQuery()
111: 		}
112: 		finally {
113: 			$SqlConn.Close()
114: 		}
115: 	}
116: 
117: }

Baixar Functions.psm1 (functions.rar)

 

Com tudo pronto, deixávamos um Job para rodar as 6 da manhã de segunda (após todos os restores terem sido feitos) que fazia o trabalho pra gente.

 

A chamada era simples. Num Job CMDEXEC colocávamos :

 

Show de Bola !!!!

 

Com a passagem do arquivo txt que quisermos pro script, damos a flexibilidade de alterar para um servidor somente ou o numero de servidores e databases que você quiser.

Este script serve para 1 ou 1000 servidores..

 

Um dos inúmeros recursos que temos no powershell é produtividade.

Mas você deve estar pensando : Mas estes scripts estão um pouco maior dos que você colocava.

Sim estão . Quando temos muitos servidores (ou até um…como eu disse no artigo o que diferenciará você dos outros é sua maneira de pensar : Grande ou Pequena) tudo tem que ser repetitivo, uniforme, consistente automatizado e PRINCIPALMENTE com tratamento de erros.

Reparem que a maioria do código é tratamento de erros.

 

Tente fazer este mesmo script, com os recursos que tem nele (dropar ou mapear usuário, gerar arquivo de log para cada ação executada, verificar se o login existe pro usuário..etc) em TSQL e me diga, caso consiga fazer , o número de linhas que terá. Além do que você teria que gerar este script TSQL em cada servidor e assim, não centralizando o processo.

 

Como o som que finalizo este post, eu na minha humilde opinião tenho um pensamento sobre administração centralizada :

 

AP/DP – Antes de Powershell e Depois de Powershell

 

POWERSHEL ROCKS !!!

 

 

“Living easy, livin’ free
Season ticket, on a one, way ride
Asking nothing, leave me be
Taking everything in my stride

Don’t need reason, don’t need rhyme
Ain’t nothing I would rather do
Going down, party time
My friends are gonna be there too

I’m on the highway to hell

Picasa Content


I’m a highway to hell
Highway to hell
I’m on the highway to hell”

Highway To Hell

AC/DC

Follow me on Twitter

About Laerte Junior

Laerte Junior Laerte Junior is a SQL Server specialist and an active member of WW SQL Server and the Windows PowerShell community. He also is a huge Star Wars fan (yes, he has the Darth Vader´s Helmet with the voice changer). He has a passion for DC comics and living the simple life. "May The Force be with all of us"
This entry was posted in Powershell. Bookmark the permalink.

Leave a comment