{"id":2079,"date":"2015-09-09T00:00:00","date_gmt":"2015-08-24T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/writing-build-vnext-tasks-for-visual-studio-online\/"},"modified":"2021-04-27T13:41:10","modified_gmt":"2021-04-27T13:41:10","slug":"writing-build-vnext-tasks-for-visual-studio-online","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/devops\/database-devops\/writing-build-vnext-tasks-for-visual-studio-online\/","title":{"rendered":"Writing Build vNext tasks for Visual Studio Online"},"content":{"rendered":"<div id=\"pretty\">\n<h2>Background<\/h2>\n<p>Visual  Studio Online (VSO) is perhaps Microsoft&#8217;s worst-named tool. It&#8217;s NOT hosted  Visual Studio. It&#8217;s hosted TFS 2015. As with Team Foundation Server (TFS), it&#8217;s  a source-control server, a build server, and a project-planner. VSO lacks a few  features of on-site TFS, but essentially it&#8217;s the same. Microsoft is now  adopting a &#8216;cloud-first&#8217; attitude &#8211; building new features into VSO first and  then releasing them on-site &#8211; so VSO is certainly no second-class citizen.<\/p>\n<p>TFS  traditionally uses XAML definitions to define a build process. These are written  in Visual Studio using a Workflow-style visual editor. This is a powerful but  notoriously difficult process. Very few people bother, usually relying on the  default templates.<\/p>\n<p> Microsoft has created a new way of writing build process for VSO, currently  known as &#8216;<a href=\"https:\/\/msdn.microsoft.com\/Library\/vs\/alm\/Build\/feature-overview#Buildforyourplatform_speakingyourlanguage\">Build  vNext<\/a>&#8216;. This new  method will be very familiar for people working with build-systems such as  Bamboo, TeamCity or Jenkins. On the VSO website, you create a build definition  then click to add a few tasks (e.g. msbuild, run a command, start an Azure  website, etc). You fill in the details of these tasks, then hit Queue Build. On  the website you also setup options like build-triggering and build-variables.<\/p>\n<p class=\"illustration\"> <a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2272-VsoSQLCIBlog_Screenshot1.png\"> <img loading=\"lazy\" decoding=\"async\" height=\"368\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2272-VsoSQLCIBlog_Screenshot1.png\" width=\"619\" alt=\"2272-VsoSQLCIBlog_Screenshot1.png\" \/><\/a><\/p>\n<p> Microsoft has declared that all new projects should use Build vNext, so they  clearly consider it the future of TFS.<\/p>\n<h2>Writing TFS Build Tasks<\/h2>\n<p>What I  want to talk about today is writing VSO Agent Tasks. These tasks are the  fundamental steps in a build vNext build process, such as running MSBuild,  deploying an Azure Cloud Service, or running a PowerShell Script. There are  currently about 30 built-in tasks.<\/p>\n<p>At  Redgate Software, we make tools for SQL Developers and DBAs. Redgate SQL CI  enables you to build, test, synchronize and publish databases. It&#8217;s a  command-line tool, so can be invoked as part of a build by adding a PowerShell  or Command Line step that invokes something like:<\/p>\n<pre>sqlCI.exe Build \/scriptsFolder=WidgetShop\\Database\\Scriptsfolder \/packageId=WidgetShop \/packageVersion=1.0 \/temporaryDatabaseServer=(localdb)\\v11.0<\/pre>\n<p>This is a bit verbose, but  perhaps tolerable. But what if I also want to add error-checking or some  PowerShell? And re-use it all over? And what happens if the command-line changes  slightly, and I have to update command-line invocations all over the place?  Writing a custom build-task is a solution.<\/p>\n<p>By building a task, you&#8217;re  creating a reusable build component that can be shared with anyone using your  technology.<\/p>\n<p>Before writing this  article, I created four build tasks corresponding to the four Redgate SQL CI  actions. This makes it much nicer to use from VSO:<\/p>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2272-VsoSQLCIBlog_Screenshot2.png\"> <img loading=\"lazy\" decoding=\"async\" height=\"479\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2272-VsoSQLCIBlog_Screenshot2.png\" width=\"529\" alt=\"2272-VsoSQLCIBlog_Screenshot2.png\" \/><\/a><\/p>\n<p class=\"caption\">Adding a build task to  the build<\/p>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2272-VsoSQLCIBlog_Screenshot3.png\">  <img loading=\"lazy\" decoding=\"async\" height=\"479\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2272-VsoSQLCIBlog_Screenshot3.png\" width=\"618\" alt=\"2272-VsoSQLCIBlog_Screenshot3.png\" \/><\/a><\/p>\n<p class=\"caption\">A build process with six  steps<\/p>\n<h2>Writing a TFS Build Task<\/h2>\n<p>There  are currently about 30 built-in tasks, which are all  <a href=\"https:\/\/github.com\/Microsoft\/vso-agent-tasks\"> open-source in GitHub<\/a>.  At the time of writing, this GitHub repository is the primary place you should  go for guidance and documentation. These is currently no formal documentation,  so you&#8217;ll have to rely on scraps of information in this repository (and articles  like this one!)<\/p>\n<p>A task  primarily consists of three components:<\/p>\n<ul>\n<li>&#160;A task.json file defining the web UI of the task. This describes  \t\tthings like the name of the task, the parameters it takes, and so on.  \t\tThis JSON format is currently not documented, so you&#8217;ll have to look at  \t\texisting tasks for inspiration.<\/li>\n<li>&#160;A target to be executed (in PowerShell, Node.js, or both).  \t\tThis task file(s) is referenced by the JSON.<\/li>\n<li>icon.png &#8211; a 32&#215;32 png icon which appears with your task in the VSO  \t\tweb UI.<\/li>\n<\/ul>\n<p>You can  also localize the JSON file by providing a task.loc.json file which references  string-files in a Strings\/resources.resjson folder.<\/p>\n<p>Here&#8217;s  the layout of the task. The sqlCI directory contains various supporting files. I  use the TFX tool to upload these items to VSO.<\/p>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2272-VsoSQLCIBlog_Screenshot5.png\">  <img loading=\"lazy\" decoding=\"async\" height=\"127\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2272-VsoSQLCIBlog_Screenshot5.png\" width=\"486\" alt=\"2272-VsoSQLCIBlog_Screenshot5.png\" \/><\/a> <\/p>\n<div class=\"note\">\n<p class=\"note\">A quick  disclaimer: this article is based on my own experiences and experimentation in  the absence of documentation, so there may be errors and omissions. <\/p>\n<\/div>\n<h2>Writing a Build Task<\/h2>\n<p>The  easiest way is to take a basic build task from the GitHub, and edit it. All you  need is the task.json file, an icon.png, and a target to execute (if, like me,  you&#8217;re primarily a Windows developer, this will be a PowerShell file). <\/p>\n<h2>The task.JSON file<\/h2>\n<p>This  file defines the task and its UI. It starts with a section defining the task.  Here&#8217;s the file I used for making my SQL CI task:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">{\n&#160;&#160;&#160; \"name\": \"RedgateSqlCiTest\",\n&#160;&#160;&#160; \"friendlyName\": \"Redgate SQL CI Test\",\n&#160;&#160;&#160; \"description\": \"Test a database package with Redgate SQL CI\",\n&#160;&#160;&#160; \"helpMarkDown\": \"[More Information](https:\/\/documentation.red-gate.com\/display\/SAP1\/SQL+Automation+Pack+documentation)\",\n&#160;&#160;&#160; \"category\": \"Build\",\n&#160;&#160;&#160; \"visibility\": [\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; \"Build\"\n&#160;&#160;&#160; ],\n&#160;&#160;&#160; \"author\": \"Redgate Software\",\n&#160;&#160;&#160; \"version\": {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; \"Major\": 1,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; \"Minor\": 0,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; \"Patch\": 0\n&#160;&#160;&#160; },\n&#160;&#160;&#160; \"demands\": [\n&#160;\n&#160;&#160;&#160; ],\n&#160;&#160;&#160; \"minimumAgentVersion\": \"1.83.0\",\n&#160;&#160;&#160; \"groups\": [\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; \"name\": \"tempServerOptions\",\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; \"displayName\": \"Temporary Server\",\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; \"isExpanded\": true\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; },\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; \"name\": \"advancedOptions\",\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; \"displayName\": \"Advanced Options\",\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; \"isExpanded\": true\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160; ]\n&#160;\n<\/pre>\n<p>This is  fairly self-explanatory. A few things:<\/p>\n<ul>\n<li>The <b>id<\/b> is just the GUID that VSO uses to  identify your task. Just generate a GUID.<\/li>\n<li>The \t\t<b>friendlyName<\/b> and \t\t<b>description<\/b> are what appear in the  UI.<\/li>\n<li><strong>demands<\/strong>  are what the build agent must have to run your task, e.g. \t\t<i>AndroidSDK<\/i> or \t\t<i>npm<\/i>. These are the same as the SYSTEM  CAPABILITIES of an agent in the VSO Control Panel under \t\t<i>Agent pools<\/i>.<\/li>\n<li><strong>groups<\/strong>  \t\tare just groups of UI  controls. Each group is a collapsible box in the UI than includes all items  marked with this groupName. It&#8217;s just a neat way to group related items. \t\t<b>isExpanded<\/b> determines if the group  is closed or expanded by default in the UI.<\/li>\n<\/ul>\n<p>The<strong>  task.JSON<\/strong> then defines <i>inputs<\/i>. These  are the fields in the web UI that the user fills-in. They take a form like this:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\t &#160;&#160;&#160; &#160;&#160; {\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"name\":  \"authMethod\",\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"type\":  \"pickList\",\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"label\":  \"Authentication Method\",\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"defaultValue\":  \"sqlServerAuth\",\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"required\":  true,\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"options\": {\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"sqlServerAuth\":  \"SQL Server authentication\",\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"windowsAuth\":  \"Windows authentication\"\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }, \n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"helpMarkDown\":  \"The authentication method you want to use.\"\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"visibleRule\":  \"tempServerType = sqlServer\",\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"groupName\":  \"tempServerOptions\"\n\t\t}\n\t\t\n<\/pre>\n<p>Here&#8217;s a few things to note: \t\t<\/p>\n<ul>\n<li><strong>name<\/strong>:  passed onto node or PowerShell as a parameter.<\/li>\n<li><strong>type<\/strong>  \t\t\tcan be:\n<ul>\n<li>string &#8211; A text box.<\/li>\n<li>filePath &#8211; Also a text box,  but with an additional button which opens a file selection box, where the user  can select a file from source-control.<\/li>\n<li>pickList &#8211; A combo box. You  select from a list of values.<\/li>\n<li>boolean &#8211; A checkbox. This  takes the value true or false.<\/li>\n<\/ul>\n<\/li>\n<li><strong>label<\/strong>:  the string shown in the UI, next to the control (e.g. &#8220;First line of address&#8221;).<\/li>\n<li><strong>required<\/strong><strong>:  \t\t\t<\/strong>Set true if this field is mandatory. Then, if the user leaves it empty, he will  get the standard red &#8216;required&#8217; error.<\/li>\n<li><strong>visibleRule<\/strong><strong>:  \t\t\t<\/strong>an expression which is evaluated to determine whether the field is shown in the  UI. Usually, this will refer to the value of another field. For instance, you  may only want to show a &#8220;Delete files recursively&#8221; checkbox if a &#8220;Delete files&#8221;  checkbox is already selected, so would write: &#8220;visibleRule&#8221;: &#8220;deleteFiles =  true&#8221;<\/li>\n<li><strong>groupName<\/strong>:  The group this controls is in (see <b>groups<\/b> described above).<\/li>\n<\/ul>\n<p>There is no password-style field. Instead, set sensitive fields to <code>$(MySecretPassword)<\/code>, and define <code>MySecretPassword<\/code> in the Variables tab:<\/p>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2272-VsoSQLCIBlog_Screenshot437.png\"> <img loading=\"lazy\" decoding=\"async\" height=\"221\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2272-VsoSQLCIBlog_Screenshot437.png\" width=\"620\" alt=\"2272-VsoSQLCIBlog_Screenshot437.png\" \/><\/a><\/p>\n<p>There  may be more elements available, but due to the absence of documentation, I don&#8217;t  know what they are yet!<\/p>\n<p>At the  bottom of the file, we see a few more items:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\t &#160;&#160;&#160; \"instanceNameFormat\":  \"Test $(packageId) with SQL CI\",\n\t\t &#160;&#160;&#160;  \"execution\": {\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"PowerShell\": {\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"target\":  \"$(currentDirectory)\\\\SqlCiTest.ps1\",\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"argumentFormat\":  \"\",\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;  \"workingDirectory\":  \"$(currentDirectory)\"\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n\t\t &#160;&#160;&#160; } \n<\/pre>\n<ul>\n<li>InstanceNameFormat  is what is shown in the list of build steps. It can refer to inputs via the  $(inputName) substitution.<\/li>\n<li>execution  and <b>target<\/b> give the PowerShell file  that should be invoked during the build. A similar thing is used for invoking  node (see the Tasks GitHub for examples). This PowerShell file I will describe  next.<\/li>\n<\/ul>\n<h2>The PowerShell file<\/h2>\n<p>This  starts with parameters corresponding to the inputs in the task.json file:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tparam(\n\t\t &#160;&#160;&#160; [string]$dbFolder, \n\t\t &#160;&#160;&#160; [string]$subFolderPath, \n\t\t &#160;&#160;&#160; [string]$packageName,\n\t\t &#160;&#160;&#160; [string]$additionalParams\n\t\t) \n<\/pre>\n<p>You can  then do whatever you want to do. &#160;In my  SQL CI task plugin, it invokes the SQL CI command-line using code like this:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\t &#160;&#160;&#160;&#160; [string]$sqlCiExePath = [System.IO.Path]::GetFullPath(\".\\tasks\\RedgateSqlCiBuild\\1.0.0\\sqlCI\\SQLCI.exe\")\n\t\t&#160;\n\t\t &#160;&#160;&#160; if(!(Test-Path $sqlCiExePath)) {\n\t\t &#160;&#160;&#160;&#160;&#160;&#160;&#160; Throw [System.IO.FileNotFoundException] \"SQL CI executable not found at $sqlCiExePath\"\n\t\t &#160;&#160;&#160; }\n\t\t&#160;\n\t\t &#160;&#160;&#160; $psi = New-Object System.Diagnostics.ProcessStartInfo\n\t\t &#160;&#160;&#160; $psi.FileName = $sqlCiExePath\n\t\t &#160;&#160;&#160; $psi.Arguments = $sqlCiArgs\n\t\t &#160;&#160;&#160; $psi.UseShellExecute = $false\n\t\t &#160;&#160;&#160; $psi.CreateNoWindow = $true\n\t\t &#160;&#160;&#160; $psi.RedirectStandardOutput = $true\n\t\t &#160;&#160;&#160; $psi.RedirectStandardError = $true\n\t\t&#160;\n\t\t &#160;&#160;&#160; $process = New-Object System.Diagnostics.Process\n\t\t &#160;&#160;&#160; $process.StartInfo = $psi\n\t\t &#160;&#160;&#160; $isNewProcessStarted = $process.Start() \n\t\t &#160;&#160;&#160; $hasExited = $process.WaitForExit(10 * 60 * 1000)&#160;# wait 10 minutes for process to exit\n\t\t &#160;&#160;&#160; \n\t\t &#160;&#160;&#160; $stdout = $process.StandardOutput.ReadToEnd()\n\t\t &#160;&#160;&#160; $stderr = $process.StandardError.ReadToEnd()\n\t\t &#160;&#160;&#160; \n\t\t &#160;&#160;&#160; Write-Host \"$stdout\"\n\t\t &#160;&#160;&#160; Write-Host \"$stderr\" \n\t\t<\/pre>\n<p>Use  Write-XXXX to write to the build log, e.g.<code> Write-Verbose \t\t<\/code>to write Verbose  logging output and<code> Write-Host <\/code>to write essential information.<\/p>\n<p>It&#8217;s  usually best to start your script with  \t\t$ErrorActionPreference \t\t= &#8220;Stop&#8221;. This ensures  the script stops immediately when encountering an error, rather than executing  further instructions.<\/p>\n<p>Also,  be aware that the task runs in the task directory, not the source directory.  This is likely inconvenient, as you probably want to perform an operation on  something in the source directory. To get around this, either pass the source  directory as a parameter, or use the (undocumented) <code>Get-TaskVariable<\/code> cmdlet:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\t$buildSourcesDirectory = Get-TaskVariable -Context $distributedTaskContext -Name \"Build.SourcesDirectory\" <\/pre>\n<h2>Uploading a task<\/h2>\n<p class=\"MsoNormal\">Once  \tyou&#8217;ve written your task, you&#8217;ll want to upload it to VSO. To do this, use  \tthe &#8220;<a href=\"https:\/\/github.com\/Microsoft\/tfs-cli\">TFS Cross Platform Command Line<\/a>&#8220;,  \talso known as TFX.<\/p>\n<p class=\"MsoNormal\">TFX  \tis quite easy to use, instructions are in the README.md at the  \t<a href=\"https:\/\/github.com\/Microsoft\/tfs-cli\"> \tGitHub<\/a>.  \tBasically:<\/p>\n<ul>\n<li>&#160;Install Node.JS  \tfrom the <a href=\"https:\/\/nodejs.org\/download\/\">Node website<\/a>.<\/li>\n<li>Run at the command-line: <code>npm install -g tfx-cli<\/code><\/li>\n<li>Run <code>tfx login<\/code> and enter your collection url (<i>probably  \thttps:\/\/youraccount.visualstudio.com<\/i>) and your \t\t<a href=\"http:\/\/roadtoalm.com\/2015\/07\/22\/using-personal-access-tokens-to-access-visual-studio-online\/\">personal access token<\/a><\/li>\n<li>Run <code>tfx upload  \t\t&lt;folder&gt; <\/code>to upload your task.<\/li>\n<li>Run <code>tfx delete &lt;GUID&gt; <\/code>to delete your task. TFX does not overwrite, so you&#8217;ll need to  \t\tdelete before uploading again. <\/li>\n<\/ul>\n<p class=\"MsoNormal\"> \tUploading will be pretty quick. If there&#8217;s a syntax error you&#8217;ll get an  \terror. If there&#8217;s a semantic error (e.g. an input is in a non-existent  \tgroup), you&#8217;ll probably get a 404 error. Debugging, of me, involved a  \t&#8220;binary chop&#8221; to find the broken lines of code. The error-detection systems  \tin task-writing are currently poor.<\/p>\n<p class=\"MsoNormal\">When  \tuploading a task, you will need to refresh the VSO web page (e.g. hit F5) to  \tupdate the task&#8217;s UI. Tasks are cached, so going to another page and back is  \tusually insufficient. The task will update, you don&#8217;t need to remove and  \tre-add it.<\/p>\n<p class=\"MsoNormal\">You  \tcan put any files or folders adjacent to the <code>task.json<\/code> and they will be  \tuploaded too. This is useful for tasks which invoke binaries.<\/p>\n<h2>Running your task<\/h2>\n<p class=\"MsoNormal\">To  \trun your build, just click &#8216;Queue new build&#8217; and watch it run.<\/p>\n<p class=\"MsoNormal\">If  \tyour task fails, try debugging using <code>Write-XXXX <\/code>commands. To see the output  \tof <code>Write-Debug<\/code> and <code>Write-Verbose<\/code>, add the  \t<code>build-variable<\/code> system.debug with  \tthe value true.<\/p>\n<h2><\/h2>\n<\/p><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Hosted TFS, now called Visual Studio Online (VSO), has a new way of writing build processes called Build vNext. Agent tasks are the building blocks of processes and you can supplement the built-in ones with custom build tasks defined in JSON that use targets written in node.js or PowerShell. Jason Crease shows how to develop custom build tasks for building, testing, publishing and synchronizing databases.&hellip;<\/p>\n","protected":false},"author":95472,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143516],"tags":[4168,5965,4880,4635,4150],"coauthors":[],"class_list":["post-2079","post","type-post","status-publish","format-standard","hentry","category-database-devops","tag-database","tag-database-delivery","tag-json","tag-powershell","tag-sql"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2079","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/95472"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=2079"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2079\/revisions"}],"predecessor-version":[{"id":90746,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2079\/revisions\/90746"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=2079"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=2079"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=2079"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=2079"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}