Public/Set-mssDatabaseOwner.TempPoint.ps1
|
<#
.SYNOPSIS Setzt den Eigentümer (Owner) einer oder mehrerer Datenbanken auf einen einheitlichen Login. .DESCRIPTION Prüft 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 (unabhängig vom tatsächlichen sa-Namen, der per Obfuskation umbenannt sein kann) oder einen beliebigen anderen Login. Ablauf pro Datenbank: 1. Aktuellen Owner lesen 2. Prüfen ob Änderung notwendig (bereits korrekt → überspringen) 3. Prüfen ob Ziel-Login auf der Instanz existiert 4. ALTER AUTHORIZATION ON DATABASE::<Name> TO <Login> ausführen 5. Ergebnis protokollieren Gibt für jede Datenbank ein Statusobjekt zurück: Status = OK / Skipped / Failed / NotFound .PARAMETER SqlInstance SQL Server-Instanz(en). Pipeline-fähig. Standard: aktueller Computername. .PARAMETER SqlCredential PSCredential für 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 Eigentümer gesetzt wird. Standard: sa-Account (wird automatisch anhand der SID 0x01 ermittelt, unabhängig 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 für das Änderungsprotokoll. Standard: aus Modulkonfiguration. .PARAMETER ContinueOnError Bei Fehler auf einer Instanz fortfahren. Standard: $false. .PARAMETER EnableException Ausnahmen sofort auslösen. .EXAMPLE # Sa-Account auf allen Benutzerdatenbanken setzen Set-mssDatabaseOwner -SqlInstance "SQL01" .EXAMPLE # Bestimmte Datenbanken mit eigenem Login Set-mssDatabaseOwner -SqlInstance "SQL01" -Database "Prod*" -OwnerLogin "svc_sqlowner" .EXAMPLE # Pipeline über mehrere Instanzen 'SQL01','SQL02' | Set-mssDatabaseOwner .EXAMPLE # WhatIf - nur zeigen was geändert würde Set-mssDatabaseOwner -SqlInstance "SQL01" -WhatIf .NOTES Erfordert: dbatools, Invoke-mssLogging, Get-mssDefaultOutputPath, Copy-mssToCentralPath Benötigt: sysadmin oder ALTER ANY DATABASE auf der Instanz. Der sa-Account wird über SID 0x01 ermittelt - funktioniert auch nach Umbenennung. Systemdatenbanken: master/model/msdb können Owner-Änderungen erhalten, tempdb nie. #> function Set-mssDatabaseOwner { [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-mssLogging -Message $msg -FunctionName $functionName -Level "ERROR" throw $msg } if (-not $OutputPath) { $OutputPath = Get-mssDefaultOutputPath } Invoke-mssLogging -Message ("Starte " + $functionName) -FunctionName $functionName -Level "INFO" } process { foreach ($instance in $SqlInstance) { Invoke-mssLogging -Message ("[$instance] Verarbeite Instanz") -FunctionName $functionName -Level "INFO" try { $connParams = @{ SqlInstance = $instance SqlCredential = $SqlCredential ErrorAction = 'Stop' } # ------------------------------------------------------------------- # 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." } } # Prüfen 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-mssLogging -Message ("[$instance] Ziel-Owner: $targetLogin") -FunctionName $functionName -Level "INFO" # ------------------------------------------------------------------- # 2. Datenbanken ermitteln # ------------------------------------------------------------------- $dbGetParams = @{ SqlInstance = $instance SqlCredential = $SqlCredential ErrorAction = 'Stop' } $allDbs = Get-DbaDatabase @dbGetParams # tempdb immer ausschließen $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 } } # Ausschlüsse 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-mssLogging -Message ("[$instance] Keine Datenbanken nach Filterung gefunden.") -FunctionName $functionName -Level "WARNING" continue } Invoke-mssLogging -Message ("[$instance] $($dbList.Count) Datenbank(en) zu prüfen.") -FunctionName $functionName -Level "INFO" # ------------------------------------------------------------------- # 3. Pro Datenbank Owner prüfen 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 Änderung." $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-mssLogging -Message ("[$instance] $dbName: Owner $currentOwner → $targetLogin") -FunctionName $functionName -Level "INFO" } catch { $rowResult.Status = 'Failed' $rowResult.Message = $_.Exception.Message $failedCount++ Invoke-mssLogging -Message ("[$instance] $dbName: Fehler beim Owner-Setzen: " + $_.Exception.Message) -FunctionName $functionName -Level "ERROR" } } else { $rowResult.Status = 'WhatIf' $rowResult.Message = "WhatIf: Keine Änderung durchgeführt." } $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_HHmmss' $csvFile = Join-Path $OutputPath ("OwnerChange_" + $safeInst + "_" + $stamp + ".csv") $instanceResults | Export-Csv -Path $csvFile -NoTypeInformation -Encoding UTF8 -Force Copy-mssToCentralPath -Path @($csvFile) Invoke-mssLogging -Message ("[$instance] Protokoll: $csvFile") -FunctionName $functionName -Level "INFO" } $summary = "[$instance] Geändert: $changedCount, Übersprungen: $skippedCount, Fehler: $failedCount" Invoke-mssLogging -Message $summary -FunctionName $functionName -Level "INFO" Write-Verbose $summary foreach ($r in $instanceResults) { $allResults.Add($r) } } catch { $errMsg = "Fehler auf '$instance': " + $_.Exception.Message Invoke-mssLogging -Message $errMsg -FunctionName $functionName -Level "ERROR" if ($EnableException) { throw } if (-not $ContinueOnError) { Write-Error $errMsg; return } Write-Warning $errMsg } } } end { Invoke-mssLogging -Message ($functionName + " abgeschlossen. " + $allResults.Count + " Datenbank(en) verarbeitet.") -FunctionName $functionName -Level "INFO" return $allResults } } |