tests/Unit/Public/SysadminRecovery.Tests.ps1
|
#Requires -Modules Pester <# .SYNOPSIS Unit Tests fuer die sysadmin-Funktionen: Invoke-sqmTempSysadminAction, Grant-sqmTemporarySysadmin und Restore-sqmSysadminAccess. .DESCRIPTION Diese Funktionen vergeben bzw. entziehen die maechtigste Serverrolle. Getestet wird deshalb vor allem, was NICHT passieren darf: kein stilles Erfolgsergebnis ohne Mitgliedschaft, keine dauerhaft deaktivierten Server-Trigger, kein Notfall-Zugriff bei vorhandenem sysadmin, kein Login-Drop mit weiteren Rollen, und der Dienst wird nach der Notfallprozedur immer wieder normal gestartet. Invoke-DbaQuery wird durch einen kleinen Zustandsautomaten ersetzt (Login, Rollen, Trigger). #> BeforeAll { . "$PSScriptRoot\..\..\..\tests\TestHelpers.ps1" Import-sqmTestModule InModuleScope sqmSQLTool { $script:dbatoolsAvailable = $true } } AfterAll { Remove-Variable -Name SqmQueries, SqmState, SqmCalls, SqmTick -Scope Global -ErrorAction SilentlyContinue if (Get-Module sqmSQLTool) { Remove-Module sqmSQLTool -Force } $env:MSSQLTOOLS_SKIP_AUTO_UPDATE = $null } Describe 'Invoke-sqmTempSysadminAction' { BeforeEach { $global:SqmQueries = [System.Collections.Generic.List[string]]::new() $global:SqmState = @{ LoginExists = $true; Direct = $false; Effective = $false; OtherRoles = 0; Triggers = @('trg_audit', 'trg]odd'); AlterWorks = $true } Mock -ModuleName sqmSQLTool Invoke-sqmLogging { } Mock -ModuleName sqmSQLTool Write-sqmEventLogSafe { } Mock -ModuleName sqmSQLTool Get-sqmConfig { $null } Mock -ModuleName sqmSQLTool Set-sqmSqlPolicyState { [pscustomobject]@{ Status = 'Success' } } Mock -ModuleName sqmSQLTool Invoke-DbaQuery { $global:SqmQueries.Add($Query) $st = $global:SqmState switch -Wildcard ($Query) { '*FROM sys.server_triggers*' { $st.Triggers | ForEach-Object { [pscustomobject]@{ name = $_ } } } 'DISABLE TRIGGER*' { } 'ENABLE TRIGGER*' { } '*COUNT(*) AS Cnt FROM sys.server_principals*' { [pscustomobject]@{ Cnt = [int]$st.LoginExists } } '*AS IsEffective, CASE*' { [pscustomobject]@{ IsEffective = [int]$st.Effective; IsDirect = [int]$st.Direct } } 'CREATE LOGIN*' { $st.LoginExists = $true } '*ADD MEMBER*' { if ($st.AlterWorks) { $st.Direct = $true; $st.Effective = $true } } '*DROP MEMBER*' { $st.Direct = $false; $st.Effective = $false } '*AS IsDirectMember*' { [pscustomobject]@{ IsDirectMember = [int]$st.Direct } } '*AS IsEffective;' { [pscustomobject]@{ IsEffective = [int]$st.Effective } } '*r.name <> ''public''*' { [pscustomobject]@{ Cnt = $st.OtherRoles } } '*DROP LOGIN*' { $st.LoginExists = $false } '*sp_delete_job*' { } } } } Context 'Grant' { It 'vergibt die Rolle an ein vorhandenes Login und bestaetigt die direkte Mitgliedschaft' { $r = Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Grant $r.Status | Should -Be 'Success' $r.LoginCreated | Should -BeFalse $global:SqmQueries | Where-Object { $_ -like '*ALTER SERVER ROLE [[]sysadmin] ADD MEMBER [[]DOM\user]*' } | Should -Not -BeNullOrEmpty $global:SqmQueries | Where-Object { $_ -like 'CREATE LOGIN*' } | Should -BeNullOrEmpty } It 'legt ein fehlendes AD-Login mit -CreateLoginIfMissing an' { $global:SqmState.LoginExists = $false $r = Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\neu' -Action Grant -CreateLoginIfMissing $r.LoginCreated | Should -BeTrue $global:SqmQueries | Should -Contain 'CREATE LOGIN [DOM\neu] FROM WINDOWS;' } It 'verweigert ein fehlendes Login ohne -CreateLoginIfMissing' { $global:SqmState.LoginExists = $false { Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\neu' -Action Grant } | Should -Throw '*CreateLoginIfMissing*' $global:SqmQueries | Where-Object { $_ -like '*ADD MEMBER*' } | Should -BeNullOrEmpty } It 'legt kein SQL-Login ohne Domaene automatisch an' { $global:SqmState.LoginExists = $false { Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'appuser' -Action Grant -CreateLoginIfMissing } | Should -Throw '*Windows-/AD-Logins*' $global:SqmQueries | Where-Object { $_ -like 'CREATE LOGIN*' } | Should -BeNullOrEmpty } It 'meldet einen Fehler, wenn die Mitgliedschaft nach ALTER SERVER ROLE nicht besteht' { $global:SqmState.AlterWorks = $false { Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Grant } | Should -Throw '*KEIN direktes Mitglied*' } It 'escaped ] im Login und Hochkommas im Literal' { Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login "DOM\o]'x" -Action Grant | Out-Null ($global:SqmQueries -join "`n") | Should -Match ([regex]::Escape('ADD MEMBER [DOM\o]]''x]')) ($global:SqmQueries -join "`n") | Should -Match ([regex]::Escape("m.name = N'DOM\o]''x'")) } It 'unterstuetzt dbcreator und nutzt dann diese Rolle' { Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Grant -Role dbcreator | Out-Null ($global:SqmQueries | Where-Object { $_ -like '*ADD MEMBER*' }) | Should -Match '\[dbcreator\]' } It 'lehnt unbekannte Rollen ab' { { Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Grant -Role securityadmin } | Should -Throw } } Context 'Server-Trigger und Policy' { It 'deaktiviert Server-Trigger vor der Aenderung und aktiviert sie danach wieder' { Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Grant | Out-Null $q = @($global:SqmQueries) $disable = $q.IndexOf('DISABLE TRIGGER [trg_audit] ON ALL SERVER;') $alter = ($q | Where-Object { $_ -like '*ADD MEMBER*' } | Select-Object -First 1) $enable = $q.IndexOf('ENABLE TRIGGER [trg_audit] ON ALL SERVER;') $disable | Should -BeGreaterOrEqual 0 $enable | Should -BeGreaterThan $q.IndexOf($alter) $q.IndexOf($alter) | Should -BeGreaterThan $disable $q | Should -Contain 'DISABLE TRIGGER [trg]]odd] ON ALL SERVER;' $q | Should -Contain 'ENABLE TRIGGER [trg]]odd] ON ALL SERVER;' } It 'aktiviert Trigger auch dann wieder, wenn die Vergabe scheitert' { $global:SqmState.AlterWorks = $false { Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Grant } | Should -Throw $global:SqmQueries | Should -Contain 'ENABLE TRIGGER [trg_audit] ON ALL SERVER;' } It '-DisableServerTriggers $false laesst Trigger in Ruhe' { Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Grant -DisableServerTriggers $false | Out-Null $global:SqmQueries | Where-Object { $_ -like '*TRIGGER*' } | Should -BeNullOrEmpty } It 'deaktiviert die konfigurierte Policy und stellt sie danach wieder her' { Mock -ModuleName sqmSQLTool Get-sqmConfig { 'PolicyX' } -ParameterFilter { $Key -eq 'DefaultPolicy' } Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Grant | Out-Null Should -Invoke -ModuleName sqmSQLTool Set-sqmSqlPolicyState -Times 1 -Exactly -ParameterFilter { $State -eq 'Disable' -and $Policy -eq 'PolicyX' } Should -Invoke -ModuleName sqmSQLTool Set-sqmSqlPolicyState -Times 1 -Exactly -ParameterFilter { $State -eq 'Enable' } } } Context 'Revoke' { BeforeEach { $global:SqmState.Direct = $true; $global:SqmState.Effective = $true } It 'entzieht die direkte Mitgliedschaft' { $r = Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Revoke $r.Status | Should -Be 'Success' $global:SqmState.Direct | Should -BeFalse ($global:SqmQueries | Where-Object { $_ -like '*DROP MEMBER*' }) | Should -Not -BeNullOrEmpty } It '-RemoveLogin entfernt das Login, wenn es keine weiteren Rollen hat' { $r = Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Revoke -RemoveLogin $r.LoginRemoved | Should -BeTrue ($global:SqmQueries | Where-Object { $_ -like '*DROP LOGIN [[]DOM\user]*' }) | Should -Not -BeNullOrEmpty } It '-RemoveLogin behaelt das Login, wenn es noch weiteren Serverrollen angehoert' { $global:SqmState.OtherRoles = 1 $r = Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Revoke -RemoveLogin $r.LoginRemoved | Should -BeFalse ($global:SqmQueries | Where-Object { $_ -like '*DROP LOGIN*' }) | Should -BeNullOrEmpty } It 'loescht den angegebenen Agent-Job (Hochkommas verdoppelt)' { $r = Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Revoke -JobName "Job'1" $r.JobDeleted | Should -BeTrue ($global:SqmQueries | Where-Object { $_ -like '*sp_delete_job*' }) | Should -Match "N'Job''1'" } It 'ohne -JobName wird kein Job geloescht' { Invoke-sqmTempSysadminAction -SqlInstance 'SQL01' -Login 'DOM\user' -Action Revoke | Out-Null ($global:SqmQueries | Where-Object { $_ -like '*sp_delete_job*' }) | Should -BeNullOrEmpty } } } Describe 'Grant-sqmTemporarySysadmin' { BeforeEach { $global:SqmCalls = [System.Collections.Generic.List[string]]::new() $global:SqmState = @{ Replicas = @('SQL01', 'SQL02', 'SQL03'); LoginCount = 1; JobExists = $false } Mock -ModuleName sqmSQLTool Invoke-sqmLogging { } Mock -ModuleName sqmSQLTool Invoke-sqmTempSysadminAction { $global:SqmCalls.Add("Action:${Action}:$("$SqlInstance"):${Login}:${Role}") [pscustomobject]@{ LoginCreated = $false } } Mock -ModuleName sqmSQLTool Get-DbaAgentJob { if ($global:SqmState.JobExists) { [pscustomobject]@{ Name = "$Job" } } } Mock -ModuleName sqmSQLTool Remove-DbaAgentJob { $global:SqmCalls.Add("RemoveJob:$Job") } Mock -ModuleName sqmSQLTool New-DbaAgentJob { $global:SqmCalls.Add("NewJob:$("$SqlInstance"):${Job}") } Mock -ModuleName sqmSQLTool New-DbaAgentJobStep { $global:SqmCalls.Add("Step:$Command") } Mock -ModuleName sqmSQLTool Invoke-DbaQuery { if ($Query -like '*availability_replicas*') { $global:SqmState.Replicas | ForEach-Object { [pscustomobject]@{ replica_server_name = $_ } } } elseif ($Query -like '*COUNT(*) AS Cnt*') { [pscustomobject]@{ Cnt = $global:SqmState.LoginCount } } } } It 'vergibt sofort und legt je Replikat einen Entzugs-Job an' { $r = @(Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\user' -Days 7 -TicketNumber 'CHG1') $r.Status | Should -Be @('Success', 'Success', 'Success') $r.SqlInstance | Should -Be @('SQL01', 'SQL02', 'SQL03') ($global:SqmCalls | Where-Object { $_ -like 'Action:Grant:*' }).Count | Should -Be 3 ($global:SqmCalls | Where-Object { $_ -like 'NewJob:*' }).Count | Should -Be 3 $r[0].Immediate | Should -BeTrue $r[0].GrantJob | Should -BeNullOrEmpty $r[0].RevocationTime | Should -BeGreaterThan (Get-Date).AddDays(6.9) } It '-PrimaryOnly und -SkipSecondaryServers begrenzen die Zielserver' { (@(Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\user' -Days 1 -PrimaryOnly)).SqlInstance | Should -Be @('SQL01') $global:SqmCalls.Clear() (@(Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\user' -Days 1 -SkipSecondaryServers 'SQL02')).SqlInstance | Should -Be @('SQL01', 'SQL03') } It 'faellt auf die eigene Instanz zurueck, wenn keine AG-Replikate ermittelt werden' { $global:SqmState.Replicas = @() (@(Grant-sqmTemporarySysadmin -SqlInstance 'SQL09' -Login 'DOM\user' -Days 1)).SqlInstance | Should -Be @('SQL09') } It 'der Entzugs-Job ruft Revoke mit Login, Rolle, Ticket und Jobname auf' { Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\user' -Days 2 -PrimaryOnly -TicketNumber "CHG'1" -Role dbcreator | Out-Null $cmd = $global:SqmCalls | Where-Object { $_ -like 'Step:*' } | Select-Object -First 1 $cmd | Should -Match '-Action Revoke' $cmd | Should -Match "-Login 'DOM\\user'" $cmd | Should -Match "-Role 'dbcreator'" $cmd | Should -Match "-TicketNumber 'CHG''1'" $cmd | Should -Match '-JobName ''sqmTempDbcreator_' } It 'ein neu angelegtes Login wird beim Entzug mit -RemoveLogin entfernt' { Mock -ModuleName sqmSQLTool Invoke-sqmTempSysadminAction { [pscustomobject]@{ LoginCreated = $true } } Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\neu' -Days 1 -PrimaryOnly | Out-Null ($global:SqmCalls | Where-Object { $_ -like 'Step:*' } | Select-Object -First 1) | Should -Match '-RemoveLogin' } It 'mit -StartDate in der Zukunft werden Vergabe UND Entzug als Jobs geplant, ohne sofort zu vergeben' { $start = (Get-Date).AddDays(3) $r = Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\user' -Days 2 -PrimaryOnly -StartDate $start $r.Immediate | Should -BeFalse $r.GrantJob | Should -Match '_Grant$' $r.RevokeJob | Should -Match '_Revoke$' ($global:SqmCalls | Where-Object { $_ -like 'Action:*' }) | Should -BeNullOrEmpty ($global:SqmCalls | Where-Object { $_ -like 'NewJob:*' }).Count | Should -Be 2 $r.RevocationTime | Should -BeGreaterThan $start.AddDays(1.9) } It '-WhatIf vergibt nichts und legt keine Jobs an' { $r = @(Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\user' -Days 1 -WhatIf) $r.Status | Should -Be @('WhatIf', 'WhatIf', 'WhatIf') $global:SqmCalls | Should -BeNullOrEmpty } It 'bricht pro Server ab, wenn der Job schon existiert, ausser mit -Force' { $global:SqmState.JobExists = $true $r = Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\user' -Days 1 -PrimaryOnly $r.Status | Should -Be 'Error' $r.Message | Should -Match 'existiert' $global:SqmCalls.Clear() $r2 = Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\user' -Days 1 -PrimaryOnly -Force $r2.Status | Should -Be 'Success' ($global:SqmCalls | Where-Object { $_ -like 'RemoveJob:*' }).Count | Should -Be 1 } It 'meldet Error und vergibt nichts, wenn die Login-Pruefung scheitert' { Mock -ModuleName sqmSQLTool Invoke-DbaQuery { if ($Query -like '*COUNT(*) AS Cnt*') { throw 'nicht erreichbar' } } $r = Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\user' -Days 1 -PrimaryOnly $r.Status | Should -Be 'Error' $r.Message | Should -Match 'Login-Pruefung fehlgeschlagen' $global:SqmCalls | Should -BeNullOrEmpty } It 'validiert -Days und -Role' { { Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\user' -Days 0 } | Should -Throw { Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\user' -Days 3651 } | Should -Throw { Grant-sqmTemporarySysadmin -SqlInstance 'SQL01' -Login 'DOM\user' -Days 1 -Role 'securityadmin' } | Should -Throw } } Describe 'Restore-sqmSysadminAccess' { BeforeEach { $global:SqmCalls = [System.Collections.Generic.List[string]]::new() $global:SqmQueries = [System.Collections.Generic.List[string]]::new() $global:SqmState = @{ Probe = 'fail'; Batch = 'ok'; ResetFails = $false } $global:SqmTick = 0 Mock -ModuleName sqmSQLTool Invoke-sqmLogging { } Mock -ModuleName sqmSQLTool Write-sqmEventLogSafe { $global:SqmCalls.Add("Event:$EventId") } Mock -ModuleName sqmSQLTool Start-Sleep { } Mock -ModuleName sqmSQLTool Test-Path { $false } -ParameterFilter { $Path -like 'HKLM:\Cluster*' } Mock -ModuleName sqmSQLTool Stop-DbaService { $global:SqmCalls.Add('Stop') } Mock -ModuleName sqmSQLTool Start-DbaService { $global:SqmCalls.Add('Start') } Mock -ModuleName sqmSQLTool Set-DbaStartupParameter { $global:SqmCalls.Add("Startup:SingleUser=$([bool]$SingleUser)") if (-not $SingleUser -and $global:SqmState.ResetFails) { throw 'Registry gesperrt' } } Mock -ModuleName sqmSQLTool Invoke-DbaQuery { $global:SqmQueries.Add($Query) if ($Query -like '*IS_SRVROLEMEMBER*' -and $Query -notlike '*DECLARE*') { if ($global:SqmState.Probe -eq 'sysadmin') { [pscustomobject]@{ IsSysadmin = 1 } } elseif ($global:SqmState.Probe -eq 'user') { [pscustomobject]@{ IsSysadmin = 0 } } else { throw 'Anmeldung fehlgeschlagen' } } else { $global:SqmCalls.Add("Batch:pwd=$($SqlParameter.pwd.Length)") switch ($global:SqmState.Batch) { 'ok' { [pscustomobject]@{ LoginExisted = 0; TriggersReEnabled = 1; IsSysadminNow = 1 } } 'nomember' { [pscustomobject]@{ LoginExisted = 0; TriggersReEnabled = 0; IsSysadminNow = 0 } } 'down' { throw 'Server startet noch' } } } } } It 'stoppt den Dienst, startet Single-User, fuehrt den Batch aus und startet den Dienst wieder normal' { $r = Restore-sqmSysadminAccess -SqlInstance 'SQL01' -Login 'DOM\rescue' -Confirm:$false $r.Status | Should -Be 'Success' ($global:SqmCalls | Where-Object { $_ -notlike 'Event:*' }) | Should -Be @( 'Stop', 'Startup:SingleUser=True', 'Start', 'Batch:pwd=24', 'Stop', 'Startup:SingleUser=False', 'Start') } It 'erzeugt ohne -Password ein 24-stelliges Passwort und liefert es zurueck' { $r = Restore-sqmSysadminAccess -SqlInstance 'SQL01' -Login 'DOM\rescue' -Confirm:$false $r.Password | Should -BeOfType [securestring] $r.PasswordPlainText.Length | Should -Be 24 } It 'verwendet ein uebergebenes Passwort' { $pw = ConvertTo-SecureString 'Aa1!bbbbbbbbbbbbbbbbbbbb' -AsPlainText -Force $r = Restore-sqmSysadminAccess -SqlInstance 'SQL01' -Login 'DOM\rescue' -Password $pw -Confirm:$false $r.PasswordPlainText | Should -Be 'Aa1!bbbbbbbbbbbbbbbbbbbb' $global:SqmCalls | Should -Contain 'Batch:pwd=24' } It 'der Batch escaped ] im Login und uebergibt das Passwort nur als Parameter' { Restore-sqmSysadminAccess -SqlInstance 'SQL01' -Login "DOM\a]b'c" -Confirm:$false | Out-Null $batch = $global:SqmQueries | Where-Object { $_ -like '*DECLARE @triggerNames*' } $batch | Should -Match ([regex]::Escape('ALTER SERVER ROLE sysadmin ADD MEMBER [DOM\a]]b''c];')) $batch | Should -Match "N'DOM\\a]b''c'" $batch | Should -Match 'PASSWORD = @pwd' } It 'verweigert die Notfallprozedur, wenn das aktuelle Konto schon sysadmin ist' { $global:SqmState.Probe = 'sysadmin' { Restore-sqmSysadminAccess -SqlInstance 'SQL01' -Login 'DOM\rescue' -Confirm:$false } | Should -Throw '*bereits sysadmin*' $global:SqmCalls | Should -BeNullOrEmpty } It '-Force umgeht die sysadmin-Pruefung' { $global:SqmState.Probe = 'sysadmin' (Restore-sqmSysadminAccess -SqlInstance 'SQL01' -Login 'DOM\rescue' -Force -Confirm:$false).Status | Should -Be 'Success' } It 'verweigert auf einem Cluster-Knoten, ausser mit -Force' { Mock -ModuleName sqmSQLTool Test-Path { $true } -ParameterFilter { $Path -like 'HKLM:\Cluster*' } $inst = $env:COMPUTERNAME { Restore-sqmSysadminAccess -SqlInstance $inst -Login 'DOM\rescue' -Confirm:$false } | Should -Throw '*Failover-Cluster*' $global:SqmCalls | Should -BeNullOrEmpty } It '-WhatIf stoppt keinen Dienst' { $r = Restore-sqmSysadminAccess -SqlInstance 'SQL01' -Login 'DOM\rescue' -WhatIf $r | Should -BeNullOrEmpty $global:SqmCalls | Should -BeNullOrEmpty } It 'stellt den normalen Start auch dann wieder her, wenn der Batch scheitert' { $global:SqmState.Batch = 'nomember' { Restore-sqmSysadminAccess -SqlInstance 'SQL01' -Login 'DOM\rescue' -Confirm:$false } | Should -Throw '*KEIN direktes Mitglied*' $calls = $global:SqmCalls | Where-Object { $_ -notlike 'Event:*' } $calls[-3..-1] | Should -Be @('Stop', 'Startup:SingleUser=False', 'Start') $global:SqmCalls | Should -Contain 'Event:9021' } It 'gibt nach dem Timeout auf und stellt den Dienst trotzdem wieder her' { $global:SqmState.Batch = 'down' Mock -ModuleName sqmSQLTool Get-Date { [datetime]::new(2030, 1, 1).AddMinutes($global:SqmTick++) } { Restore-sqmSysadminAccess -SqlInstance 'SQL01' -Login 'DOM\rescue' -TimeoutSeconds 30 -Confirm:$false } | Should -Throw '*nimmt*Verbindung an*' $calls = $global:SqmCalls | Where-Object { $_ -notlike 'Event:*' } $calls[-3..-1] | Should -Be @('Stop', 'Startup:SingleUser=False', 'Start') } It 'meldet ein nicht zurueckgesetztes Startparameter-Flag als kritisch, startet den Dienst aber trotzdem' { $global:SqmState.ResetFails = $true $r = Restore-sqmSysadminAccess -SqlInstance 'SQL01' -Login 'DOM\rescue' -Confirm:$false $r.Status | Should -Be 'Success' $global:SqmCalls | Should -Contain 'Event:9022' ($global:SqmCalls | Where-Object { $_ -notlike 'Event:*' })[-1] | Should -Be 'Start' } It 'lehnt zu kurze Timeouts ab' { { Restore-sqmSysadminAccess -SqlInstance 'SQL01' -Login 'DOM\rescue' -TimeoutSeconds 5 -Confirm:$false } | Should -Throw } } |