Public/Invoke-sqmTempSysadminAction.ps1

<#
.SYNOPSIS
    Fuehrt die eigentliche sysadmin-Vergabe bzw. -Entziehung aus und loescht optional
    den aufrufenden SQL-Agent-Job nach Erfolg (selbstloeschender Job).
 
.DESCRIPTION
    Wird von den durch Grant-sqmTemporarySysadmin erzeugten Agent-Jobs aufgerufen,
    kann aber auch manuell verwendet werden (z. B. fuer einen vorzeitigen Entzug).
 
    Aktion:
      Grant -> ALTER SERVER ROLE [sysadmin] ADD MEMBER (nur wenn noch nicht Mitglied)
      Revoke -> ALTER SERVER ROLE [sysadmin] DROP MEMBER (nur wenn Mitglied)
 
    Protokolliert jede Aktion in das Modul-Logfile (Invoke-sqmLogging) UND in das
    Windows Application Event Log (Source 'sqmSQLTool') - inklusive Auftragsnummer.
 
    Ist -JobName gesetzt und die Aktion erfolgreich, wird der Job per sp_delete_job
    geloescht. Bei einem Fehler wird der Job NICHT geloescht und ein Fehler geworfen,
    damit der Fehlschlag in der Job-Historie sichtbar bleibt.
 
.PARAMETER SqlInstance
    SQL Server Instanz. Default: lokaler Computername.
 
.PARAMETER SqlCredential
    PSCredential fuer die SQL-Verbindung (SQL-Authentifizierung).
 
.PARAMETER Login
    Betroffener Login / Server-Principal (Windows-, SQL- oder Gruppen-Login).
 
.PARAMETER Action
    'Grant' oder 'Revoke'.
 
.PARAMETER TicketNumber
    Optionale Auftrags-/Ticketnummer fuer die Protokollierung.
 
.PARAMETER JobName
    Optional: Name des aufrufenden Agent-Jobs. Wird nach erfolgreicher Aktion
    geloescht (Selbstloeschung).
 
.EXAMPLE
    Invoke-sqmTempSysadminAction -SqlInstance SQL01 -Login 'DOM\u.maier' -Action Revoke
 
.EXAMPLE
    # Vorzeitiger manueller Entzug inkl. Entfernen des geplanten Revoke-Jobs:
    Invoke-sqmTempSysadminAction -Login 'DOM\u.maier' -Action Revoke -JobName 'sqmTempSysadmin_DOM_u.maier_Revoke'
 
.NOTES
    Requires: dbatools, Invoke-sqmLogging. Ausfuehrender Kontext braucht sysadmin/
    ALTER auf der Serverrolle. Im Job-Kontext laeuft dies unter dem SQL-Agent-Dienstkonto.
#>

function Invoke-sqmTempSysadminAction
{
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param (
        [Parameter(Mandatory = $false)]
        [string]$SqlInstance = $env:COMPUTERNAME,
        [Parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]$SqlCredential,
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Login,
        [Parameter(Mandatory = $true)]
        [ValidateSet('Grant', 'Revoke')]
        [string]$Action,
        [Parameter(Mandatory = $false)]
        [string]$TicketNumber,
        [Parameter(Mandatory = $false)]
        [string]$JobName
    )

    begin
    {
        $functionName = $MyInvocation.MyCommand.Name
    }

    process
    {
        $connParams = @{ SqlInstance = $SqlInstance }
        if ($SqlCredential) { $connParams['SqlCredential'] = $SqlCredential }

        # Quoting: [login] (Bracket-escape) und N'login' (String-escape)
        $loginBracket = '[' + ($Login -replace '\]', ']]') + ']'
        $loginLit     = $Login -replace "'", "''"
        $ticketText   = if ($TicketNumber) { $TicketNumber } else { '(keine)' }

        try
        {
            if ($Action -eq 'Grant')
            {
                $sql = @"
IF IS_SRVROLEMEMBER('sysadmin', N'$loginLit') = 0
    ALTER SERVER ROLE [sysadmin] ADD MEMBER $loginBracket;
"@

            }
            else
            {
                $sql = @"
IF IS_SRVROLEMEMBER('sysadmin', N'$loginLit') = 1
    ALTER SERVER ROLE [sysadmin] DROP MEMBER $loginBracket;
"@

            }

            Invoke-DbaQuery @connParams -Database master -Query $sql -EnableException -ErrorAction Stop

            $msg = "sysadmin $Action fuer Login '$Login' auf '$SqlInstance' erfolgreich. Auftragsnummer: $ticketText."
            Invoke-sqmLogging -Message $msg -FunctionName $functionName -Level "INFO"
            Write-sqmEventLogSafe -Message $msg -EntryType 'Information' -EventId $(if ($Action -eq 'Grant') { 9001 } else { 9002 })

            # Selbstloeschung des aufrufenden Jobs - NUR bei Erfolg
            if ($JobName)
            {
                $jobLit = $JobName -replace "'", "''"
                $delSql = "IF EXISTS (SELECT 1 FROM msdb.dbo.sysjobs WHERE name = N'$jobLit') EXEC msdb.dbo.sp_delete_job @job_name = N'$jobLit';"
                Invoke-DbaQuery @connParams -Database msdb -Query $delSql -EnableException -ErrorAction Stop
                Invoke-sqmLogging -Message "Selbstloeschender Job '$JobName' entfernt." -FunctionName $functionName -Level "INFO"
            }

            return [PSCustomObject]@{
                SqlInstance  = $SqlInstance
                Login        = $Login
                Action       = $Action
                TicketNumber = $TicketNumber
                JobDeleted   = [bool]$JobName
                Status       = 'Success'
                Message      = $msg
                Timestamp    = Get-Date
            }
        }
        catch
        {
            $errMsg = "sysadmin $Action fuer Login '$Login' auf '$SqlInstance' FEHLGESCHLAGEN (Auftragsnummer: $ticketText): $($_.Exception.Message)"
            Invoke-sqmLogging -Message $errMsg -FunctionName $functionName -Level "ERROR"
            Write-sqmEventLogSafe -Message $errMsg -EntryType 'Error' -EventId 9009
            # Fehler werfen, damit ein aufrufender Agent-Job als FEHLGESCHLAGEN endet und sich NICHT loescht.
            throw $errMsg
        }
    }
}

# -------------------------------------------------------------------------------
# Private Hilfsfunktion: schreibt ins Windows Application Event Log unter der
# Source 'sqmSQLTool' (von Install.ps1 registriert). Schlaegt das Schreiben fehl
# (Source fehlt / keine Rechte), wird der Fehler ignoriert - das Logfile bleibt
# massgeblich.
# -------------------------------------------------------------------------------
function Write-sqmEventLogSafe
{
    [CmdletBinding()]
    param (
        [string]$Message,
        [ValidateSet('Information', 'Warning', 'Error')]
        [string]$EntryType = 'Information',
        [int]$EventId = 9000
    )
    try
    {
        if (-not [System.Diagnostics.EventLog]::SourceExists('sqmSQLTool'))
        {
            # Anlegen erfordert Adminrechte - bei Fehlschlag still ueberspringen.
            New-EventLog -LogName Application -Source 'sqmSQLTool' -ErrorAction Stop
        }
        Write-EventLog -LogName Application -Source 'sqmSQLTool' -EntryType $EntryType -EventId $EventId -Message $Message -ErrorAction Stop
    }
    catch
    {
        Write-Verbose "Event-Log-Eintrag uebersprungen: $($_.Exception.Message)"
    }
}