66 Transfers SQL and Windows logins from a source instance to a target instance.
77
88 Process:
9- 1. Disable policy (Set-sqmSqlPolicyState -State Disable, if -DisablePolicy $true)
10- 2. Connect + authentication mode check / alignment
11- 3. Load and filter logins
12- 4. Check Windows logins against Active Directory (AD module required)
9+ 1. Connect + authentication mode check / alignment
10+ 2. Load and filter logins
11+ 3. Check Windows logins against Active Directory (AD module required)
1312 - Unresolvable logins are skipped and reported as 'AdOrphan'.
13+ 4. Disable policy (Set-sqmSqlPolicyState -State Disable, if -DisablePolicy $true)
1414 5. Copy logins (Copy-DbaLogin, password hash + SID mapping)
15- 6. Repair orphaned users on all user databases on the target
15+ 6. Re-enable policy - guaranteed via a finally block scoped tightly around step 5,
16+ even on error. The policy is only disabled for the duration of the actual copy,
17+ not for the connect/auth-mode/AD-check steps before it.
18+ 7. Repair orphaned users on all user databases on the target
1619 (Repair-DbaDbOrphanUser - always runs, no optional switch)
17- 7. Re-enable policy - guaranteed via finally block, even on error.
1820
1921 Authentication mode alignment:
2022 If the source uses Mixed Mode (SQL + Windows) and the target is set to
5153 databases on the target (no optional switch).
5254
5355 Policy:
54- Before copying, Set-sqmSqlPolicyState disables the configured default policy
55- on the target instance. After completion (even on error) it is guaranteed to
56- be re-enabled via a finally block.
56+ Immediately before the Copy-DbaLogin call, Set-sqmSqlPolicyState disables the
57+ configured default policy on the target instance; immediately after (in a finally
58+ block scoped to just that step) it is guaranteed to be re-enabled, even on error.
59+ Connect, auth-mode check and the AD lookup run BEFORE this window with the policy
60+ still enabled - the disabled window is kept as short as possible.
5761 Controlled by -DisablePolicy (default: $true).
58- The finally block re-enables the policy only if it was previously successfully
59- disabled ($policyWasDisabled flag).
62+ Re-enable only runs if the policy was previously successfully disabled
63+ ($policyWasDisabled flag).
6064
6165. PARAMETER Source
6266 Source SQL Server instance. Mandatory.
150154 AD check : Requires the ActiveDirectory module (RSAT). Behavior when module is missing
151155 is controllable via -AdModuleAction (Install/Skip/Abort).
152156 Auth Mode SMO : Server.LoginMode - Integrated(0/1) = Windows Only, Mixed(2) = SQL+Windows
153- Policy guarantee: The finally block ensures the policy is re-enabled even on unhandled exceptions.
157+ Policy guarantee: A finally block scoped tightly around the Copy-DbaLogin call ensures
158+ the policy is re-enabled immediately afterwards, even on unhandled
159+ exceptions during the copy.
154160#>
155161function Copy-sqmLogins
156162{
@@ -296,11 +302,111 @@ function Copy-sqmLogins
296302 Timestamp = (Get-Date )
297303 })
298304 }
299-
300- # Merker: wurde die Policy tatsaechlich deaktiviert?
301- # Wird im finally-Block ausgewertet um unnoetige Reaktivierung zu vermeiden.
302- $policyWasDisabled = $false
303-
305+
306+ # Hilfsfunktion: Policy auf Zielinstanz deaktivieren.
307+ # Wird absichtlich erst UNMITTELBAR vor Copy-DbaLogin aufgerufen (nicht vor Connect/
308+ # Auth-Mode-Check/AD-Pruefung) - das Zeitfenster, in dem eine sicherheitsrelevante
309+ # Policy deaktiviert ist, soll so kurz wie moeglich sein.
310+ # Rueckgabe: $true nur wenn tatsaechlich deaktiviert wurde (dann muss _EnablePolicy
311+ # spaeter aufgerufen werden), sonst $false.
312+ function _DisablePolicy
313+ {
314+ if (-not $DisablePolicy ) { return $false }
315+
316+ $_configuredPolicy = Get-sqmConfig - Key ' DefaultPolicy' 3> $null
317+ if ([string ]::IsNullOrWhiteSpace($_configuredPolicy ))
318+ {
319+ $skipMsg = " Policy-Handling uebersprungen: kein 'DefaultPolicy' in der Modulkonfiguration. " +
320+ " Verwende 'Set-sqmConfig -DefaultPolicy <Name>' um einen Policy-Namen zu setzen."
321+ Write-Warning $skipMsg
322+ Invoke-sqmLogging - Message $skipMsg - FunctionName $functionName - Level ' WARNING'
323+ _AddResult ' PolicyDisable' ' (Server)' ' Skipped' $skipMsg
324+ return $false
325+ }
326+
327+ try
328+ {
329+ $policyDisableAction = " Default-Policy auf '$Destination ' deaktivieren"
330+ Invoke-sqmLogging - Message $policyDisableAction - FunctionName $functionName - Level ' INFO'
331+
332+ $policyResult = Set-sqmSqlPolicyState `
333+ - SqlInstance $Destination `
334+ - SqlCredential $dstCred `
335+ - State Disable `
336+ - ContinueOnError:$ContinueOnError `
337+ - EnableException:$EnableException
338+
339+ $policyStatus = ($policyResult | Select-Object - ExpandProperty Status - First 1 )
340+
341+ if ($policyStatus -eq ' Success' )
342+ {
343+ _AddResult ' PolicyDisable' ' (Server)' ' Success' ' Default-Policy erfolgreich deaktiviert.'
344+ Invoke-sqmLogging - Message " Policy auf '$Destination ' deaktiviert." `
345+ - FunctionName $functionName - Level ' INFO'
346+ return $true
347+ }
348+ elseif ($policyStatus -eq ' Skipped' )
349+ {
350+ # Policy existiert nicht - kein Re-Enable erforderlich
351+ _AddResult ' PolicyDisable' ' (Server)' ' Skipped' ' Policy nicht gefunden - uebersprungen.'
352+ Invoke-sqmLogging - Message " Policy auf '$Destination ' nicht gefunden - uebersprungen." `
353+ - FunctionName $functionName - Level ' WARNING'
354+ return $false
355+ }
356+ else
357+ {
358+ $msg = " Policy-Deaktivierung auf '$Destination ' fehlgeschlagen (Status: $policyStatus )."
359+ Invoke-sqmLogging - Message $msg - FunctionName $functionName - Level ' ERROR'
360+ _AddResult ' PolicyDisable' ' (Server)' ' Failed' $msg
361+ if ($EnableException ) { throw $msg }
362+ return $false
363+ }
364+ }
365+ catch
366+ {
367+ $msg = " Fehler bei Policy-Deaktivierung auf '$Destination ': $ ( $_.Exception.Message ) "
368+ Invoke-sqmLogging - Message $msg - FunctionName $functionName - Level ' ERROR'
369+ _AddResult ' PolicyDisable' ' (Server)' ' Failed' $msg
370+ if ($EnableException ) { throw }
371+ return $false
372+ }
373+ }
374+
375+ # Hilfsfunktion: Policy auf Zielinstanz wieder aktivieren.
376+ # Wird in einem eng um Copy-DbaLogin gelegten finally-Block aufgerufen - laeuft also
377+ # unmittelbar NACH dem eigentlichen Lauf, garantiert auch bei Fehlern in Copy-DbaLogin.
378+ # EnableException wird hier absichtlich NIE gesetzt: ein Fehler beim Reaktivieren darf
379+ # eine urspruengliche Ausnahme aus dem Copy-Vorgang nicht verdecken.
380+ function _EnablePolicy
381+ {
382+ try
383+ {
384+ $reenableAction = " Default-Policy auf '$Destination ' wieder aktivieren"
385+ Invoke-sqmLogging - Message $reenableAction - FunctionName $functionName - Level ' INFO'
386+
387+ $reEnableResult = Set-sqmSqlPolicyState `
388+ - SqlInstance $Destination `
389+ - SqlCredential $dstCred `
390+ - State Enable `
391+ - ContinueOnError:$ContinueOnError `
392+ - EnableException:$false
393+
394+ $reEnableStatus = ($reEnableResult | Select-Object - ExpandProperty Status - First 1 )
395+ _AddResult ' PolicyEnable' ' (Server)' $reEnableStatus " Policy reaktiviert: $reEnableStatus "
396+ Invoke-sqmLogging - Message " Policy-Reaktivierung auf '$Destination ': $reEnableStatus " `
397+ - FunctionName $functionName - Level ' INFO'
398+ }
399+ catch
400+ {
401+ # Fehler hier nur loggen, nicht weiterwerfen - sonst wuerde eine urspruengliche
402+ # Ausnahme aus dem Copy-Vorgang verdeckt.
403+ $msg = " KRITISCH: Policy-Reaktivierung auf '$Destination ' fehlgeschlagen: $ ( $_.Exception.Message ) "
404+ Write-Warning $msg
405+ Invoke-sqmLogging - Message $msg - FunctionName $functionName - Level ' ERROR'
406+ _AddResult ' PolicyEnable' ' (Server)' ' Failed' $msg
407+ }
408+ }
409+
304410 # ?? AD-Modul sicherstellen ????????????????????????????????????????????
305411 # Steuerung ueber -AdModuleAction:
306412 # 'Install' (Standard) ? Install-sqmAdModule aufrufen falls nicht vorhanden
@@ -359,80 +465,9 @@ function Copy-sqmLogins
359465 try # aeusserer try ? finally garantiert Policy-Reaktivierung in jedem Fall
360466 {
361467 # ??????????????????????????????????????????????????????????????
362- # 1. Policy auf Zielinstanz deaktivieren
363- # ??????????????????????????????????????????????????????????????
364- if ($DisablePolicy )
365- {
366- # Vor dem Aufruf pruefen ob DefaultPolicy konfiguriert ist.
367- # Fehlt sie, wird Policy-Handling uebersprungen statt mit Fehler abzubrechen.
368- $_configuredPolicy = Get-sqmConfig - Key ' DefaultPolicy' 3> $null
369- if ([string ]::IsNullOrWhiteSpace($_configuredPolicy ))
370- {
371- $skipMsg = " Policy-Handling uebersprungen: kein 'DefaultPolicy' in der Modulkonfiguration. " +
372- " Verwende 'Set-sqmConfig -DefaultPolicy <Name>' um einen Policy-Namen zu setzen."
373- Write-Warning $skipMsg
374- Invoke-sqmLogging - Message $skipMsg - FunctionName $functionName - Level ' WARNING'
375- _AddResult ' PolicyDisable' ' (Server)' ' Skipped' $skipMsg
376- }
377- else
378- {
379-
380- $policyDisableAction = " Default-Policy auf '$Destination ' deaktivieren"
381- if ($PSCmdlet.ShouldProcess ($Destination , $policyDisableAction ))
382- {
383- try
384- {
385- Invoke-sqmLogging - Message $policyDisableAction - FunctionName $functionName - Level ' INFO'
386-
387- $policyResult = Set-sqmSqlPolicyState `
388- - SqlInstance $Destination `
389- - SqlCredential $dstCred `
390- - State Disable `
391- - ContinueOnError:$ContinueOnError `
392- - EnableException:$EnableException
393-
394- $policyStatus = ($policyResult | Select-Object - ExpandProperty Status - First 1 )
395-
396- if ($policyStatus -eq ' Success' )
397- {
398- # Nur bei 'Success' muss spaeter reaktiviert werden
399- $policyWasDisabled = $true
400- _AddResult ' PolicyDisable' ' (Server)' ' Success' ' Default-Policy erfolgreich deaktiviert.'
401- Invoke-sqmLogging - Message " Policy auf '$Destination ' deaktiviert." `
402- - FunctionName $functionName - Level ' INFO'
403- }
404- elseif ($policyStatus -eq ' Skipped' )
405- {
406- # Policy existiert nicht ? kein Re-Enable erforderlich
407- _AddResult ' PolicyDisable' ' (Server)' ' Skipped' ' Policy nicht gefunden - uebersprungen.'
408- Invoke-sqmLogging - Message " Policy auf '$Destination ' nicht gefunden - uebersprungen." `
409- - FunctionName $functionName - Level ' WARNING'
410- }
411- else
412- {
413- $msg = " Policy-Deaktivierung auf '$Destination ' fehlgeschlagen (Status: $policyStatus ). Abbruch."
414- Invoke-sqmLogging - Message $msg - FunctionName $functionName - Level ' ERROR'
415- _AddResult ' PolicyDisable' ' (Server)' ' Failed' $msg
416- if (-not $ContinueOnError -and $EnableException ) { throw $msg }
417- if (-not $ContinueOnError ) { return $results }
418- }
419- }
420- catch
421- {
422- $msg = " Fehler bei Policy-Deaktivierung auf '$Destination ': $ ( $_.Exception.Message ) "
423- Invoke-sqmLogging - Message $msg - FunctionName $functionName - Level ' ERROR'
424- _AddResult ' PolicyDisable' ' (Server)' ' Failed' $msg
425- if (-not $ContinueOnError -and $EnableException ) { throw }
426- if (-not $ContinueOnError ) { return $results }
427- }
428- }
429- else
430- {
431- _AddResult ' PolicyDisable' ' (Server)' ' WhatIf' ' WhatIf: Policy wuerde deaktiviert.'
432- }
433- } # end else (DefaultPolicy konfiguriert)
434- } # end if ($DisablePolicy)
435-
468+ # 1. Policy-Deaktivierung liegt jetzt eng um Copy-DbaLogin (Schritt 6, s.u.),
469+ # nicht mehr hier vor Connect/Auth-Mode-Check/AD-Pruefung - das Zeitfenster
470+ # mit deaktivierter Policy soll so kurz wie moeglich sein.
436471 # ??????????????????????????????????????????????????????????????
437472 # 2. Verbindung zu Quelle und Ziel aufbauen (SMO-Server-Objekte)
438473 # ??????????????????????????????????????????????????????????????
@@ -720,10 +755,17 @@ function Copy-sqmLogins
720755
721756 if ($PSCmdlet.ShouldProcess ($Destination , $copyAction ))
722757 {
758+ # Policy-Deaktivierung liegt bewusst HIER, direkt vor Copy-DbaLogin - und wird
759+ # im finally garantiert direkt danach wieder aktiviert (s. _DisablePolicy/
760+ # _EnablePolicy oben in begin). Verbinden/Auth-Mode-Check/AD-Pruefung laufen
761+ # VOR diesem Block mit weiterhin aktiver Policy.
762+ $policyWasDisabled = $false
723763 try
724764 {
725765 Invoke-sqmLogging - Message $copyAction - FunctionName $functionName - Level ' INFO'
726-
766+
767+ $policyWasDisabled = _DisablePolicy
768+
727769 $copyParams = @ {
728770 Source = $Source
729771 Destination = $Destination
@@ -733,17 +775,17 @@ function Copy-sqmLogins
733775 if ($srcCred ) { $copyParams [' SqlCredential' ] = $srcCred }
734776 if ($dstCred ) { $copyParams [' DestinationSqlCredential' ] = $dstCred }
735777 if ($Force ) { $copyParams [' Force' ] = $true }
736-
778+
737779 $copyResults = Copy-DbaLogin @copyParams
738-
780+
739781 foreach ($item in $copyResults )
740782 {
741783 $itemStatus = if ($item.Status -eq ' Successful' ) { ' Success' }
742784 else { $item.Status }
743785 $itemMsg = if ($item.Notes ) { $item.Notes }
744786 else { $item.Status }
745787 _AddResult ' CopyLogin' $item.Name $itemStatus $itemMsg
746-
788+
747789 $logLevel = if ($item.Status -eq ' Successful' ) { ' INFO' }
748790 else { ' WARNING' }
749791 Invoke-sqmLogging - Message " Login '$ ( $item.Name ) ': $itemStatus - $itemMsg " `
@@ -757,6 +799,12 @@ function Copy-sqmLogins
757799 _AddResult ' CopyLogin' ' (alle)' ' Failed' $msg
758800 if ($EnableException ) { throw }
759801 }
802+ finally
803+ {
804+ # Laeuft garantiert direkt nach Copy-DbaLogin, auch bei Fehlern darin -
805+ # das Deaktivierungsfenster endet hier, nicht erst am Ende der Funktion.
806+ if ($policyWasDisabled ) { _EnablePolicy }
807+ }
760808 }
761809 else
762810 {
@@ -819,48 +867,9 @@ function Copy-sqmLogins
819867 }
820868 finally
821869 {
822- # ??????????????????????????????????????????????????????????????
823- # 8. Policy auf Zielinstanz garantiert wieder aktivieren
824- # Laeuft immer - auch bei unbehandelten Ausnahmen im try-Block.
825- # Nur wenn $policyWasDisabled = $true (d.h. erfolgreich deaktiviert).
826- # EnableException=$false im finally-Block: Fehler hier NIEMALS werfen.
827- # ??????????????????????????????????????????????????????????????
828- if ($DisablePolicy -and $policyWasDisabled )
829- {
830- $reenableAction = " Default-Policy auf '$Destination ' wieder aktivieren"
831- if ($PSCmdlet.ShouldProcess ($Destination , $reenableAction ))
832- {
833- try
834- {
835- Invoke-sqmLogging - Message $reenableAction - FunctionName $functionName - Level ' INFO'
836-
837- $reEnableResult = Set-sqmSqlPolicyState `
838- - SqlInstance $Destination `
839- - SqlCredential $dstCred `
840- - State Enable `
841- - ContinueOnError:$ContinueOnError `
842- - EnableException:$false # im finally-Block niemals werfen
843-
844- $reEnableStatus = ($reEnableResult | Select-Object - ExpandProperty Status - First 1 )
845- _AddResult ' PolicyEnable' ' (Server)' $reEnableStatus " Policy reaktiviert: $reEnableStatus "
846- Invoke-sqmLogging - Message " Policy-Reaktivierung auf '$Destination ': $reEnableStatus " `
847- - FunctionName $functionName - Level ' INFO'
848- }
849- catch
850- {
851- # Fehler im finally-Block nur loggen, nicht weiterwerfen -
852- # sonst wuerde eine urspruengliche Ausnahme verdeckt.
853- $msg = " KRITISCH: Policy-Reaktivierung auf '$Destination ' fehlgeschlagen: $ ( $_.Exception.Message ) "
854- Write-Warning $msg
855- Invoke-sqmLogging - Message $msg - FunctionName $functionName - Level ' ERROR'
856- _AddResult ' PolicyEnable' ' (Server)' ' Failed' $msg
857- }
858- }
859- else
860- {
861- _AddResult ' PolicyEnable' ' (Server)' ' WhatIf' ' WhatIf: Policy wuerde reaktiviert.'
862- }
863- }
870+ # Policy-Reaktivierung liegt jetzt eng um Copy-DbaLogin (Schritt 6, s.o.) und laeuft
871+ # dort in einem eigenen finally direkt nach dem Copy-Vorgang - nicht mehr erst hier
872+ # am Ende der gesamten Funktion (nach Orphan-Repair etc.).
864873 }
865874 }
866875
0 commit comments