· Uwe Janke
Commands / Invoke-sqmDatabaseStandardization

Invoke-sqmDatabaseStandardization

Maintenance sqmSQLTool v1.9.149+ · Invoke ✓ -WhatIf supported
After a restore onto another server, a migration or an in-place upgrade, a database usually carries the same leftovers: users whose login has a different SID on the new server, users whose login no longer exists at all, the compatibility level of the old version, the old recovery-time default and an owner that was whoever ran the restore. This function cleans all five up in one pass, for one database or every user database on an instance.

Each step can be switched off on its own, every change honours -WhatIf, and each run writes a CSV change log. A second run on the same database only reports Skipped.

Invoke-sqmRestoreDatabase calls this function after every successful restore. Use -KeepCompatibilityLevel there to leave the level as it came out of the backup.

The five steps

StepWhat happensSwitch off with
FixOrphanUserA user whose SID matches no login, but for which a login with the same name exists, is re-mapped to it with ALTER USER ... WITH LOGIN. This is the classic case after a restore onto another server: the SQL login exists there too, just with a different SID.-SkipOrphanRepair
RemoveUserWithoutLoginUsers whose SID still matches no login are dropped. Schemas and roles owned by the user are handed to dbo first, otherwise DROP USER fails. Handover and drop run in one transaction: if the drop fails, the ownership stays as it was and the row is reported as Failed.-SkipUserRemoval
CompatibilityLevelRaised to the level of the server version (SQL Server 2022 = 160, 2025 = 170). A level that is already higher is never lowered.-SkipCompatibilityLevel
TargetRecoveryTimeTARGET_RECOVERY_TIME = 60 SECONDS (indirect checkpoint, the default for databases created since SQL Server 2016). Configurable via -TargetRecoveryTimeSeconds.-SkipTargetRecoveryTime
DatabaseOwnerOwner set to the sa account, found by SID 0x01, so it also works when sa has been renamed.-SkipOwner
Compatibility level: a new level can change execution plans, mainly through the newer cardinality estimator. For an application that is not yet certified for it, run with -SkipCompatibilityLevel and raise the level separately after a performance check.

Users that are never dropped

UserWhy it stays
dbo, guest, INFORMATION_SCHEMA, sys, ##...##Built-in and internal principals.
Created WITHOUT LOGINDeliberately login-less: Service Broker, module signing, EXECUTE AS targets. Kept unless you pass -IncludeUsersWithoutLogin.
Contained database users, certificate and asymmetric key usersThey never had a server login to begin with.
Windows users that reach the server through a group loginThe user has no login of its own, but its account is a member of a Windows group that has one. Then the database user is the permission, and dropping it would lock out a working account. Checked with xp_logininfo ... 'all'; the row is reported as Skipped with the group that grants the access.

Parameters

ParameterTypeRequiredDefaultNotes
-SqlInstancestring[]Optional$env:COMPUTERNAMEOne or more instances. Accepts pipeline input.
-SqlCredentialPSCredentialOptional, SQL authentication instead of the current Windows context.
-Databasestring[]Optionalall user databasesDatabase names, wildcards allowed (App*).
-ExcludeDatabasestring[]Optional, Databases to leave out, wildcards allowed.
-TargetRecoveryTimeSecondsintOptional60Value for TARGET_RECOVERY_TIME, 1 to 3600.
-IncludeUsersWithoutLoginswitchSwitch$falseAlso drop users that were created WITHOUT LOGIN.
-SkipOrphanRepairswitchSwitch$falseSkip step 1.
-SkipUserRemovalswitchSwitch$falseSkip step 2.
-SkipCompatibilityLevelswitchSwitch$falseSkip step 3.
-SkipTargetRecoveryTimeswitchSwitch$falseSkip step 4.
-SkipOwnerswitchSwitch$falseSkip step 5.
-OutputPathstringOptional<OutputPath>\DatabaseStandardizationDirectory for the CSV change log. Written only when a row is OK or Failed, also after an abort.
-ContinueOnErrorswitchSwitch$falseCarry on with the next instance if one cannot be reached.
-EnableExceptionswitchSwitch$falseThrow on the first failure instead of returning a Failed row.
-WhatIf / -ConfirmswitchOptional, ShouldProcess per single change, ConfirmImpact = 'Medium'. Under -WhatIf a user that step 1 would re-map is not also listed as "would be dropped" in step 2.

Execution Flow

START dbatools installed? NO throw: dbatools not found YES Read the server level and the sa login compat level = major version x 10 (2022 = 160) · sa found by SID 0x01 For each user database -Database / -ExcludeDatabase with wildcards · system DBs and snapshots never ONLINE, writable, no AG secondary? NO Skipped whole database YES 1 FixOrphanUser no login for the SID, login of the same name exists -> ALTER USER WITH LOGIN 2 RemoveUserWithoutLogin SID matches no login · WITHOUT LOGIN, contained, cert, ## users are kept Windows user with group access? YES Kept xp_logininfo 'all' NO Schemas and roles to dbo, then DROP USER one transaction · a failed DROP leaves ownership as it was, row = Failed 3 CompatibilityLevelraise to the server level, never lower 4 TargetRecoveryTime60 seconds (-TargetRecoveryTimeSeconds) 5 DatabaseOwnersa, found by SID 0x01 ShouldProcess? (each change) NO WhatIf reported, not run YES CSV change log per instance only when a change was made or failed · also written after an abort Return one row per action DONE

Result

One row per action with SqlInstance, Database, Step, Target (user or database name), OldValue, NewValue, Status and Message. A database that is skipped as a whole gets a single row with Step = Database.

StatusMeaning
OKThe change was made.
SkippedNothing to do (already at the target value), or deliberately left alone: a Windows user with group access, or a database that is offline, read-only or an AG secondary.
FailedThe change failed. The message carries the SQL Server error, for example a user that still owns objects other than schemas and roles, or a login that is already mapped to another user in the same database.
WhatIf-WhatIf: this is what would have happened.

Examples

Dry run across all user databases of an instance
Invoke-sqmDatabaseStandardization -SqlInstance "SQL01" -WhatIf |
    Format-Table Database, Step, Target, OldValue, NewValue, Status -AutoSize
One database after a migration
Invoke-sqmDatabaseStandardization -SqlInstance "SQL01" -Database "SalesDB"
Everything except the compatibility level (application not yet certified for it)
Invoke-sqmDatabaseStandardization -SqlInstance "SQL01" -Database "App*" -SkipCompatibilityLevel
Also drop users created WITHOUT LOGIN, but leave the owner alone
Invoke-sqmDatabaseStandardization -SqlInstance "SQL01" -Database "SalesDB" `
    -IncludeUsersWithoutLogin -SkipOwner
Several instances without prompting, show only what went wrong
"SQL01", "SQL02" | Invoke-sqmDatabaseStandardization -Confirm:$false |
    Where-Object Status -eq "Failed" |
    Format-Table SqlInstance, Database, Step, Target, Message -Wrap