Skip to content

Commit 7777c14

Browse files
authored
Export-DbaLogin: Add -IncludeRolePermissions switch (#10196)
1 parent 14a287e commit 7777c14

2 files changed

Lines changed: 87 additions & 0 deletions

File tree

public/Export-DbaLogin.ps1

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ function Export-DbaLogin {
8888
Includes detailed object-level permissions for each database user associated with the exported logins.
8989
Use this for complete permission migration when you need granular security settings preserved in the target environment.
9090
91+
.PARAMETER IncludeRolePermissions
92+
Includes permissions granted to database roles that the login's database users are members of.
93+
By default, Export-DbaLogin scripts role membership (ALTER ROLE ... ADD MEMBER) but not the permissions granted to those roles.
94+
Use this switch to also export GRANT/DENY statements for each non-fixed role, ensuring the roles have the correct permissions on the target server.
95+
9196
.PARAMETER EnableException
9297
By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
9398
This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
@@ -207,6 +212,7 @@ function Export-DbaLogin {
207212
[switch]$NoPrefix,
208213
[switch]$Passthru,
209214
[switch]$ObjectLevel,
215+
[switch]$IncludeRolePermissions,
210216
[switch]$EnableException
211217
)
212218

@@ -529,6 +535,29 @@ function Export-DbaLogin {
529535
} catch {
530536
Stop-Function -Message "Failed to extract permissions for user $dbUserName in database $dbName" -Continue -ErrorRecord $_
531537
}
538+
539+
if ($IncludeRolePermissions) {
540+
foreach ($role in $sourceDb.Roles) {
541+
if ($role.IsFixedRole -eq $false -and $role.EnumMembers() -contains $dbUserName) {
542+
$splatExportRole = @{
543+
SqlInstance = $server
544+
Database = $dbName
545+
Role = $role.Name
546+
Passthru = $true
547+
NoPrefix = $true
548+
BatchSeparator = ""
549+
}
550+
try {
551+
$roleScript = Export-DbaDbRole @splatExportRole
552+
if ($roleScript) {
553+
$outsql += $roleScript
554+
}
555+
} catch {
556+
Write-Message -Level Warning -Message "Failed to export permissions for role $($role.Name) in database $dbName : $($_.Exception.Message)"
557+
}
558+
}
559+
}
560+
}
532561
} else {
533562
try {
534563
$sql = $server.Databases[$dbName].Users[$dbUserName].Script($scriptOptions)
@@ -551,6 +580,29 @@ function Export-DbaLogin {
551580
}
552581
}
553582

583+
if ($IncludeRolePermissions) {
584+
foreach ($role in $sourceDb.Roles) {
585+
if ($role.IsFixedRole -eq $false -and $role.EnumMembers() -contains $dbUserName) {
586+
$splatExportRole = @{
587+
SqlInstance = $server
588+
Database = $dbName
589+
Role = $role.Name
590+
Passthru = $true
591+
NoPrefix = $true
592+
BatchSeparator = ""
593+
}
594+
try {
595+
$roleScript = Export-DbaDbRole @splatExportRole
596+
if ($roleScript) {
597+
$outsql += $roleScript
598+
}
599+
} catch {
600+
Write-Message -Level Warning -Message "Failed to export permissions for role $($role.Name) in database $dbName : $($_.Exception.Message)"
601+
}
602+
}
603+
}
604+
}
605+
554606
# Connect, Alter Any Assembly, etc
555607
$perms = $sourceDb.EnumDatabasePermissions($dbUserName)
556608
foreach ($perm in $perms) {

tests/Export-DbaLogin.Tests.ps1

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Describe $CommandName -Tag UnitTests {
3131
"NoPrefix",
3232
"Passthru",
3333
"ObjectLevel",
34+
"IncludeRolePermissions",
3435
"EnableException"
3536
)
3637
Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $hasParameters | Should -BeNullOrEmpty
@@ -73,6 +74,21 @@ Describe $CommandName -Tag IntegrationTests {
7374
$login3 = "dbatoolsci_exportdbalogin_login3$random"
7475
$server.Query("CREATE LOGIN [$login3] WITH PASSWORD = 'GoodPass1234!'")
7576
$db1.Query("CREATE USER [$login3] WITHOUT LOGIN")
77+
78+
# login with a custom role that has granted permissions (for IncludeRolePermissions tests)
79+
$login4 = "dbatoolsci_exportdbalogin_login4$random"
80+
$user4 = "dbatoolsci_exportdbalogin_user4$random"
81+
$role4 = "dbatoolsci_exportdbalogin_role4$random"
82+
$null = $server.Query("CREATE LOGIN [$login4] WITH PASSWORD = 'GoodPass1234!'")
83+
$db1.Query("CREATE USER [$user4] FOR LOGIN [$login4]")
84+
$db1.Query("CREATE ROLE [$role4]")
85+
$db1.Query("GRANT SELECT ON SCHEMA::dbo TO [$role4]")
86+
$db1.Query("GRANT EXECUTE ON SCHEMA::dbo TO [$role4]")
87+
if ($server.VersionMajor -lt 11) {
88+
$db1.Query("EXEC sp_addrolemember @rolename = N'$role4', @membername = N'$user4'")
89+
} else {
90+
$db1.Query("ALTER ROLE [$role4] ADD MEMBER [$user4]")
91+
}
7692
}
7793
AfterAll {
7894
Remove-DbaDatabase -SqlInstance $TestConfig.InstanceSingle -Database $dbname1
@@ -87,6 +103,7 @@ Describe $CommandName -Tag IntegrationTests {
87103
}
88104

89105
Remove-DbaLogin -SqlInstance $TestConfig.InstanceSingle -Login $login3
106+
Remove-DbaLogin -SqlInstance $TestConfig.InstanceSingle -Login $login4
90107
}
91108

92109
Context "Executes with Exclude Parameters" {
@@ -159,6 +176,24 @@ Describe $CommandName -Tag IntegrationTests {
159176
$results | Should -Match ([regex]::Escape("IF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = N'$user1')"))
160177
}
161178
}
179+
Context "Executes with IncludeRolePermissions" {
180+
It "Should include role permissions in non-ObjectLevel export" {
181+
$results = Export-DbaLogin -SqlInstance $server -Login $login4 -Database $dbname1 -IncludeRolePermissions -Passthru -WarningAction SilentlyContinue
182+
$results | Should -Match "GRANT SELECT ON SCHEMA::\[dbo\]"
183+
$results | Should -Match "GRANT EXECUTE ON SCHEMA::\[dbo\]"
184+
$results | Should -Match ([regex]::Escape("[$role4]"))
185+
}
186+
It "Should include role permissions in ObjectLevel export" {
187+
$results = Export-DbaLogin -SqlInstance $server -Login $login4 -Database $dbname1 -ObjectLevel -IncludeRolePermissions -Passthru -WarningAction SilentlyContinue
188+
$results | Should -Match "GRANT SELECT ON SCHEMA::\[dbo\]"
189+
$results | Should -Match "GRANT EXECUTE ON SCHEMA::\[dbo\]"
190+
$results | Should -Match ([regex]::Escape("[$role4]"))
191+
}
192+
It "Should not include role permissions without the switch" {
193+
$results = Export-DbaLogin -SqlInstance $server -Login $login4 -Database $dbname1 -Passthru -WarningAction SilentlyContinue
194+
$results | Should -Not -Match "GRANT SELECT ON SCHEMA::\[dbo\]"
195+
}
196+
}
162197
Context "Exports file to random and specified paths" {
163198
It "Should export file to the configured path" {
164199
$file = Export-DbaLogin -SqlInstance $TestConfig.InstanceSingle -ExcludeDatabase -WarningAction SilentlyContinue

0 commit comments

Comments
 (0)