Skip to content

Commit 3ed951f

Browse files
Add Get/Set/Remove-DbaDbDataClassification commands (#10301)
1 parent d62acf6 commit 3ed951f

8 files changed

Lines changed: 971 additions & 0 deletions

dbatools.psd1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,9 @@
730730
'Get-DbaExtendedProperty',
731731
'Set-DbaExtendedProperty',
732732
'Add-DbaExtendedProperty',
733+
'Get-DbaDbDataClassification',
734+
'Set-DbaDbDataClassification',
735+
'Remove-DbaDbDataClassification',
733736
'Get-DbaOleDbProvider',
734737
'Get-DbaConnectedInstance',
735738
'Disconnect-DbaInstance',

dbatools.psm1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,9 @@ if ($PSVersionTable.PSVersion.Major -lt 5) {
920920
'Get-DbaExtendedProperty',
921921
'Set-DbaExtendedProperty',
922922
'Add-DbaExtendedProperty',
923+
'Get-DbaDbDataClassification',
924+
'Set-DbaDbDataClassification',
925+
'Remove-DbaDbDataClassification',
923926
'Get-DbaOleDbProvider',
924927
'Get-DbaConnectedInstance',
925928
'Disconnect-DbaInstance',
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
function Get-DbaDbDataClassification {
2+
<#
3+
.SYNOPSIS
4+
Retrieves data classification information for columns in SQL Server databases
5+
6+
.DESCRIPTION
7+
Retrieves data classification labels stored as extended properties on table columns. Data classification
8+
is used to tag sensitive data columns with information type and sensitivity labels, which helps with
9+
compliance, data governance, and security auditing.
10+
11+
Classification metadata is stored as four extended properties on each classified column:
12+
- sys_information_type_id: GUID identifying the information type
13+
- sys_information_type_name: Human-readable information type name (e.g., "Financial", "Health", "Credentials")
14+
- sys_sensitivity_label_id: GUID identifying the sensitivity label
15+
- sys_sensitivity_label_name: Human-readable sensitivity label (e.g., "Public", "General", "Confidential")
16+
17+
These properties are compatible with Microsoft Information Protection (MIP) labels used by SQL Server
18+
Data Discovery & Classification in SSMS and Azure SQL Database.
19+
20+
Requires SQL Server 2005 or later due to use of sys.extended_properties.
21+
22+
.PARAMETER SqlInstance
23+
The target SQL Server instance or instances.
24+
25+
.PARAMETER SqlCredential
26+
Login to the target instance using alternative credentials. Accepts PowerShell credentials (Get-Credential).
27+
28+
Windows Authentication, SQL Server Authentication, Active Directory - Password, and Active Directory -
29+
Integrated are all supported.
30+
31+
For MFA support, please use Connect-DbaInstance.
32+
33+
.PARAMETER Database
34+
Specifies which databases to search for data classifications. Only applies when connecting directly via SqlInstance.
35+
36+
.PARAMETER Schema
37+
Filters results to columns in the specified schema(s).
38+
39+
.PARAMETER Table
40+
Filters results to columns in the specified table(s).
41+
42+
.PARAMETER Column
43+
Filters results to the specified column name(s).
44+
45+
.PARAMETER InputObject
46+
Accepts database objects piped from Get-DbaDatabase.
47+
48+
.PARAMETER EnableException
49+
By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
50+
This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
51+
Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
52+
53+
.NOTES
54+
Tags: DataClassification, Classification, Compliance, Security
55+
Author: the dbatools team + Claude
56+
57+
Website: https://dbatools.io
58+
Copyright: (c) 2024 by dbatools, licensed under MIT
59+
License: MIT https://opensource.org/licenses/MIT
60+
61+
.LINK
62+
https://dbatools.io/Get-DbaDbDataClassification
63+
64+
.OUTPUTS
65+
PSCustomObject
66+
67+
Returns one object per classified column with the following properties:
68+
- ComputerName: The computer name of the SQL Server instance
69+
- InstanceName: The SQL Server instance name
70+
- SqlInstance: The full SQL Server instance name
71+
- Database: The database name
72+
- Schema: The schema name of the table
73+
- Table: The table name
74+
- Column: The column name
75+
- InformationTypeId: GUID identifying the information type
76+
- InformationType: Human-readable information type name
77+
- SensitivityLabelId: GUID identifying the sensitivity label
78+
- SensitivityLabel: Human-readable sensitivity label name
79+
80+
.EXAMPLE
81+
PS C:\> Get-DbaDbDataClassification -SqlInstance sql2019
82+
83+
Returns all data classifications across all databases on sql2019.
84+
85+
.EXAMPLE
86+
PS C:\> Get-DbaDbDataClassification -SqlInstance sql2019 -Database AdventureWorks
87+
88+
Returns all data classifications in the AdventureWorks database.
89+
90+
.EXAMPLE
91+
PS C:\> Get-DbaDbDataClassification -SqlInstance sql2019 -Database AdventureWorks -Table Customer
92+
93+
Returns data classifications for columns in the Customer table.
94+
95+
.EXAMPLE
96+
PS C:\> Get-DbaDatabase -SqlInstance sql2019 -Database AdventureWorks | Get-DbaDbDataClassification
97+
98+
Returns all data classifications in AdventureWorks by piping the database object.
99+
100+
.EXAMPLE
101+
PS C:\> Get-DbaDbDataClassification -SqlInstance sql2019 -Database AdventureWorks | Where-Object SensitivityLabel -eq "Highly Confidential"
102+
103+
Returns only columns classified as Highly Confidential in AdventureWorks.
104+
#>
105+
[CmdletBinding()]
106+
param (
107+
[DbaInstanceParameter[]]$SqlInstance,
108+
[PSCredential]$SqlCredential,
109+
[string[]]$Database,
110+
[string[]]$Schema,
111+
[string[]]$Table,
112+
[string[]]$Column,
113+
[parameter(ValueFromPipeline)]
114+
[Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
115+
[switch]$EnableException
116+
)
117+
process {
118+
if ($SqlInstance) {
119+
$InputObject = Get-DbaDatabase -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database | Where-Object IsAccessible
120+
}
121+
122+
foreach ($db in $InputObject) {
123+
$server = $db.Parent
124+
125+
if ($server.VersionMajor -lt 9) {
126+
Stop-Function -Message "Data classification requires SQL Server 2005 or later. Skipping $server" -Target $server -Continue
127+
}
128+
129+
$sql = "
130+
SELECT
131+
SCHEMA_NAME(o.schema_id) AS SchemaName,
132+
o.name AS TableName,
133+
c.name AS ColumnName,
134+
CAST(ep1.value AS NVARCHAR(MAX)) AS InformationTypeId,
135+
CAST(ep2.value AS NVARCHAR(MAX)) AS InformationType,
136+
CAST(ep3.value AS NVARCHAR(MAX)) AS SensitivityLabelId,
137+
CAST(ep4.value AS NVARCHAR(MAX)) AS SensitivityLabel
138+
FROM sys.objects o
139+
INNER JOIN sys.columns c ON o.object_id = c.object_id
140+
LEFT JOIN sys.extended_properties ep1
141+
ON ep1.major_id = o.object_id AND ep1.minor_id = c.column_id
142+
AND ep1.name = 'sys_information_type_id' AND ep1.class = 1
143+
LEFT JOIN sys.extended_properties ep2
144+
ON ep2.major_id = o.object_id AND ep2.minor_id = c.column_id
145+
AND ep2.name = 'sys_information_type_name' AND ep2.class = 1
146+
LEFT JOIN sys.extended_properties ep3
147+
ON ep3.major_id = o.object_id AND ep3.minor_id = c.column_id
148+
AND ep3.name = 'sys_sensitivity_label_id' AND ep3.class = 1
149+
LEFT JOIN sys.extended_properties ep4
150+
ON ep4.major_id = o.object_id AND ep4.minor_id = c.column_id
151+
AND ep4.name = 'sys_sensitivity_label_name' AND ep4.class = 1
152+
WHERE o.type = 'U'
153+
AND (ep1.value IS NOT NULL OR ep2.value IS NOT NULL OR ep3.value IS NOT NULL OR ep4.value IS NOT NULL)
154+
ORDER BY SCHEMA_NAME(o.schema_id), o.name, c.name"
155+
156+
try {
157+
$results = $db.Query($sql)
158+
} catch {
159+
Stop-Function -Message "Error querying data classifications in $($db.Name) on $server" -ErrorRecord $_ -Target $db -Continue
160+
}
161+
162+
foreach ($row in $results) {
163+
if ($Schema -and $row.SchemaName -notin $Schema) { continue }
164+
if ($Table -and $row.TableName -notin $Table) { continue }
165+
if ($Column -and $row.ColumnName -notin $Column) { continue }
166+
167+
[PSCustomObject]@{
168+
ComputerName = $db.ComputerName
169+
InstanceName = $db.InstanceName
170+
SqlInstance = $db.SqlInstance
171+
Database = $db.Name
172+
Schema = $row.SchemaName
173+
Table = $row.TableName
174+
Column = $row.ColumnName
175+
InformationTypeId = $row.InformationTypeId
176+
InformationType = $row.InformationType
177+
SensitivityLabelId = $row.SensitivityLabelId
178+
SensitivityLabel = $row.SensitivityLabel
179+
DatabaseObject = $db
180+
}
181+
}
182+
}
183+
}
184+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
function Remove-DbaDbDataClassification {
2+
<#
3+
.SYNOPSIS
4+
Removes data classification labels from SQL Server table columns
5+
6+
.DESCRIPTION
7+
Removes all data classification metadata from table columns by dropping the four extended properties:
8+
- sys_information_type_id
9+
- sys_information_type_name
10+
- sys_sensitivity_label_id
11+
- sys_sensitivity_label_name
12+
13+
Accepts piped input from Get-DbaDbDataClassification, making it easy to remove classifications
14+
from specific columns or bulk-remove classifications across databases.
15+
16+
Requires SQL Server 2005 or later due to use of sp_dropextendedproperty.
17+
18+
.PARAMETER InputObject
19+
Accepts classification objects piped from Get-DbaDbDataClassification.
20+
21+
.PARAMETER WhatIf
22+
Shows what would happen if the command were to run. No actions are actually performed.
23+
24+
.PARAMETER Confirm
25+
Prompts you for confirmation before executing any changing operations within the command.
26+
27+
.PARAMETER EnableException
28+
By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
29+
This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
30+
Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
31+
32+
.NOTES
33+
Tags: DataClassification, Classification, Compliance, Security
34+
Author: the dbatools team + Claude
35+
36+
Website: https://dbatools.io
37+
Copyright: (c) 2024 by dbatools, licensed under MIT
38+
License: MIT https://opensource.org/licenses/MIT
39+
40+
.LINK
41+
https://dbatools.io/Remove-DbaDbDataClassification
42+
43+
.OUTPUTS
44+
PSCustomObject
45+
46+
Returns one object per successfully processed column with these properties:
47+
- ComputerName: The computer name of the SQL Server instance
48+
- InstanceName: The SQL Server instance name
49+
- SqlInstance: The full SQL Server instance name
50+
- Database: The database name
51+
- Schema: The schema of the table
52+
- Table: The table name
53+
- Column: The column name
54+
- Status: The result of the removal operation
55+
56+
.EXAMPLE
57+
PS C:\> Get-DbaDbDataClassification -SqlInstance sql2019 -Database AdventureWorks | Remove-DbaDbDataClassification
58+
59+
Removes all data classifications from the AdventureWorks database.
60+
61+
.EXAMPLE
62+
PS C:\> Get-DbaDbDataClassification -SqlInstance sql2019 -Database AdventureWorks -Table Customer | Remove-DbaDbDataClassification -Confirm:$false
63+
64+
Removes data classifications from all classified columns in the Customer table without prompting.
65+
66+
.EXAMPLE
67+
PS C:\> Get-DbaDbDataClassification -SqlInstance sql2019 -Database AdventureWorks | Where-Object SensitivityLabel -eq "General" | Remove-DbaDbDataClassification
68+
69+
Removes only classifications with the "General" sensitivity label from AdventureWorks.
70+
#>
71+
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
72+
param (
73+
[parameter(ValueFromPipeline, Mandatory)]
74+
[psobject[]]$InputObject,
75+
[switch]$EnableException
76+
)
77+
process {
78+
foreach ($classObj in $InputObject) {
79+
$db = $classObj.DatabaseObject
80+
if (-not $db) {
81+
Stop-Function -Message "No database object found in input. Use Get-DbaDbDataClassification to get valid input objects." -Continue
82+
continue
83+
}
84+
85+
$server = $db.Parent
86+
$schemaName = $classObj.Schema
87+
$tableName = $classObj.Table
88+
$columnName = $classObj.Column
89+
$target = "[$schemaName].[$tableName].[$columnName] in $($db.Name) on $server"
90+
91+
if ($Pscmdlet.ShouldProcess($target, "Removing data classification")) {
92+
$escapedSchema = $schemaName.Replace("'", "''")
93+
$escapedTable = $tableName.Replace("'", "''")
94+
$escapedColumn = $columnName.Replace("'", "''")
95+
96+
$status = "Removed"
97+
$errors = @()
98+
99+
foreach ($propName in "sys_information_type_id", "sys_information_type_name", "sys_sensitivity_label_id", "sys_sensitivity_label_name") {
100+
$checkSql = "
101+
SELECT COUNT(1) AS PropExists
102+
FROM sys.extended_properties ep
103+
INNER JOIN sys.objects o ON ep.major_id = o.object_id
104+
INNER JOIN sys.columns c ON o.object_id = c.object_id AND ep.minor_id = c.column_id
105+
WHERE SCHEMA_NAME(o.schema_id) = '$escapedSchema'
106+
AND o.name = '$escapedTable'
107+
AND c.name = '$escapedColumn'
108+
AND ep.name = '$propName'
109+
AND ep.class = 1"
110+
111+
try {
112+
$exists = $db.Query($checkSql).PropExists
113+
if ($exists -gt 0) {
114+
$dropSql = "EXEC sys.sp_dropextendedproperty @name = N'$propName', @level0type = N'SCHEMA', @level0name = N'$escapedSchema', @level1type = N'TABLE', @level1name = N'$escapedTable', @level2type = N'COLUMN', @level2name = N'$escapedColumn'"
115+
$db.Query($dropSql)
116+
}
117+
} catch {
118+
$errors += $propName
119+
Write-Message -Level Warning -Message "Failed to drop extended property '$propName' from $target : $_"
120+
}
121+
}
122+
123+
if ($errors.Count -gt 0) {
124+
$status = "Partial - failed to remove: $($errors -join ', ')"
125+
}
126+
127+
[PSCustomObject]@{
128+
ComputerName = $classObj.ComputerName
129+
InstanceName = $classObj.InstanceName
130+
SqlInstance = $classObj.SqlInstance
131+
Database = $classObj.Database
132+
Schema = $schemaName
133+
Table = $tableName
134+
Column = $columnName
135+
Status = $status
136+
}
137+
}
138+
}
139+
}
140+
}

0 commit comments

Comments
 (0)