Showing posts with label deploy. Show all posts
Showing posts with label deploy. Show all posts

Tuesday, June 21, 2016

PowerShell script to execute an Entity Framework Migration

I got sick and tired of coming up with hacks for including Entity Framework migrations as part of my deployment processes, so I came up with a much cleaner hack in the form of a PowerShell script that can be used to execute a database migration given an assembly and a connection string. This script does have one caveat though: for some reason if it's run more than once in a given PowerShell session, it causes a stack overflow exception, and I haven't been able to figure out why. I guess I'll have to leave it as an exercise to you. Have at it:

Param(
    [ValidateNotNullOrEmpty()][string]$assemblyFile,
[ValidateNotNullOrEmpty()][string]$connectionString
#    [ValidateNotNullOrEmpty()][string]$server,
#    [ValidateNotNullOrEmpty()][string]$databaseName,
#    [ValidateNotNullOrEmpty()][string]$userName,
#    [ValidateNotNullOrEmpty()][string]$password
)

$assemblyFolder = [System.IO.Path]::GetDirectoryName($assemblyFile)

$OnAssemblyResolve = [System.ResolveEventHandler] {
  param($sender, $e)

  # First load the assemblies already in our app domain
  foreach($a in [System.AppDomain]::CurrentDomain.GetAssemblies())
  {
    if ($a.FullName -eq $e.Name)
    {
      return $a
    }
  }

  $fn = Join-Path $assemblyFolder "$($e.Name.Split(',')[0]).dll"

  if (Test-Path $fn)
  {
      $ass = [Reflection.Assembly]::LoadFile($fn)
      return $ass
  }

  return $null
}

[System.AppDomain]::CurrentDomain.add_AssemblyResolve($OnAssemblyResolve)

Set-Location ($assemblyFolder)

[System.Reflection.Assembly] $assembly = [System.Reflection.Assembly]::LoadFile($assemblyFile)

$types = $null

try
{
    $types = $assembly.GetTypes()
}
catch [Exception]
{
    $ErrorMessage = $_.Exception.Message

    throw "Failed to load types from assembly"
}

$configurationType = [System.Type]::GetType("System.Data.Entity.Migrations.DbMigrationsConfiguration, EntityFramework")
$migratorType = [System.Type]::GetType("System.Data.Entity.Migrations.DbMigrator, EntityFramework")
$connectionInfoType = [System.Type]::GetType("System.Data.Entity.Infrastructure.DbConnectionInfo, EntityFramework")

if ($configurationType -eq $null -or $migratorType -eq $null -or $connectionInfoType -eq $null)
{
    throw "Failed to load entity framework"
}

$migrationConfigurationTypes = @($types | ? { $configurationType.IsAssignableFrom($_) })

if ($migrationConfigurationTypes.Length -ne 1)
{
    throw "Failed to find single migration type for Entity framework in the assembly to migrate, found $($migrationConfigurationTypes.Length) migration configuration types"
}

Write-Warning "If you see an error similar to 'Could not find a connection string named [connection string name] in the application config', try changing your DbContext descendent to use the DbContext('connection string name') constructor syntax instead of DbContext('name=connection string name'). This is a known bug in EntityFramework."

#$builder = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
# Need to use psbase, otherwise these properties throw an exception
#$builder.psbase.DataSource = $server
#$builder.psbase.InitialCatalog = $databaseName
#$builder.psbase.UserID = $userName
#$builder.psbase.Password = $password

#$connectionString = $builder.ToString()

$configuration = [System.Activator]::CreateInstance($migrationConfigurationTypes[0])
$connectionInfo = New-Object $connectionInfoType ($connectionString, "System.Data.SqlClient")
$configuration.TargetDatabase = $connectionInfo
$migrator = [System.Activator]::CreateInstance($migratorType, @($configuration))
$migrator.Update()

Write-Host 'Successfully migrated database'


Wednesday, March 30, 2016

Updating your Azure AD Application Manifest

We've recently found that as we develop more applications in Azure, we need to put safeguards on the deployment of the applications to ensure that they're configured correctly. Part of this means editing the application manifests to ensure that certain settings are always enforced on certain applications. Here's what Microsoft has to say about updating Application manifests.

Bottom line, if you want to automate anything to do with manifests, you'll have to write your own application to use the Azure Graph API libraries to retrieve the manifest / application settings and edit them.

Monday, October 05, 2015

SqlPackage.exe fails to deploy to Azure with error "The database platform service with type Microsoft.Data.Tools.Schema.Sql.SqlAzureV12DatabaseSchemaProvider is not valid."

I've recently started trying to deploy to one of my own databases in Azure using SqlPackage.exe. I've used it numerous times at work without problem, but on my own system at home, I keep running into the following error:

