1
+ <#
2
+ . SYNOPSIS
3
+ Given a new [file].bacpac, creates a new [file]-patched.bacpac with master key information removed from the model.xml and origin.xml.
4
+ . DESCRIPTION
5
+ When exporting a .bacpac from Azure SQLDB with auditing enabled, the .bacpac will contain a master key without a password in the model.xml. A
6
+ master key without a password is an Azure SQLDB only feature, so it's presence prevents being able to import the .bacpac into an on-premise
7
+ SQL Server database. This script works around this limitation by extracting the model.xml and origin.xml from the .bacpac, removing the references
8
+ to the master key, and creating a new .bacpac with the updated model.xml and origin.xml. The resulting .bacpac can then be imported to an on-premise
9
+ database.
10
+ . EXAMPLE
11
+ C:\PS> .\RemoveMasterKey.ps1 -bacpacPath "C:\BacPacs\Test.bacpac" # Generates a Test-patched.bacpac file
12
+ . PARAMETER bacpacPath
13
+ Specifies the path the .bacpac to patch.
14
+ #>
15
+ param (
16
+ [Parameter (Mandatory = $true , HelpMessage = " Specifies the path the .bacpac. This file will not be modified." )]
17
+ [string ]$bacpacPath
18
+ )
19
+
20
+
21
+ if ($PSVersionTable.PSVersion.Major -lt 4 ) {
22
+ Write-Host " Unsupported powershell version. This script requires powershell version 4.0 or later"
23
+ return
24
+ }
25
+
26
+
27
+ Add-Type - Assembly System.IO.Compression.FileSystem
28
+
29
+
30
+ $targetBacpacPath = [System.IO.Path ]::Combine(
31
+ [System.IO.Path ]::GetDirectoryName($bacpacPath ),
32
+ [System.IO.Path ]::GetFileNameWithoutExtension($bacpacPath ) + " -patched" + [System.IO.Path ]::GetExtension($bacpacPath ))
33
+ $originXmlFile = [System.IO.Path ]::Combine([System.IO.Path ]::GetDirectoryName($targetBacpacPath ), " Origin.xml" )
34
+ $modelXmlFile = [System.IO.Path ]::Combine([System.IO.Path ]::GetDirectoryName($targetBacpacPath ), " model.xml" )
35
+
36
+
37
+ if ([System.IO.File ]::Exists($targetBacpacPath )) {
38
+ [System.IO.File ]::Delete($targetBacpacPath )
39
+ }
40
+
41
+
42
+ #
43
+ # Extract the model.xml and Origin.xml from the .bacpac
44
+ #
45
+ $zip = [System.IO.Compression.ZipFile ]::OpenRead($bacpacPath )
46
+ foreach ($entry in $zip.Entries ) {
47
+ if ([string ]::Compare($entry.Name , " model.xml" , $True ) -eq 0 ) {
48
+ [System.IO.Compression.ZipFileExtensions ]::ExtractToFile($entry , $modelXmlFile , $true )
49
+ break
50
+ }
51
+ }
52
+ foreach ($entry in $zip.Entries ) {
53
+ if ([string ]::Compare($entry.Name , " Origin.xml" , $True ) -eq 0 ) {
54
+ [System.IO.Compression.ZipFileExtensions ]::ExtractToFile($entry , $originXmlFile , $true )
55
+ break
56
+ }
57
+ }
58
+ $zip.Dispose ()
59
+
60
+
61
+ if (! [System.IO.File ]::Exists($modelXmlFile )) {
62
+ Write-Host " Could not extract the model.xml from file " + $bacpacPath
63
+ return
64
+ }
65
+ if (! [System.IO.File ]::Exists($originXmlFile )) {
66
+ Write-Host " Could not extract the Origin.xml from file " + $bacpacPath
67
+ return
68
+ }
69
+
70
+
71
+ #
72
+ # Modify the model.xml
73
+ #
74
+ [xml ]$modelXml = Get-Content $modelXmlFile
75
+ $ns = New-Object System.Xml.XmlNamespaceManager($modelXml.NameTable )
76
+ $ns.AddNamespace (" x" , $modelXml.DocumentElement.NamespaceURI )
77
+
78
+
79
+ $masterKeyNodes = $modelXml.SelectNodes (" //x:DataSchemaModel/x:Model/x:Element[@Type='SqlMasterKey']" , $ns )
80
+ foreach ($masterKeyNode in $masterKeyNodes ) {
81
+ $masterKeyNode.ParentNode.RemoveChild ($masterKeyNode )
82
+ }
83
+
84
+
85
+ $sqlDatabaseCredentialNodes = $modelXml.SelectNodes (" //x:DataSchemaModel/x:Model/x:Element[@Type='SqlDatabaseCredential']" , $ns )
86
+ foreach ($sqlDatabaseCredentialNode in $sqlDatabaseCredentialNodes ) {
87
+ if ($sqlDatabaseCredentialNode.Property.Name -eq " Identity" -and $sqlDatabaseCredentialNode.Property.Value -eq " SHARED ACCESS SIGNATURE" )
88
+ {
89
+ $sqlDatabaseCredentialNode.ParentNode.RemoveChild ($sqlDatabaseCredentialNode )
90
+ }
91
+ }
92
+
93
+
94
+ $modelXml.Save ($modelXmlFile )
95
+
96
+
97
+ #
98
+ # Modify the Origin.xml
99
+ #
100
+ [xml ]$originXml = Get-Content $originXmlFile
101
+ $ns = New-Object System.Xml.XmlNamespaceManager($originXml.NameTable )
102
+ $ns.AddNamespace (" x" , $originXml.DocumentElement.NamespaceURI )
103
+
104
+
105
+ $databaseCredentialNode = $originXml.SelectSingleNode (" //x:DacOrigin/x:Server/x:ObjectCounts/x:DatabaseCredential" , $ns )
106
+ if ($databaseCredentialNode ) {
107
+ if ($databaseCredentialNode.InnerText -eq " 1" ) {
108
+ $databaseCredentialNode.ParentNode.RemoveChild ($databaseCredentialNode )
109
+ } else {
110
+ $databaseCredentialNode.InnerText = $databaseCredentialNode.Value - 1
111
+ }
112
+ }
113
+
114
+
115
+ $masterKeyNode = $originXml.SelectSingleNode (" //x:DacOrigin/x:Server/x:ObjectCounts/x:MasterKey" , $ns )
116
+ if ($masterKeyNode ) {
117
+ $masterKeyNode.ParentNode.RemoveChild ($masterKeyNode )
118
+ }
119
+
120
+
121
+ $modelXmlHash = (Get-FileHash $modelXmlFile - Algorithm SHA256).Hash
122
+ $checksumNode = $originXml.SelectSingleNode (" //x:DacOrigin/x:Checksums/x:Checksum" , $ns )
123
+ if ($checksumNode ) {
124
+ $checksumNode.InnerText = $modelXmlHash
125
+ }
126
+
127
+
128
+ $originXml.Save ($originXmlFile )
129
+
130
+
131
+ #
132
+ # Create the new .bacpac using the patched model.xml and Origin.xml
133
+ #
134
+ $zipSource = [System.IO.Compression.ZipFile ]::OpenRead($bacpacPath )
135
+ $zipTarget = [System.IO.Compression.ZipFile ]::Open($targetBacpacPath , " Create" )
136
+ foreach ($entry in $zipSource.Entries ) {
137
+ if ([string ]::Compare($entry.Name , " Origin.xml" , $True ) -eq 0 ) {
138
+ [System.IO.Compression.ZipFileExtensions ]::CreateEntryFromFile($zipTarget , $originXmlFile , $entry.FullName )
139
+ } elseif ([string ]::Compare($entry.Name , " model.xml" , $True ) -eq 0 ) {
140
+ [System.IO.Compression.ZipFileExtensions ]::CreateEntryFromFile($zipTarget , $modelXmlFile , $entry.FullName )
141
+ } else {
142
+ $targetEntry = $zipTarget.CreateEntry ($entry.FullName )
143
+ $sourceStream = $null
144
+ $targetStream = $null
145
+ try {
146
+ $sourceStream = [System.IO.Stream ]$entry.Open ()
147
+ $targetStream = [System.IO.Stream ]$targetEntry.Open ()
148
+ $sourceStream.CopyTo ($targetStream )
149
+ }
150
+ finally {
151
+ if ($targetStream -ne $null ) {
152
+ $targetStream.Dispose ()
153
+ }
154
+ if ($sourceStream -ne $null ) {
155
+ $sourceStream.Dispose ()
156
+ }
157
+ }
158
+ }
159
+ }
160
+ $zipSource.Dispose ()
161
+ $zipTarget.Dispose ()
162
+
163
+
164
+ [System.IO.File ]::Delete($modelXmlFile )
165
+ [System.IO.File ]::Delete($originXmlFile )
166
+
167
+
168
+ Write-Host " Completed update to the model.xml and Origin.xml in file" ([System.IO.Path ]::GetFullPath($targetBacpacPath ))
0 commit comments