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
    }
}