|
| 1 | +<# |
| 2 | +.Synopsis |
| 3 | + Clone a SQL Server Login based on another exsiting login. |
| 4 | +
|
| 5 | +.DESCRIPTION |
| 6 | + Clone a SQL Server Login based on another exsiting login on one or multiple servers and the process can generate a script for auditing purpose. |
| 7 | +
|
| 8 | +.EXAMPLE |
| 9 | + The following command scripts out the permissions of login account [John] and generates the script at "c:\temp\clone.sql" |
| 10 | + Notice, parameters [OldLogin] and [NewLogin] uses the same value of "John" |
| 11 | + Clone-SQLLogin -Server Server1, Server2 -OldLogin John -NewLogin John -FilePath "c:\temp\clone.sql" |
| 12 | + |
| 13 | +.EXAMPLE |
| 14 | + The following command exports a script that can be used to clone login account [John] for new login account [David], and the script is created at "c:\temp\clone.sql" |
| 15 | + also the -Execute parameter means the new account "David" will be created |
| 16 | + Clone-SQLLogin -Server Server1, Server2 -OldLogin John -NewLogin David -NewPassword 'P@$$W0rd' -FilePath "c:\temp\clone.sql" -Execute; |
| 17 | +
|
| 18 | +.Parameter ServerInstance |
| 19 | + ServerInstance is of string array datat ype, and can accept a string of sql instance names, spearated by comma. (mandatory) |
| 20 | +
|
| 21 | +.Parameter OldLogin |
| 22 | + The login account is the source where all the permissions will be scripted out and to be used for the NewLogin account (mandatory) |
| 23 | +
|
| 24 | +.Parameter NewLogin |
| 25 | + The login account is the target account that we want to clone (mandatory) |
| 26 | +
|
| 27 | +.Parameter NewPassword |
| 28 | + The password for the new login account if we want to create the login, default to empty string "" (optional) |
| 29 | +
|
| 30 | +.Parameter FilePath |
| 31 | + The full path name for the generated sql script (optional) |
| 32 | + |
| 33 | +.Parameter Execute |
| 34 | + This is a swich parameter, if present, it means we need to create the NewLogin account. |
| 35 | +
|
| 36 | +.OUTPUTS |
| 37 | + none |
| 38 | +
|
| 39 | +.NOTES |
| 40 | + A few service broker related permissions are not covered in this version 1. |
| 41 | + Original link: https://www.mssqltips.com/sqlservertip/4572/cloning-a-sql-server-login-with-all-permissions-using-powershell/ |
| 42 | + Author: Jeffrey Yao |
| 43 | +#> |
| 44 | +#requires -version 3.0 |
| 45 | +add-type -assembly "Microsoft.SqlServer.Smo, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"; #if Version-11.xx means sql server 2012 |
| 46 | + |
| 47 | +function Clone-SQLLogin |
| 48 | +{ |
| 49 | + [CmdletBinding(SupportsShouldProcess=$true)] |
| 50 | + |
| 51 | + Param |
| 52 | + ( |
| 53 | + # Param1 help description |
| 54 | + [Parameter(Mandatory=$true, |
| 55 | + ValueFromPipeline=$true, |
| 56 | + Position=0)] |
| 57 | + [string[]] $ServerInstance, |
| 58 | + |
| 59 | + [Parameter(Mandatory=$true)] |
| 60 | + [string] $OldLogin, |
| 61 | + |
| 62 | + [Parameter(Mandatory=$true)] |
| 63 | + [string] $NewLogin, |
| 64 | + |
| 65 | + [string] $NewPassword="", |
| 66 | + |
| 67 | + [string] $FilePath="", |
| 68 | + [switch] $Execute |
| 69 | + ) |
| 70 | + |
| 71 | + Begin |
| 72 | + { |
| 73 | + [string]$newUser=$newLogin.Substring($newLogin.IndexOf('\')+1); # if $newLogin is a Windows account, such as domain\username, since "\" is invalid in db user name, we need to remove it |
| 74 | + |
| 75 | + [hashtable[]] $hta = @(); # a hashtable array |
| 76 | + [hashtable] $h = @{}; |
| 77 | + |
| 78 | + |
| 79 | + if ( ($FilePath -ne "") -and (test-path -Path $FilePath)) |
| 80 | + { del -Path $filepath; } |
| 81 | + } |
| 82 | + Process |
| 83 | + { |
| 84 | + |
| 85 | + foreach ($sqlinstance in $ServerInstance) |
| 86 | + { |
| 87 | + |
| 88 | + $svr = new-object "Microsoft.SqlServer.Management.Smo.Server" $sqlinstance; |
| 89 | + if ($svr.Edition -eq $null) |
| 90 | + { |
| 91 | + Write-warning "$sqlinstance cannot be connected"; |
| 92 | + continue; |
| 93 | + } |
| 94 | + |
| 95 | + [string]$str = ""; |
| 96 | + |
| 97 | + if (-not $WindowsLogin) |
| 98 | + { |
| 99 | + $str += "create login $($newLogin) with password='$($newPassword)'; `r`n" |
| 100 | + } |
| 101 | + else |
| 102 | + { |
| 103 | + $str += "create login $($newLogin) from windows;`r`n " |
| 104 | + } |
| 105 | + |
| 106 | + #find role membership for $login |
| 107 | + if ($svr.logins[$OldLogin] -ne $null) |
| 108 | + { $svr.logins[$oldLogin].ListMembers() | % {$str += "exec sp_addsrvrolemember @loginame = '$($newLogin)', @rolename = '$($_)'; `r`n"};} |
| 109 | + else |
| 110 | + { Write-warning "$oldLogin does not exist on server [$($svr.name)] so this sql instance is skipped"; continue; } |
| 111 | + |
| 112 | + # find permission granted to $login |
| 113 | + |
| 114 | + |
| 115 | + $svr.EnumObjectPermissions($oldLogin) | % { if ($_.PermissionState -eq 'GrantWithGrant') |
| 116 | + {$str += "GRANT $($_.PermissionType) on $($_.ObjectClass)::[$($_.ObjectName)] to [$newLogin] WITH GRANT OPTION; `r`n"} |
| 117 | + else |
| 118 | + { $str += "$($_.PermissionState) $($_.PermissionType) on $($_.ObjectClass)::[$($_.ObjectName)] to [$newLogin]; `r`n"} } |
| 119 | + |
| 120 | + $svr.EnumServerPermissions($oldLogin) | % { if ($_.PermissionState -eq 'GrantWithGrant') |
| 121 | + { $str += "GRANT $($_.PermissionType) to [$newLogin] WITH GRANT OPTION; `r`n"} |
| 122 | + else |
| 123 | + { $str += "$($_.PermissionState) $($_.PermissionType) to [$newLogin]; `r`n" } } |
| 124 | + |
| 125 | + $h = @{Server=$sqlinstance; DBName = 'master'; sqlcmd = $str}; |
| 126 | + $hta += $h; |
| 127 | + #$str; |
| 128 | + |
| 129 | + |
| 130 | + $ObjPerms = @(); # store login mapped users in each db on $svr |
| 131 | + $Roles = @(); |
| 132 | + $DBPerms = @(); |
| 133 | + foreach ($itm in $svr.logins[$oldLogin].EnumDatabaseMappings()) |
| 134 | + { |
| 135 | + if ($svr.Databases[$itm.DBName].Status -ne 'Normal') |
| 136 | + { continue;} |
| 137 | + |
| 138 | + if ($svr.Databases[$itm.DBName].Users[$newUser] -eq $null) |
| 139 | + { $hta += @{Server=$sqlinstance; DBName = $itm.DBName; sqlcmd = "create user [$newUser] for login [$newLogin];`r`n" }; } |
| 140 | + |
| 141 | + $r = $svr.Databases[$itm.DBName].Users[$itm.UserName].EnumRoles(); |
| 142 | + if ($r -ne $null) |
| 143 | + { |
| 144 | + $r | % { $hta += @{Server=$sqlinstance; DBName = $itm.DBName; sqlcmd = "exec sp_addrolemember @rolename='$_', @memberName='$($newUser)';`r`n" } } |
| 145 | + } |
| 146 | + |
| 147 | + |
| 148 | + $p = $svr.Databases[$itm.DBName].EnumDatabasePermissions($itm.UserName); |
| 149 | + if ($p -ne $null) |
| 150 | + { $ObjPerms += @{DBName=$itm.DBName; Permission=$p};} |
| 151 | + |
| 152 | + $p = $svr.Databases[$itm.DBName].EnumObjectPermissions($itm.UserName) |
| 153 | + if ($p -ne $null) |
| 154 | + { $ObjPerms += @{DBName=$itm.DBName; Permission=$p}; } |
| 155 | + |
| 156 | + $p = $svr.Databases[$itm.DBName].Certificates | % {$_.EnumObjectPermissions($itm.UserName)} |
| 157 | + if ($p -ne $null) |
| 158 | + { $ObjPerms += @{DBName=$itm.DBName; Permission=$p};} |
| 159 | + |
| 160 | + #AsymmetricKeys |
| 161 | + $p = $svr.Databases[$itm.DBName].AsymmetricKeys | % {$_.EnumObjectPermissions($itm.UserName)} |
| 162 | + if ($p -ne $null) |
| 163 | + { $ObjPerms += @{DBName=$itm.DBName; Permission=$p}; } |
| 164 | + |
| 165 | + #SymmetricKeys |
| 166 | + $p = $svr.Databases[$itm.DBName].SymmetricKeys | % {$_.EnumObjectPermissions($itm.UserName)} |
| 167 | + if ($p -ne $null) |
| 168 | + { $ObjPerms += @{DBName=$itm.DBName; Permission=$p};} |
| 169 | + |
| 170 | + #XMLSchemaCollections |
| 171 | + $p = $svr.Databases[$itm.DBName].XMLSchemaCollections | % {$_.EnumObjectPermissions($itm.UserName)} |
| 172 | + if ($p -ne $null) |
| 173 | + { $ObjPerms += @{DBName=$itm.DBName; Permission=$p};} |
| 174 | + |
| 175 | + #service broker components |
| 176 | + $p = $svr.Databases[$itm.DBName].ServiceBroker.MessageTypes | % {$_.EnumObjectPermissions($itm.UserName)} |
| 177 | + if ($p -ne $null) |
| 178 | + { $ObjPerms += @{DBName=$itm.DBName; Permission=$p};} |
| 179 | + |
| 180 | + $p = $svr.Databases[$itm.DBName].ServiceBroker.Routes | % {$_.EnumObjectPermissions($itm.UserName)} |
| 181 | + if ($p -ne $null) |
| 182 | + { $ObjPerms += @{DBName=$itm.DBName; Permission=$p};} |
| 183 | + |
| 184 | + $p = $svr.Databases[$itm.DBName].ServiceBroker.ServiceContracts | % {$_.EnumObjectPermissions($itm.UserName)} |
| 185 | + if ($p -ne $null) |
| 186 | + { $ObjPerms += @{DBName=$itm.DBName; Permission=$p};} |
| 187 | + |
| 188 | + $p = $svr.Databases[$itm.DBName].ServiceBroker.Services | % {$_.EnumObjectPermissions($itm.UserName)} |
| 189 | + if ($p -ne $null) |
| 190 | + { $ObjPerms += @{DBName=$itm.DBName; Permission=$p};} |
| 191 | + |
| 192 | + #Full text |
| 193 | + $p = $svr.Databases[$itm.DBName].FullTextCatalogs | % {$_.EnumObjectPermissions($itm.UserName)} |
| 194 | + if ($p -ne $null) |
| 195 | + { $ObjPerms += @{DBName=$itm.DBName; Permission=$p};} |
| 196 | + |
| 197 | + $p = $svr.Databases[$itm.DBName].FullTextStopLists | % {$_.EnumObjectPermissions($itm.UserName)} |
| 198 | + if ($p -ne $null) |
| 199 | + { $ObjPerms += @{DBName=$itm.DBName; Permission=$p};} |
| 200 | + } |
| 201 | + |
| 202 | + |
| 203 | + #generate t-sql to apply permission using SMO only |
| 204 | + #[string]$str = ([System.String]::Empty) |
| 205 | + foreach ($pr in $ObjPerms) |
| 206 | + { |
| 207 | + |
| 208 | + $h = @{Server=$sqlinstance; DBName=$($pr.DBName); sqlcmd=""}; |
| 209 | + $str = "" #"use $($pr.DBName) `r`n" |
| 210 | + foreach ($p in $pr.Permission) |
| 211 | + { |
| 212 | + [string]$op_state = $p.PermissionState; |
| 213 | + |
| 214 | + if ($p.ObjectClass -ne "ObjectOrColumn") |
| 215 | + { |
| 216 | + [string] $schema = ""; |
| 217 | + |
| 218 | + if ($p.ObjectSchema -ne $null) |
| 219 | + { $schema = "$($p.ObjectSchema)."} |
| 220 | + |
| 221 | + [string]$option = ""; |
| 222 | + |
| 223 | + if ($op_state -eq "GRANTwithGrant") |
| 224 | + { |
| 225 | + $op_state = 'GRANT'; |
| 226 | + $option = ' WITH GRANT OPTION'; |
| 227 | + } |
| 228 | + |
| 229 | + |
| 230 | + Switch ($p.ObjectClass) |
| 231 | + { |
| 232 | + 'Database' { $str += "$op_state $($p.PermissionType) to [$newUser]$option;`r`n";} |
| 233 | + 'SqlAssembly' { $str += "$op_state $($p.PermissionType) ON Assembly::$($schema)$($p.ObjectName) to [$newUser]$option;`r`n";} |
| 234 | + 'Schema' { $str += "$op_state $($p.PermissionType) ON SCHEMA::$($schema)$($p.ObjectName) to [$newUser]$option;`r`n";} |
| 235 | + 'UserDefinedType' { $str += "$op_state $($p.PermissionType) ON TYPE::$($schema)$($p.ObjectName) to [$newUser]$option;`r`n";} |
| 236 | + 'AsymmetricKey' { $str += "$op_state $($p.PermissionType) ON ASYMMETRIC KEY::$($schema)$($p.ObjectName) to [$newUser]$option;`r`n";} |
| 237 | + 'SymmetricKey' { $str += "$op_state $($p.PermissionType) ON SYMMETRIC KEY::$($schema)$($p.ObjectName) to [$newUser]$option;`r`n";} |
| 238 | + 'Certificate' { $str += "$op_state $($p.PermissionType) ON Certificate::$($schema)$($p.ObjectName) to [$newUser]$option`r`n";} |
| 239 | + 'XmlNamespace' { $str += "$op_state $($p.PermissionType) ON XML SCHEMA COLLECTION::$($schema)$($p.ObjectName) to [$newUser]$option`r`n";} |
| 240 | + 'FullTextCatalog' { $str += "$op_state $($p.PermissionType) ON FullText Catalog::$($schema)[$($p.ObjectName)] to [$newUser]$option`r`n";} |
| 241 | + 'FullTextStopList' { $str += "$op_state $($p.PermissionType) ON FullText Stoplist::$($schema)[$($p.ObjectName)] to [$newUser]$option`r`n";} |
| 242 | + 'MessageType' { $str += "$op_state $($p.PermissionType) ON Message Type::$($schema)[$($p.ObjectName)] to [$newUser]$option`r`n";} |
| 243 | + 'ServiceContract' { $str += "$op_state $($p.PermissionType) ON Contract::$($schema)[$($p.ObjectName)] to [$newUser]$option`r`n";} |
| 244 | + 'ServiceRoute' { $str += "$op_state $($p.PermissionType) ON Route::$($schema)[$($p.ObjectName)] to [$newUser]$option`r`n";} |
| 245 | + 'Service' { $str += "$op_state $($p.PermissionType) ON Service::$($schema)[$($p.ObjectName)] to [$newUser]$option`r`n";} |
| 246 | + #you can add other stuff like Available Group etc in this switch block as well |
| 247 | + }#switch |
| 248 | + |
| 249 | + } |
| 250 | + else |
| 251 | + { |
| 252 | + [string]$col = "" #if grant is on column level, we need to capture it |
| 253 | + if ($p.ColumnName -ne $null) |
| 254 | + { $col = "($($p.ColumnName))"}; |
| 255 | + |
| 256 | + $str += "$op_state $($p.PermissionType) ON Object::$($p.ObjectSchema).$($p.ObjectName) $col to [$newUser];`r`n"; |
| 257 | + }#else |
| 258 | + |
| 259 | + } |
| 260 | + #$str += "go`r`n"; |
| 261 | + $h.sqlcmd = $str; |
| 262 | + $hta += $h; |
| 263 | + } |
| 264 | + |
| 265 | + |
| 266 | + }#loop $ServerInstance |
| 267 | + } #process block |
| 268 | + End |
| 269 | + { |
| 270 | + [string] $sqlcmd = ""; |
| 271 | + |
| 272 | + if ($FilePath.Length -gt 3) # $FilePath is provided |
| 273 | + { |
| 274 | + [string]$servername=""; |
| 275 | + |
| 276 | + foreach ($h in $hta) |
| 277 | + { |
| 278 | + if ($h.Server -ne $Servername) |
| 279 | + { |
| 280 | + $ServerName=$h.Server; |
| 281 | + $sqlcmd += ":connect $servername `r`n" |
| 282 | + } |
| 283 | + |
| 284 | + $sqlcmd += "use $($h.DBName);`r`n" + $h.sqlcmd +"`r`ngo`r`n"; |
| 285 | + |
| 286 | + } |
| 287 | + $sqlcmd | out-file -FilePath $FilePath -Append ; |
| 288 | + } |
| 289 | + |
| 290 | + if ($Execute) |
| 291 | + { |
| 292 | + foreach ($h in $hta) |
| 293 | + { |
| 294 | + $server = new-object "Microsoft.sqlserver.management.smo.server" $h.Server; |
| 295 | + $database = $server.databases[$h.DBName]; |
| 296 | + $database.ExecuteNonQuery($h.sqlcmd) |
| 297 | + } |
| 298 | + } #$Execute |
| 299 | + |
| 300 | + }#end block |
| 301 | +} #clone-sqllogin |
| 302 | + |
| 303 | +# test, change parameters to your own. The following creates a script about all permissions assigned to [Bobby] |
| 304 | +# Clone-SQLLogin -Server "$env:ComputerName", "$env:ComputerName\sql2014" -OldLogin Bobby -NewLogin Bobby -FilePath "c:\temp\Bobby_perm.sql"; |
0 commit comments