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…
Voltando a nosso script, o txt será assim:
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 restored5: Server1,db2,db36: Server2,db1,db27: Server3\Instace1,db2,db3,db4,db5,db68:9: $DropUserNotLogin = Flag to Drop a user withou login. default = true10:11: $PathServerList = Path that have a $FileListName12:13: $PathLogFile = Path to log error file14: $PathFunctionFile = Path constains Function File15: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-null29: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 message53: try {
54: Import-Module -Name $VFunctionFile -WarningAction SilentlyContinue -ErrorAction Stop55: }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) -178: $Count = 179:80: $Server= New-Object "Microsoft.SqlServer.Management.Smo.Server" "$SvrObject" -ErrorAction stop81: for(;$Count -le $TotalDB;$Count++)
82: {83:84: $Db = $Server.databases[$ServerDBList[$Count]]85: $DbName = $Db.name86: $db.users | Where-Object {!$_.IsSystemObject -and $_.Login -eq ""} | foreach {
87: $ExistLogin = Get-Login $Server $_.name88: 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.Message99: 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 = $_.name115: 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.Message133: 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 = $NamePS19: Server name = $Server10: Message To Log = $Message11: Path to generate file = $PathFileLog12: Date to Log = $TodayDate13: #>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: process29: {30:31: #test if path wich will contains the error file exists. if not create32:33: if (!(Test-Path -path $PathFileLog))
34: {35: try {
36: New-Item $PathFileLog -itemtype directory -ErrorAction Stop37: }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 -append50: }51: }52:53: Function Get-Login ()54: <#55: ----------------------------------------------------------56: Verify login exists57: ----------------------------------------------------------58: Server Object = $Server59: Login Name = $LoginName60: 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: process72: {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 Logins84: ----------------------------------------------------------85: Server Name = $Server86: Database Name = $Database87: USer Name = $USer88: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: process100: {101:102: $SqlConn = New-Object System.Data.SqlClient.SqlConnection103: $SqlCmd = New-Object System.Data.SqlClient.SqlCommand104:105: try{
106: $SqlConn.ConnectionString = "Server=" + $Server+ ";Database=" + $Database + ";Integrated Security=True"107: $sqlconn.Open()108: $SqlCmd.Connection = $SqlConn109: $SqlCmd.CommandText = "ALTER USER " + $USer + " WITH LOGIN = " + $USer110: $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 strideDon’t need reason, don’t need rhyme
Ain’t nothing I would rather do
Going down, party time
My friends are gonna be there tooI’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
- RT @jacobsebastian: TSQL Challenge 12 – Solution Matthieu Hodin: i’m vry excited 2 write the post 4 the 3 winner of T.. http://bit.ly/18ECyW