"Internal Error. The database platform service with type Microsoft.Data.Tools.Schema.Sql.SqlAzureV12DatabaseSchemaProvider is not valid. You must make sure the service is loaded, or you must provide the full type name of a valid database platform service."

As it turns out, you need at least SSMS CU#6 to get a version of SqlPackage.exe recent enough to deploy to Azure with SQL Azure v12. The copyright on the SqlPackage.exe executable must be 2015 (or later). Such a version comes with the latest versions of SQL Server Data Tools (SSDT). If you're unable to use the version of SqlPackage.exe that comes with SQL Server 2014 because it's too old ("C:\Program Files (x86)\Microsoft SQL Server\120\DAC\bin\SqlPackage.exe"), you can use the version that comes with SSDT for Visual Studio 2013 ("C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\120\sqlpackage.exe").

Tuesday, August 25, 2015

Parameterizing ApplicationInsights.config

I've been working on a lot of web applications in Azure recently, and we're using Application Insights to analyze logs and instrument events in the application. Additionally, we're using MSDeploy to deploy all of our applications as we're trying to get to the point of having Continuous Delivery on-premise. Because we use multiple environments, we need to use the parameters.xml file and SetParameters files to set parameters at deployment time, meaning that we also need to parameterize the InstrumentationKey used by Application Insights to determine which instance of App Insights gets the instrumentation for which application.

This is a bit of a challenge because the XPath support for the parameters.xml file is incomplete and leaves something to be desired. At first glance, I tried to use the naive XPath expression to parameterize our InstrumentationKey:

Match="/ApplicationInsights/InstrumentationKey"

I realized that this wasn't working after checking our deployments, so after some Googling, I found the recommendation that to parameterize an *element* rather than an attribute, you need to use "/text()" in your XPath expression (which makes sense if you know enough about XPath and XQuery). So I tried it:

Match="/ApplicationInsights/InstrumentationKey/text()"

Still didn't work. Then I was stumped for a while and let things simmer.

After a couple more hours and getting frustrated, it hit me: when parameterizing another element a few weeks prior, I ran into issues because a parent element of the one that I wanted to parameterize overrode the default namespace. I checked the ApplicationInsights.config file, and sure enough, the default namespace was the non-empty namespace. I had to use a wildcard to solve the previous problem, so I tried the following:

Match="/*/InstrumentationKey/text()"

Still didn't work, much to my surprise this time. After a little bit more Googling around, I found out that this time it was because the *root* element itself used a non-empty namespace, and overrode the default. I stumbled across this response by Vishal Joshi to a comment on one of his blog posts, which included a suggestion for it using something I didn't even know you could do with XPath (and I've actually got quite a bit of experience using XPath): use the local-name query operator to match only on local name rather than fully qualified name. If you're querying a document that uses an empty default namespace, they're both the same, hence why the simple match expressions work 99% of the time. Updating my query to this:

Match="//*[local-name()='InstrumentationKey']/text()"

... worked like a charm!

Wednesday, August 19, 2015

A really quick note about deploying SQL Server .dacpac files to SQL Azure

When you're using SqlPackage.exe to deploy a .dacpac to Azure, you must have access to the 'master' table in order for the tool to retrieve information necessary to create a diff of the database and deploy the differences during the Publish action.

For that reason, you need the 'sa' account, because simply having 'db_owner' on the Azure database is not sufficient.

Tuesday, August 18, 2015

Debugging database deployments with MSDeploy and SqlPackage

I recently had problems deploying a database to Azure, and thanks to this article, I now know why. The best part of this experience is that I've learned to debug this in a much easier manner than relying on the unhelpful error message:

*** An error occurred during deployment plan generation. Deployment cannot continue.
Failed to import target model my-database-name. Detailed message Value cannot be null.
Parameter name: conn
Value cannot be null.
Parameter name: conn

The short of it is that:

1) you need to enable a log trace with these commands for dbDacFx and SSDT:

logman create trace -n DacFxDebug -p "Microsoft-SQLServerDataTools" 0x800 -o "%LOCALAPPDATA%\DacFxDebug.etl" -ets

logman create trace -n SSDTDebug -p "Microsoft-SQLServerDataToolsVS" 0x800 -o "%LOCALAPPDATA%\SSDTDebug.etl" -ets

2) Go execute the command that's causing you grief
3) Execute the following commands to stop the logging trace:

logman stop DacFxDebug -ets

logman stop SSDTDebug -ets

4) Open 'eventvwr' in the Run dialog available from the Start Menu
5) Go to the right side 'Actions' pane, and click on 'Open Saved Log'.
6) When prompted to convert the log, click 'No'.

The logs will display under the 'Saved Logs' folder in the left hand navigation tree, and you'll be able to inspect the errors that occurred. Good luck with deployment!