Skip to content

Instantly share code, notes, and snippets.

@JohnLBevan
Last active August 3, 2018 14:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JohnLBevan/6a8876f71a25aa600254d7127cf1a819 to your computer and use it in GitHub Desktop.
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
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
@JohnLBevan
Copy link
Author

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