Skip to content

Commit a910566

Browse files
author
Uwe Janke
committed
v1.8.18.0: Copy-sqmLogins - narrow the policy-disable window to just the copy call
Sync-sqmLoginsToAlwaysOn was failing in FI-TS environments with "Policy 'New Login_Enforce Passwort Policy' has been violated". Copy-sqmLogins -Force (default true) passes -Force through to dbatools Copy-DbaLogin, which does DROP+CREATE (not ALTER) for existing logins - every sync run fires a real CREATE_LOGIN event that the PBM policy evaluates. The policy was disabled at the very start (before connect, auth-mode check incl. possible service restart, AD lookup) and only re-enabled at the very end (after orphan repair) - an unnecessarily wide window for a security- relevant policy to be off. Extracted _DisablePolicy/_EnablePolicy helpers and scoped them tightly around the Copy-DbaLogin call: disable immediately before, enable in a dedicated finally immediately after. Connect/auth-mode-check/AD-lookup now run before the disabled window; orphan repair runs after re-enable. Verified on DEV02: result order is now AuthModeCheck -> PolicyDisable -> CopyLogin -> RepairOrphanUsers (previously PolicyDisable came first). No behavior change when -DisablePolicy $false or no DefaultPolicy configured.
1 parent a0799c7 commit a910566

3 files changed

Lines changed: 170 additions & 138 deletions

File tree

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
# sqmSQLTool — Changelog
22

3+
## [1.8.18.0] — 2026-07-02
4+
5+
### Bugfix
6+
7+
**`Copy-sqmLogins`** — Policy-Deaktivierungsfenster auf den eigentlichen Kopiervorgang verengt
8+
- Ausgangslage: `Sync-sqmLoginsToAlwaysOn` schlug in FI-TS-Umgebungen mit "Policy 'New
9+
Login_Enforce Passwort Policy' has been violated" fehl. `Copy-sqmLogins -Force`
10+
(Default `$true`) reicht `-Force` an dbatools `Copy-DbaLogin` durch, das bei bereits
11+
vorhandenen Logins DROP + CREATE statt ALTER macht — jeder Sync-Lauf loest damit ein
12+
echtes `CREATE_LOGIN`-Event aus, das die PBM-Policy prueft.
13+
- Die Policy wurde bisher ganz am Anfang deaktiviert (vor Connect, Auth-Mode-Check inkl.
14+
moeglichem Dienst-Neustart, AD-Pruefung) und erst ganz am Ende (nach Orphan-Repair)
15+
wieder aktiviert — ein unnoetig grosses Zeitfenster fuer eine sicherheitsrelevante
16+
Policy.
17+
- Fix: `_DisablePolicy`/`_EnablePolicy`-Hilfsfunktionen eingefuehrt und eng um den
18+
`Copy-DbaLogin`-Aufruf gelegt (Disable unmittelbar davor, Enable in einem dediziert
19+
dafuer scope-ten `finally` unmittelbar danach). Connect/Auth-Mode-Check/AD-Pruefung
20+
laufen jetzt VOR dem deaktivierten Fenster, Orphan-Repair NACH der Reaktivierung.
21+
Verifiziert auf DEV02: Reihenfolge jetzt AuthModeCheck -> PolicyDisable -> CopyLogin
22+
-> RepairOrphanUsers (vorher: PolicyDisable ganz zuerst).
23+
- Kein Verhaltensunterschied bei `-DisablePolicy $false` oder wenn kein `DefaultPolicy`
24+
konfiguriert ist (weiterhin "Skipped", keine Deaktivierung).
25+
326
## [1.8.17.0] — 2026-07-02
427

528
### Bugfix

Public/Copy-sqmLogins.ps1

Lines changed: 146 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
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
@@ -51,12 +53,14 @@
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.
@@ -150,7 +154,9 @@
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
#>
155161
function 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

sqmSQLTool.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
RootModule = 'sqmSQLTool.psm1'
1818

1919
# Version number of this module.
20-
ModuleVersion = '1.8.17.0'
20+
ModuleVersion = '1.8.18.0'
2121

2222
# ID used to uniquely identify this module
2323
GUID = 'c4b10ba2-aee2-4d8d-ad86-a6e97c346ba6'

0 commit comments

Comments
 (0)