Автоматический редеплоймент сборки. PS1

Я ожидал, что в комментах к предыдущему посту какая-нибудь добрая душа выложит Powershellьный вариант кода, и я с радостью им воспользуюсь, чтобы всякий раз не открывать Visual Studio. Нет, никто не сподобился. Пассивная масса.

Пришлось самому на старости лет засучать рукава и, кряхтя, осваивать PowerShell. Какой забавный язык! (с) граф типа отец Боярского в "Собаке на сене".

Ну, не взыщите. Приведенный ниже скрипт на PowerShell является аналогом сишарпейного кода "Автоматический редеплоймент сборки"\Скрипт 4.

Имеется dll, из которой на SQL Server по имени $srvName в базе $dbName сделана сборка по имени $asmName, а из нее разные процедуры и функции. В dll чего-нибудь поменяли, и сборку нужно передеплоить: убить все ее процедуры и функции, саму сборку, создать ее заново, пересоздать существовавшие на ее основе процедуры и функции. Если от ее процедур и функций успели стать зависимыми еще какие-нибудь объекты, цепочка, соответственно, удлиняется с двух сторон.

Если изменение в dll означало, что из нее удалились или добавились какие-нибудь методы, на основе которых существовали или предстоит создать SQLные модули, $scriptToCreate надо сохранить в файл, сделать возможность его откорректировать и уже потом выполнять.

[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out - Null

function Main()

{

cls

$srvName = ""; $dbName = "TestFS"; $asmName = "MyAssembly"

$srv = new-object -TypeName "Microsoft.SqlServer.Management.Smo.Server" -ArgumentList $srvName

$db = $srv.Databases[$dbName]

$asm = $db.Assemblies[$asmName];

$scr = New-Object -TypeName "Microsoft.SqlServer.Management.Smo.Scripter" -ArgumentList $srv

$scr.Options.SchemaQualify = $true #чтобы объекты скриптовались со своими схемами (dbo, ...)

$scr.Options.IncludeIfNotExists = $true #предваряются оператором if exists при удалении и if not exists при создании

$scr.Options.ScriptDrops = $true #создается скрипт на удаление

$scr.Options.WithDependencies = $true #при генерации скрипта на удаление означает, что в скрипт также включаются referencing objects, т.е. которые зависят от данного

$scr.Options.DriUniqueKeys = $true #таблице TestFTS требуется ограничение unique на rowguidcol, его также нужно заскриптовать

$sb = New-Object -TypeName "System.Text.StringBuilder"

$scr.Script(@($asm)) | ForEach-Object -Process { [Void] $sb.Append($_ + "`n`n") } #[void], чтобы избежать uncaptured output в ф-ции, к-й будет относиться к ее рез-там

$scriptToDrop = $sb.ToString(); $sb.Length = 0

$scr.Options.ScriptDrops = $false #генерируется скрипт на создание

$dt = $scr.DiscoverDependencies(@($asm), $false) #parent = false означает, что под связанными объектами понимаются не родительские, а дочерние, т.е. referencing

$dc = $scr.WalkDependencies($dt) #скрипт получается в порядке от дочерних объектов к родительским, что неправильно, т.к. сначала должен создаваться родительский объект, а уже потом дочерние на его основе

$dc1 = New-Object -TypeName "Microsoft.SqlServer.Management.Smo.DependencyCollection"

for ($i = $dc.Count; $i -gt 0; $i--) { [Void] $dc1.Add($dc[$i - 1]) } #переставляем в обратном порядке

$scr.ScriptWithList($dc1) | ForEach-Object -Process { [void] $sb.Append($_ + "`n`n") } #генерим скрипты от явно построенной коллекции связанных объектов

           

$scriptToCreate = $sb.ToString();

$scriptToCreate = ReplaceAssemblyBytesToFilePath $scriptToCreate $asm.SqlAssemblyFiles

Write - Host "Вы согласны с тем, что будут выполнены следующие скрипты:`n"

$scriptToDrop

$scriptToCreate

Write-Host "[Y/N]?"

[char] $key = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown").Character

if ($key.ToString().ToUpper() -eq "Y") { $db.ExecuteNonQuery($scriptToDrop); $db.ExecuteNonQuery($scriptToCreate) }

}

#<summary>

#Процедура производит замену в скрипте создания сборки

#create assembly ... from 0x4D5A900003000 ... with permission_set = ...

#бинарщины на пути к файлам этой сборки:

#create assembly ... from 'C:\...\bin\Debug\ClassLibrary1.dll' with permission_set ...

#</summary>

#<param name="scriptToCreate">

#Сгенеренный скрипт создания сборки и, возможно, других объектов.

#Однако предполагается, что в этом скрипте оператор create assembly один.

#В противном случае каждую сборку надо скриптовать отдельно.

#</param>

#<param name="files">Файлы из SqlAssembly.SqlAssemblyFiles</param>

#<returns></returns>

function ReplaceAssemblyBytesToFilePath($scriptToCreate, $files)

{

$pattern = new-object -TypeName "Text.RegularExpressions.Regex" -ArgumentList "create assembly \[\w+\]\r\nauthorization \[\w+\]\r\nfrom ", $([Text.RegularExpressions.RegExOptions]::IgnoreCase)

$match = $pattern.Match($scriptToCreate)

$sb = new-object -TypeName "System.Text.StringBuilder" -ArgumentList $scriptToCreate.Substring(0, $match.Index + $match.Length);

$files | ForEach-Object -Process { [Void] $sb.Append("'"); [Void] $sb.Append($_.Name); [Void] $sb.Append("', "); }

[Void] $sb.Remove($sb.Length - 2, 1) #убираем последнюю запятую (пробел оставляем)

$pattern = new-object -TypeName "Text.RegularExpressions.Regex" -ArgumentList "with permission_set", $([Text.RegularExpressions.RegexOptions]::IgnoreCase)

$match = $pattern.Match($scriptToCreate, $match.Index)

[Void] $sb.Append($scriptToCreate.Substring($match.Index))

return $sb.ToString()

}

Main