bin/Public/Set-sqmDatabaseOwner.ps1
|
<#
.SYNOPSIS Setzt den Eigentuemer (Owner) einer oder mehrerer Datenbanken auf einen einheitlichen Login. .DESCRIPTION Prueft und korrigiert den Datenbankbesitzer auf einer oder mehreren SQL Server-Instanzen. Typischer Anwendungsfall: Nach Restores oder Migrationen ist der Owner oft ein nicht mehr existierender oder falscher Login. Die Funktion setzt ihn einheitlich auf den sa-Account (unabhaengig vom tatsaechlichen sa-Namen, der per Obfuskation umbenannt sein kann) oder einen beliebigen anderen Login. Ablauf pro Datenbank: 1. Aktuellen Owner lesen 2. Pruefen ob aenderung notwendig (bereits korrekt ? ueberspringen) 3. Pruefen ob Ziel-Login auf der Instanz existiert 4. ALTER AUTHORIZATION ON DATABASE::<Name> TO <Login> ausfuehren 5. Ergebnis protokollieren Gibt fuer jede Datenbank ein Statusobjekt zurueck: Status = OK / Skipped / Failed / NotFound .PARAMETER SqlInstance SQL Server-Instanz(en). Pipeline-faehig. Standard: aktueller Computername. .PARAMETER SqlCredential PSCredential fuer die Verbindung. .PARAMETER Database Datenbankname(n). Wildcards erlaubt (z.B. 'Prod*'). Standard: alle Benutzerdatenbanken. .PARAMETER ExcludeDatabase Datenbanken die ausgeschlossen werden. Wildcards erlaubt. .PARAMETER OwnerLogin Login der als neuer Eigentuemer gesetzt wird. Standard: sa-Account (wird automatisch anhand der SID 0x01 ermittelt, unabhaengig davon ob er umbenannt wurde). .PARAMETER IncludeSystemDatabases Auch Systemdatenbanken (master, model, msdb) einbeziehen. Standard: $false. tempdb wird immer ausgeschlossen. .PARAMETER Force Auch Datenbanken verarbeiten die bereits den korrekten Owner haben (erzwingt Neusetzen). .PARAMETER OutputPath Verzeichnis fuer das aenderungsprotokoll. Standard: aus Modulkonfiguration. .PARAMETER ContinueOnError Bei Fehler auf einer Instanz fortfahren. Standard: $false. .PARAMETER EnableException Ausnahmen sofort ausloesen. .EXAMPLE # Sa-Account auf allen Benutzerdatenbanken setzen Set-sqmDatabaseOwner -SqlInstance "SQL01" .EXAMPLE # Bestimmte Datenbanken mit eigenem Login Set-sqmDatabaseOwner -SqlInstance "SQL01" -Database "Prod*" -OwnerLogin "svc_sqlowner" .EXAMPLE # Pipeline ueber mehrere Instanzen 'SQL01','SQL02' | Set-sqmDatabaseOwner .EXAMPLE # WhatIf - nur zeigen was geaendert wuerde Set-sqmDatabaseOwner -SqlInstance "SQL01" -WhatIf .NOTES Erfordert: dbatools, Invoke-sqmLogging, Get-sqmDefaultOutputPath, Copy-sqmToCentralPath Benoetigt: sysadmin oder ALTER ANY DATABASE auf der Instanz. Der sa-Account wird ueber SID 0x01 ermittelt - funktioniert auch nach Umbenennung. Systemdatenbanken: master/model/msdb koennen Owner-aenderungen erhalten, tempdb nie. #> function Set-sqmDatabaseOwner { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)] [string[]]$SqlInstance = @($env:COMPUTERNAME), [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential]$SqlCredential, [Parameter(Mandatory = $false)] [string[]]$Database = @(), [Parameter(Mandatory = $false)] [string[]]$ExcludeDatabase = @(), [Parameter(Mandatory = $false)] [string]$OwnerLogin, [Parameter(Mandatory = $false)] [switch]$IncludeSystemDatabases, [Parameter(Mandatory = $false)] [switch]$Force, [Parameter(Mandatory = $false)] [string]$OutputPath, [Parameter(Mandatory = $false)] [switch]$ContinueOnError, [Parameter(Mandatory = $false)] [switch]$EnableException ) begin { $functionName = $MyInvocation.MyCommand.Name $allResults = [System.Collections.Generic.List[PSCustomObject]]::new() if (-not $script:dbatoolsAvailable) { $msg = "dbatools-Modul nicht gefunden. Bitte installieren: Install-Module dbatools" Invoke-sqmLogging -Message $msg -FunctionName $functionName -Level "ERROR" throw $msg } if (-not $OutputPath) { $OutputPath = Get-sqmDefaultOutputPath } Invoke-sqmLogging -Message ("Starte " + $functionName) -FunctionName $functionName -Level "INFO" } process { foreach ($instance in $SqlInstance) { Invoke-sqmLogging -Message ("[$instance] Verarbeite Instanz") -FunctionName $functionName -Level "INFO" try { $connParams = @{ SqlInstance = $instance SqlCredential = $SqlCredential } # ------------------------------------------------------------------- # 1. Ziel-Login ermitteln # Standard: sa-Account via SID 0x01 (funktioniert nach Umbenennung) # ------------------------------------------------------------------- $targetLogin = $OwnerLogin if (-not $targetLogin) { $saQuery = "SELECT name FROM sys.server_principals WHERE sid = 0x01 AND type = 'S'" $saResult = Invoke-DbaQuery @connParams -Database 'master' -Query $saQuery -ErrorAction Stop if ($saResult) { $targetLogin = $saResult.name } else { throw "Sa-Account (SID 0x01) konnte nicht ermittelt werden." } } # Pruefen ob Ziel-Login existiert $loginCheckQuery = "SELECT name FROM sys.server_principals WHERE name = N'$($targetLogin -replace "'", "''")' AND type IN ('S','U','G')" $loginExists = Invoke-DbaQuery @connParams -Database 'master' -Query $loginCheckQuery -ErrorAction Stop if (-not $loginExists) { throw "Ziel-Login '$targetLogin' existiert nicht auf '$instance'." } Invoke-sqmLogging -Message ("[$instance] Ziel-Owner: $targetLogin") -FunctionName $functionName -Level "INFO" # ------------------------------------------------------------------- # 2. Datenbanken ermitteln # ------------------------------------------------------------------- $dbGetParams = @{ SqlInstance = $instance SqlCredential = $SqlCredential } $allDbs = Get-DbaDatabase @dbGetParams # tempdb immer ausschliessen $filtered = $allDbs | Where-Object { $_.Name -ne 'tempdb' } # Systemdatenbanken if (-not $IncludeSystemDatabases) { $filtered = $filtered | Where-Object { -not $_.IsSystemObject } } # Namenfilter if ($Database.Count -gt 0) { $filtered = $filtered | Where-Object { $dbName = $_.Name $match = $false foreach ($pattern in $Database) { if ($dbName -like $pattern) { $match = $true } } $match } } # Ausschluesse if ($ExcludeDatabase.Count -gt 0) { $filtered = $filtered | Where-Object { $dbName = $_.Name $exclude = $false foreach ($pattern in $ExcludeDatabase) { if ($dbName -like $pattern) { $exclude = $true } } -not $exclude } } $dbList = @($filtered) if ($dbList.Count -eq 0) { Invoke-sqmLogging -Message ("[$instance] Keine Datenbanken nach Filterung gefunden.") -FunctionName $functionName -Level "WARNING" continue } Invoke-sqmLogging -Message ("[$instance] $($dbList.Count) Datenbank(en) zu pruefen.") -FunctionName $functionName -Level "INFO" # ------------------------------------------------------------------- # 3. Pro Datenbank Owner pruefen und setzen # ------------------------------------------------------------------- $instanceResults = [System.Collections.Generic.List[PSCustomObject]]::new() $changedCount = 0 $skippedCount = 0 $failedCount = 0 foreach ($db in $dbList) { $dbName = $db.Name $currentOwner = $db.Owner $rowResult = [PSCustomObject]@{ SqlInstance = $instance DatabaseName = $dbName OldOwner = $currentOwner NewOwner = $targetLogin Status = 'Unknown' Message = '' } # Bereits korrekt? if ($currentOwner -eq $targetLogin -and -not $Force) { $rowResult.Status = 'Skipped' $rowResult.Message = "Owner bereits '$targetLogin' - keine aenderung." $skippedCount++ $instanceResults.Add($rowResult) continue } $action = "Owner von '$dbName' von '$currentOwner' auf '$targetLogin' setzen" if ($PSCmdlet.ShouldProcess("[$instance] $dbName", $action)) { try { $alterSql = "ALTER AUTHORIZATION ON DATABASE::[$($dbName -replace '\]', '\]\]')] TO [$($targetLogin -replace '\]', '\]\]')]" Invoke-DbaQuery @connParams -Database 'master' -Query $alterSql -ErrorAction Stop $rowResult.Status = 'OK' $rowResult.Message = "Owner erfolgreich auf '$targetLogin' gesetzt." $changedCount++ Invoke-sqmLogging -Message ("[" + $instance + "] " + $dbName + ": Owner " + $currentOwner + " -> " + $targetLogin) -FunctionName $functionName -Level "INFO" } catch { $rowResult.Status = 'Failed' $rowResult.Message = $_.Exception.Message $failedCount++ Invoke-sqmLogging -Message ("[" + $instance + "] " + $dbName + ": Fehler beim Owner-Setzen: " + $_.Exception.Message) -FunctionName $functionName -Level "ERROR" } } else { $rowResult.Status = 'WhatIf' $rowResult.Message = "WhatIf: Keine aenderung durchgefuehrt." } $instanceResults.Add($rowResult) } # ------------------------------------------------------------------- # 4. Protokoll schreiben # ------------------------------------------------------------------- $changed = $instanceResults | Where-Object { $_.Status -eq 'OK' } if ($changed -and $PSCmdlet.ShouldProcess($instance, "Protokoll schreiben")) { if (-not (Test-Path $OutputPath)) { New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null } $safeInst = $instance -replace '\\', '_' $stamp = Get-Date -Format 'yyyyMMdd_HHmsqm' $csvFile = Join-Path $OutputPath ("OwnerChange_" + $safeInst + "_" + $stamp + ".csv") $instanceResults | Export-Csv -Path $csvFile -NoTypeInformation -Encoding UTF8 -Force Copy-sqmToCentralPath -Path @($csvFile) Invoke-sqmLogging -Message ("[$instance] Protokoll: $csvFile") -FunctionName $functionName -Level "INFO" } $summary = "[$instance] Geaendert: $changedCount, uebersprungen: $skippedCount, Fehler: $failedCount" Invoke-sqmLogging -Message $summary -FunctionName $functionName -Level "INFO" Write-Verbose $summary foreach ($r in $instanceResults) { $allResults.Add($r) } } catch { $errMsg = "Fehler auf '$instance': " + $_.Exception.Message Invoke-sqmLogging -Message $errMsg -FunctionName $functionName -Level "ERROR" if ($EnableException) { throw } if (-not $ContinueOnError) { Write-Error $errMsg; return } Write-Warning $errMsg } } } end { Invoke-sqmLogging -Message ($functionName + " abgeschlossen. " + $allResults.Count + " Datenbank(en) verarbeitet.") -FunctionName $functionName -Level "INFO" return $allResults } } |