Last active
August 3, 2018 14:47
-
-
Save JohnLBevan/6a8876f71a25aa600254d7127cf1a819 to your computer and use it in GitHub Desktop.
Remove unsupported content from bacpac/dacpac files, to allow them to be loaded in older versions of SQL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function New-TemporaryDirectory { | |
[CmdletBinding()] | |
Param () | |
Process { | |
[string]$path = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.Guid]::NewGuid()) | |
New-Item -ItemType Directory -Path $path | |
} | |
} | |
function Update-BacPacChecksum { | |
[CmdletBinding()] | |
Param ( | |
#improve: allow a compressed bacpac to be sent & have this update the internal file (for now just written to support the below rather than to fix any corrupted bacpac's checksum | |
[Parameter(Mandatory = $true)] | |
[string]$ExtractedBacPacPath | |
) | |
Process { | |
[string]$modelPath = Join-Path -Path $tempPath -ChildPath 'model.xml' | |
[string]$originPath = Join-Path -Path $tempPath -ChildPath 'Origin.xml' | |
[System.Security.Cryptography.HashAlgorithm]$hasher = [System.Security.Cryptography.HashAlgorithm]::Create('System.Security.Cryptography.SHA256CryptoServiceProvider') | |
[System.IO.FileStream]$fileStream = [System.IO.FileStream]::new($modelPath, [System.IO.FileMode]::Open) | |
[byte[]]$hash = $hasher.ComputeHash($fileStream) | |
$fileStream.Close() | |
[string]$hashString = ($hash | %{$_.ToString('X2')}) -join '' | |
[xml]$origin = (Get-Content -Path $originPath -Raw) | |
[System.Xml.XmlNamespaceManager] $xnm = [System.Xml.XmlNamespaceManager]::new($origin.NameTable); | |
$xnm.AddNamespace('dac', 'http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02') | |
$origin.SelectSingleNode('/dac:DacOrigin/dac:Checksums/dac:Checksum[@Uri="/model.xml"]/text()', $xnm).Value = $hashString | |
$origin.Save($originPath) | |
} | |
} | |
function Convert-ToSimpleBacPac { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory = $true)] | |
[string]$SourceFilename | |
, | |
[switch]$RemoveColumnStoreIndex | |
, | |
[switch]$RemoveFullTextIndex | |
, | |
[int]$MaxColumnsPerIndex = ([int]::MaxValue-1) #removes any indexes which have too many columns to exist (doesn't just truncate, since that would impact unique indexes / I couldn't be bothered to get clever) | |
) | |
Process { | |
[string]$tempPath = (New-TemporaryDirectory).Fullname | |
[string]$modelPath = Join-Path -Path $tempPath -ChildPath 'model.xml' | |
Expand-Archive -Path $SourceFilename -DestinationPath $tempPath #in theory we could just grab the model from the bacpac, then overwrite that file; but for now going for speed of dev over speed of performance... | |
[xml]$model = (Get-Content -Path $modelPath -Raw) | |
[System.Xml.XmlNamespaceManager] $xnm = [System.Xml.XmlNamespaceManager]::new($model.NameTable); | |
$xnm.AddNamespace('dac', 'http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02') | |
if ($RemoveColumnStoreIndex.IsPresent) { | |
$model.SelectNodes("/dac:DataSchemaModel/dac:Model/dac:Element[@Type = 'SqlColumnStoreIndex']",$xnm) | %{$_.ParentNode.RemoveChild($_) | Out-Null} | |
} | |
if ($RemoveFullTextIndex.IsPresent) { | |
$model.SelectNodes("/dac:DataSchemaModel/dac:Model/dac:Element[@Type = 'SqlFullTextIndex']",$xnm) | %{$_.ParentNode.RemoveChild($_) | Out-Null} | |
} | |
$MaxColumnsPerIndex = $MaxColumnsPerIndex+1 | |
$model.SelectNodes("/dac:DataSchemaModel/dac:Model/dac:Element[@Type = 'SqlPrimaryKeyConstraint' or @Type = 'SqlIndex' or @Type = 'SqlUniqueConstraint' or @Type = 'SqlFullTextIndex'][./dac:Relationship/dac:Entry[ $MaxColumnsPerIndex ]]",$xnm) | %{$_.ParentNode.RemoveChild($_) | Out-Null} | |
<#NVENTQUALITYORDERCREATIONPOLICYSTAGING | |
Other values are available | |
$model.SelectNodes('/dac:DataSchemaModel/dac:Model/dac:Element/@Type',$xnm) | select -ExpandProperty value -Unique | |
SqlCheckConstraint | |
SqlDatabaseOptions | |
SqlDefaultConstraint | |
SqlPrimaryKeyConstraint | |
SqlRoleMembership | |
SqlSchema | |
SqlTable | |
SqlIndex | |
SqlFullTextIndex | |
SqlUniqueConstraint | |
SqlUser | |
SqlLogin | |
SqlFullTextCatalog | |
SqlTableType | |
SqlView | |
SqlProcedure | |
SqlInlineTableValuedFunction | |
SqlMultiStatementTableValuedFunction | |
SqlScalarFunction | |
SqlRole | |
SqlDmlTrigger | |
SqlColumnStoreIndex | |
SqlForeignKeyConstraint | |
SqlSequence | |
SqlPermissionStatement | |
#> | |
$model.Save($modelPath) | |
Update-BacPacChecksum -ExtractedBacPacPath $tempPath | |
Compress-Archive -Path $tempPath -DestinationPath "$SourceFilename.zip" | |
Rename-Item -Path "$SourceFilename.zip" -NewName $SourceFilename | |
Remove-Item -Path $tempPath | |
} | |
} | |
#Convert-ToSimpleBacPac -SourceFilename 'c:\temp\mybacpac.bacpac' -RemoveColumnStoreIndex -MaxColumnsPerIndex 16 #example usage |
Note: Compress-Archive and Expand-Archive could be replaced by the 7zip module's equivelant (Compress-7Zip -Path $sourcePath -ArchiveFileName $archivePath -Format Zip
). See https://www.powershellgallery.com/packages/7Zip4Powershell. This would give better performance as well as making potential out-of-memory issues less likely.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Caught out by the checksum :/
Thankfully there is a fix: