blog.majcica.com Open in urlscan Pro
2a01:238:20a:202:1159::  Public Scan

URL: http://blog.majcica.com/
Submission: On January 04 via manual from CA — Scanned from CA

Form analysis 1 forms found in the DOM

GET http://blog.majcica.com/

<form role="search" method="get" class="search-form" action="http://blog.majcica.com/">
  <label>
    <span class="screen-reader-text">Search for:</span>
    <input type="search" class="search-field" placeholder="Search …" value="" name="s">
  </label>
  <input type="submit" class="search-submit" value="Search">
</form>

Text Content

Skip to content


MUMMY'S BLOG


AS MOTHER MADE IT – MARIO MAJČICA'S WEB LOG

 * About Mario Majčica
 * Contact Me


SIGNING GIT COMMITS

What does it mean to sign a Git commit and why would you like to do that?

> From Latin, signāre, or putting a mark.

As the word itself says, signing, putting a mark, ensures that the commit you
made and the code contained can’t be tempered.
Git is cryptographically secure, but it’s not foolproof. In order to ensure the
repository integrity, Git can sign tags and commits with a GPG key.

In this post, I’ll show you how to set up all of the necessary toolings in order
to be able to sign your git commits. Aside from having the latest version of Git
installed, you’ll need also the GnuPG. So let’s start.


GPG INTRODUCTION

GnuPG, also known as GPG, is a complete and free implementation of the OpenPGP
standard. All of the details about OpenPGP are defined in RFC4880 (also known as
PGP).
First of all, you need to download GPG, configure it and create/add your
personal key.
On the following address https://www.gnupg.org/download/index.html and under
“GnuPG binary releases” under windows section choose “Simple installer for the
current GnuPG” and download the installer.



When downloaded, please install the application. The installation procedure is a
simple one as no particular options are available.
Once installed you are ready to create a new key, which is the fundamental thing
in getting to sign our commit.

In command prompt issue the following command gpg --full-generate-key At this
point, you will be asked several questions you will need to answer before your
key is going to be created. Check the following example:



At the end of the process you will be asked, in a pop-up window, for a password
that needs to be assigned to this key, please provide one.



Once the key is created you need to let Git know about it. First issue the
following command gpg --list-secret-keys --keyid-format LONG which will list the
necessary information about the newly created key. You should see something like
this.



Now copy the value that is highlighted in red (key id) and issue the following
command git config --global user.signingkey 0F5CBDB9F0C9D2D3 (where
0F5CBDB9F0C9D2D3 is your key id).

This is necessary so that Git knows what key it should use in order to sign your
commits.

However, we are still not ready to go and sign our first commit. What we are
missing is to set the `gpg.program` setting in our global git config. To do so
we first need to retrieve the path of our gpg executable. The easiest way to do
so is to run the where gpg command. It will return you the path on where gpg was
installed. Now we can set the configuration by running the git config --global
gpg.program "C:\Program Files (x86)\GnuPG\bin\gpg.exe" command (obviously in
case your path differs from this one, you should adjust it).

Also, before proceeding make sure that the git user.name and user.email are set.
In case this was not yet initialized try with, git config --global user.name
"Mario Majcica" and git config --global user.email your@yemail.com.

Now we are ready to sign our first commit. Initialize a new git repository, add
a file and run git commit -S -m "signed commit". At this point you should be
prompted for the password of your key, the one you have chosen during the
creation of the key itself:



Once you enter your password, your commit will be made and it is going to be
signed, e.g.



Let’s now verify that are signature is there. In order to achieve that issue the
following command git log --show-signature -1 or a in a more kind of overview
printout git log --pretty="format:%h %G? %aN %s".

You can learn more about Git and the available command regard signing commits
here https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work.


EXPORT/IMPORT

Next step is to export our key. Why would we do that? Well, an example, so that
you can import it on another machine of yours, or import it to services like
Github who can then validate your signature.

Let’s first export our public key. To do so, use the following command: gpg
--export -a 0F5CBDB9F0C9D2D3> publicKey.asc
Obviously, 0F5CBDB9F0C9D2D3 is my key id in this case, sobstitute this value
with your key id.
This command will create a file called publicKey.asc in the current folder of
yours. Edit this file with a text editor of choice. The content of it will be
necessary information for your Github account. Now open your Github.com page and
log in. Under the settings, you will find a menu called “SSH and GPG keys”. Open
this menu then choose “New GPG Key”:



Now, copy the content of publicKey.asc and paste it in the page on GitHub, then
just click “Add GPG Key”.
Once done, you should see your new key listed in the GitHub page “SSH and GPG
keys” under the GPG Keys. I’ll now edit one of the projects in GitHub and push a
signed commit. As you can see, it is now listed that the commit is verified.



In case you click on the Verified icon you will be able to see the details about
the signature:



Before we move to the import part, let me show you a trick on how to automate
this in a popular IDE, Visual Studio Code.
Now that we are all set up, we can instruct Visual Studio Code, to sign the
commits that are made from the IDE. To do that, open the settings page in Visual
Studio Code



then search for ‘git signing’ and the relevant setting should be listed:



The setting in question is ‘Enable Commit Signing’. Check it, then make a new
commit. List your commit log and you’ll see that now also the commits made
directly from Visual Studio Code are now signed.

However, the export doesn’t end here. We need to export the private key in order
to be able to import it and use it on another machine. To do so run the
following command, gpg --export-secret-keys -a 0F5CBDB9F0C9D2D3 > privateKey.asc
(where 0F5CBDB9F0C9D2D3 should be your key id). Store this file carefully and do
not expose it to the public. It is protected by the password, still, however, in
this case, the password itself becomes the weak link.

It is now time to import it. For that, it is sufficient to issue the following
command gpg --import privateKey.asc. You do not need to import the public key,
the private key always contains the public key. One last thing, if imported on
another machine, you need to indicate the level of trust towards the newly
imported key. You can easily achieve that with the command gpg --edit-key
0F5CBDB9F0C9D2D3 trust quit where 0F5CBDB9F0C9D2D3 is again the key id of the
key on that machine. After you issue the command you will see the following
screen:



and at this stage, you will be asked for a decision. Hit 5 to indicate you trust
ultimately the given key and your job is done.
If the key already existed on the new machine, the import will fail to say ‘Key
already known’. You will have to delete both the private and public key first
(gpg –delete-keys and gpg –delete-secret-keys).


CONSLUSION

Aside from the commits, you can also sign tags. If you are not familiar with
public key cryptography, check this video on YouTube, it is one of the simplest
explanations that I heard.
Some of the useful commands in our case:
gpg --list-keys and gpg --list-secret-keys, both will list your keys, public and
private ones and the trust state.
git config --list --show-origin will show you all of the git settings so that
you can check if the necessary is already set.

To configure your Git client to sign commits by default for a local repository,
in Git versions 2.0.0 and above, run git config commit.gpgsign true. To sign all
commits by default in any local repository on your computer, run git config
--global commit.gpgsign true.

To store your GPG key passphrase so you don’t have to enter it every time you
sign a commit, I recommend using Gpg4win.

That’s all folks, don’t forget to sign your work!

TwitterLinkedInFacebookWhatsAppShare
Leave a comment

June 3, 2019June 4, 2019 Mario MajcicaNo Comments


BUILDING VISUAL STUDIO PROJECTS WITH DEVENV.COM


INTRODUCTION

This post is about my most successful Azure DevOps extension. Before I tell you
more about the newest version, let me tell you something more about the history
of it.

Several years ago, just after the TFS 2015 was released, this was one of the
first build tasks I built. I had a team who demanded it as not all of the
projects they were running were based on MSBuild. Also, the necessary script to
perform this action was quite simple, thus, a great practice ground to start
writing a custom build task (I emphasise build as the release was back then not
there yet).
It turned out to be very useful, however, considering its simplicity, I never
thought it may be of interest to others. It was until I wrote and published the
Deploy SSIS extension that I realized that may be handy to publish this task as
an extension. It is not that I suddenly changed my mind, it just made sense to
describe the whole process of building and deploying SSIS projects in a blog
post and I missed a building part. It turns out I was wrong a big time as this
is the most used extension I wrote with over a thousand downloads from the Azure
DevOps Marketplace.


WHAT’S NEW IN VERSION 2

My initial implementation stayed untouched for quite some time. In the end, it
is a simple task that just worked. However, some users made me notice an obvious
flaw. In the project was set to be built, all of the projects in the containing
solution would be built. Strangely enough the same is stated in the help page of
the tool itself, it just that I never noticed it.

> The first argument for devenv is usually a solution file or project file.
> You can also use any other file as the first argument if you want to have the
> file open automatically in an editor. When you enter a project file, the IDE
> looks for an .sln file with the same base name as the project file in the
> parent directory for the project file. If no such .sln file exists, then the
> IDE looks for a single .sln file that references the project. If no such
> single
> .sln file exists, then the IDE creates an unsaved solution with a default .sln
> file name that has the same base name as the project file.

I address this issue by adding a separate filed for the solution and for the
project. As this is a breaking change for the existing users, I published a new
major version of the task.
Some minor improvements are also now included in the new version.
The first issue ever to be reported was actually a feature request. It was asked
if the search for the solution file can be performed so that the wildcards can
be used as parameters for the solution input field. This is now addressed.
Also, all of the dependency libraries are now updated to the latest available
version. These are ‘Task Library’ which is now v0.11.0 and ‘VSSetup library’
that was boosted to v2.2.5. This also bring some changes to the retrieval of the
path of the DevEnv come to accommodate the newly added Visual Studio 2019
option.


WHAT’S NEXT

Although the task is focusing on windows tooling and needs windows only tooling
to be present on the build agent, the PowerShell implementation is somewhere
limiting. The next step will be rewriting the task in TypeScript. Not a huge
added value, however, it is making this extension easier to maintain in the
future.

Truly my hopes are in Microsoft basing all of their projects on MSBuild (as they
recently did for SSRS projects) and making this extension obsolete. However, for
time being, this is one of the puzzle pieces, that could help you in automating
your MS BI pipelines.

TwitterLinkedInFacebookWhatsAppShare
Leave a comment

May 30, 2019May 30, 2019 Mario MajcicaNo Comments


STARTING A TFS RELEASE VIA REST API


INTRO

It is a common need to inject variables in your release. When it comes to a
build, it was always an easy task, however, the release didn’t support such a
thing out of the box. In case you are using Azure DevOps (service and server),
you are good to go, as this is now possible after you do mark a variable as
‘Settable at the release time’. But what about those who are stuck to previous
versions of TFS? Well, there are some tricks that I’ll illustrate in this post.


LEVERAGING DRAFT RELEASE

There are two ways of achieving our goal. The first one is to create a new
release and do not trigger the deployment in the environments that are set to
deploy on the release creation, set the variables then trigger the deployments.
This is a more laborious and complicated way. Second method is to create a draft
release, then populate the draft with the necessary variables and then start the
release. I’ll show you the necessary steps to achieve this via the REST API
which you can try for free. 
In the upcoming cmdlets I’ll focus on achieving a goal, which is to create a
draft release, add a variable and start the release. So if you find code not
really reusable or not framework like, please consider that was out of my goal
and it would take way more effort to write.
First of all I need a couple of helper functions that I will use in order to
authenticate and get the right release id based on the release name.

$pat = '6yhymn3foxuqmsobktekvukeffhqifjt4yeldfj33v6wk4kr4idq'
$url = "https://myTest.tfs.local/tfs/DefaultCollection"
$project = "MarioTest"

function Get-PatHeader {
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)] [string]$Pat
)
BEGIN { }
PROCESS {
$encodedCredentials =
[System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$pat"))
$header = @{ }
$header.Authorization = "Basic $encodedCredentials"

return $header
}
END { }
}

function Get-ReleaseDefinitionId {
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)] [string]$CollectionUrl,
[Parameter(Mandatory = $true)] [string]$Project,
[Parameter(Mandatory = $true)] [string]$Name,
[Parameter(Mandatory = $true)] [string]$Pat
)
BEGIN { }
PROCESS {
$headers = Get-PatHeader $Pat
$reponse = Invoke-RestMethod
"$CollectionUrl/$Project/_apis/release/definitions?searchText=$Name" -Headers
$headers

return $reponse.value.id
}
END { }
}

$releaseDefinitionId = Get-ReleaseDefinitionId $url $project "Test1" $pat
$pat = '6yhymn3foxuqmsobktekvukeffhqifjt4yeldfj33v6wk4kr4idq' $url =
"https://myTest.tfs.local/tfs/DefaultCollection" $project = "MarioTest" function
Get-PatHeader { [CmdletBinding()] param ( [Parameter(Mandatory = $true)]
[string]$Pat ) BEGIN { } PROCESS { $encodedCredentials =
[System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$pat"))
$header = @{ } $header.Authorization = "Basic $encodedCredentials" return
$header } END { } } function Get-ReleaseDefinitionId { [CmdletBinding()] param (
[Parameter(Mandatory = $true)] [string]$CollectionUrl, [Parameter(Mandatory =
$true)] [string]$Project, [Parameter(Mandatory = $true)] [string]$Name,
[Parameter(Mandatory = $true)] [string]$Pat ) BEGIN { } PROCESS { $headers =
Get-PatHeader $Pat $reponse = Invoke-RestMethod
"$CollectionUrl/$Project/_apis/release/definitions?searchText=$Name" -Headers
$headers return $reponse.value.id } END { } } $releaseDefinitionId =
Get-ReleaseDefinitionId $url $project "Test1" $pat


$pat = '6yhymn3foxuqmsobktekvukeffhqifjt4yeldfj33v6wk4kr4idq'
$url = "https://myTest.tfs.local/tfs/DefaultCollection"
$project = "MarioTest"

function Get-PatHeader {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)] [string]$Pat
    )
    BEGIN { }
    PROCESS {
        $encodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$pat"))
        $header = @{ }
        $header.Authorization = "Basic $encodedCredentials"

        return $header
    }
    END { }
}

function Get-ReleaseDefinitionId {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)] [string]$CollectionUrl,
        [Parameter(Mandatory = $true)] [string]$Project,
        [Parameter(Mandatory = $true)] [string]$Name,
        [Parameter(Mandatory = $true)] [string]$Pat
    )
    BEGIN { }
    PROCESS {
        $headers = Get-PatHeader $Pat
        $reponse = Invoke-RestMethod "$CollectionUrl/$Project/_apis/release/definitions?searchText=$Name" -Headers $headers

        return $reponse.value.id
    }
    END { }
}

$releaseDefinitionId = Get-ReleaseDefinitionId $url $project "Test1" $pat

The above is to get the necessary authentication header and resolve the Release
Definition name into the id that we are going to use across all of our other
calls. As you can see, my release definition is called “Test1”.

Before we start a draft release, we need to collect some relevant information
and that is information about the artifacts that we would like to use with the
new release. Often this is a pain point for those with less experience with TFS.
However, the following cmdlets should do the trick:

function Get-ReleaseArtifacts {
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)] [string]$CollectionUrl,
[Parameter(Mandatory = $true)] [string]$Project,
[Parameter(Mandatory = $true)] [int]$ReleaseDefinitionId,
[Parameter(Mandatory = $true)] [string]$Pat
)
BEGIN { }
PROCESS {
$headers = Get-PatHeader $Pat
$reponse = Invoke-RestMethod
"$CollectionUrl/$Project/_apis/Release/artifacts/versions?releaseDefinitionId=$ReleaseDefinitionId"
-Headers $headers

return $reponse
}
END { }
}

function Get-DefaultReleaseArtifacts {
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)] $ReleaseArtifacts
)
BEGIN { }
PROCESS {
$artifacts = @()

foreach ($artifactVersion in $ReleaseArtifacts.artifactVersions) {
$artifact = [PSCustomObject]@{ }

$artifact | Add-Member -MemberType NoteProperty -Name "alias" -Value
$artifactVersion.alias
$artifact | Add-Member -MemberType NoteProperty -Name "instanceReference" -Value
$artifactVersion.defaultVersion

$artifacts += $artifact
}

return $artifacts
}
END { }
}

$releaseArtifacts = Get-ReleaseArtifacts $url $project $releaseDefinitionId $pat
$defaultArtifacts = Get-DefaultReleaseArtifacts $releaseArtifacts
function Get-ReleaseArtifacts { [CmdletBinding()] param ( [Parameter(Mandatory =
$true)] [string]$CollectionUrl, [Parameter(Mandatory = $true)] [string]$Project,
[Parameter(Mandatory = $true)] [int]$ReleaseDefinitionId, [Parameter(Mandatory =
$true)] [string]$Pat ) BEGIN { } PROCESS { $headers = Get-PatHeader $Pat
$reponse = Invoke-RestMethod
"$CollectionUrl/$Project/_apis/Release/artifacts/versions?releaseDefinitionId=$ReleaseDefinitionId"
-Headers $headers return $reponse } END { } } function
Get-DefaultReleaseArtifacts { [CmdletBinding()] param ( [Parameter(Mandatory =
$true)] $ReleaseArtifacts ) BEGIN { } PROCESS { $artifacts = @() foreach
($artifactVersion in $ReleaseArtifacts.artifactVersions) { $artifact =
[PSCustomObject]@{ } $artifact | Add-Member -MemberType NoteProperty -Name
"alias" -Value $artifactVersion.alias $artifact | Add-Member -MemberType
NoteProperty -Name "instanceReference" -Value $artifactVersion.defaultVersion
$artifacts += $artifact } return $artifacts } END { } } $releaseArtifacts =
Get-ReleaseArtifacts $url $project $releaseDefinitionId $pat $defaultArtifacts =
Get-DefaultReleaseArtifacts $releaseArtifacts


function Get-ReleaseArtifacts {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)] [string]$CollectionUrl,
        [Parameter(Mandatory = $true)] [string]$Project,
        [Parameter(Mandatory = $true)] [int]$ReleaseDefinitionId,
        [Parameter(Mandatory = $true)] [string]$Pat
    )
    BEGIN { }
    PROCESS {
        $headers = Get-PatHeader $Pat
        $reponse = Invoke-RestMethod "$CollectionUrl/$Project/_apis/Release/artifacts/versions?releaseDefinitionId=$ReleaseDefinitionId" -Headers $headers

        return $reponse
    }
    END { }
}

function Get-DefaultReleaseArtifacts {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)] $ReleaseArtifacts
    )
    BEGIN { }
    PROCESS {
        $artifacts = @()

        foreach ($artifactVersion in $ReleaseArtifacts.artifactVersions) {
            $artifact = [PSCustomObject]@{ }

            $artifact | Add-Member -MemberType NoteProperty -Name "alias" -Value $artifactVersion.alias
            $artifact | Add-Member -MemberType NoteProperty -Name "instanceReference" -Value $artifactVersion.defaultVersion
        
            $artifacts += $artifact
        }

        return $artifacts
    }
    END { }
}

$releaseArtifacts = Get-ReleaseArtifacts $url $project $releaseDefinitionId $pat
$defaultArtifacts = Get-DefaultReleaseArtifacts $releaseArtifacts

As in the Web UI, you are asked to specify the version of artifacts to use for
the release that you are creating. The same is asked by the REST API. The above
cmdlets will allow you to retrieve the list of all the available artifacts and
pick the last (default) one. You can see further examples here
https://docs.microsoft.com/en-us/azure/devops/integrate/previous-apis/rm/releases?view=azure-devops-2019#create-a-release

In case you are interested in using non the latest artifacts, you can explore
further the response and implement the necessary logic do provide the ones to
use in the next stage.

Now it’s time to create our draft release. This is easily achieved with the
following cmdlet.

function New-Release {
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)] [string]$CollectionUrl,
[Parameter(Mandatory = $true)] [string]$Project,
[Parameter(Mandatory = $true)] [string]$Pat,
[Parameter(Mandatory = $true)] $ReleaseArtifacts,
[Parameter(Mandatory = $true)] [int]$ReleaseDefinitionId,
[Parameter()] [string]$ReleaseDescription,
[Parameter()] [bool]$IsDraft = $false,
[Parameter()] [string[]]$ManualEnvironments
)
BEGIN { }
PROCESS {
$requestBody = @{ }
$requestBody.definitionId = $ReleaseDefinitionId
$requestBody.isDraft = $IsDraft
$requestBody.description = $ReleaseDescription
$requestBody.reason = "manual"
$requestBody.manualEnvironments = $ManualEnvironments
$requestBody.artifacts = $ReleaseArtifacts

$headers = Get-PatHeader $Pat
$body = $requestBody | ConvertTo-Json -Depth 10
$body = [System.Text.Encoding]::UTF8.GetBytes($body);

return Invoke-RestMethod
"$CollectionUrl/$Project/_apis/release/releases?api-version=4.1-preview.6"
-Method Post -Headers $headers -Body $body -ContentType "application/json"
}
END { }
}

$release = New-Release $url $project $pat $defaultArtifacts $releaseDefinitionId
"desc" $true
function New-Release { [CmdletBinding()] param ( [Parameter(Mandatory = $true)]
[string]$CollectionUrl, [Parameter(Mandatory = $true)] [string]$Project,
[Parameter(Mandatory = $true)] [string]$Pat, [Parameter(Mandatory = $true)]
$ReleaseArtifacts, [Parameter(Mandatory = $true)] [int]$ReleaseDefinitionId,
[Parameter()] [string]$ReleaseDescription, [Parameter()] [bool]$IsDraft =
$false, [Parameter()] [string[]]$ManualEnvironments ) BEGIN { } PROCESS {
$requestBody = @{ } $requestBody.definitionId = $ReleaseDefinitionId
$requestBody.isDraft = $IsDraft $requestBody.description = $ReleaseDescription
$requestBody.reason = "manual" $requestBody.manualEnvironments =
$ManualEnvironments $requestBody.artifacts = $ReleaseArtifacts $headers =
Get-PatHeader $Pat $body = $requestBody | ConvertTo-Json -Depth 10 $body =
[System.Text.Encoding]::UTF8.GetBytes($body); return Invoke-RestMethod
"$CollectionUrl/$Project/_apis/release/releases?api-version=4.1-preview.6"
-Method Post -Headers $headers -Body $body -ContentType "application/json" } END
{ } } $release = New-Release $url $project $pat $defaultArtifacts
$releaseDefinitionId "desc" $true


function New-Release {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)] [string]$CollectionUrl,
        [Parameter(Mandatory = $true)] [string]$Project,
        [Parameter(Mandatory = $true)] [string]$Pat,
        [Parameter(Mandatory = $true)] $ReleaseArtifacts,
        [Parameter(Mandatory = $true)] [int]$ReleaseDefinitionId,
        [Parameter()] [string]$ReleaseDescription,
        [Parameter()] [bool]$IsDraft = $false,
        [Parameter()] [string[]]$ManualEnvironments
    )
    BEGIN { }
    PROCESS {
        $requestBody = @{ }
        $requestBody.definitionId = $ReleaseDefinitionId
        $requestBody.isDraft = $IsDraft
        $requestBody.description = $ReleaseDescription
        $requestBody.reason = "manual"
        $requestBody.manualEnvironments = $ManualEnvironments
        $requestBody.artifacts = $ReleaseArtifacts
        
        $headers = Get-PatHeader $Pat
        $body = $requestBody | ConvertTo-Json -Depth 10
        $body = [System.Text.Encoding]::UTF8.GetBytes($body);

        return Invoke-RestMethod "$CollectionUrl/$Project/_apis/release/releases?api-version=4.1-preview.6" -Method Post -Headers $headers -Body $body -ContentType "application/json"
    }
    END { }
}

$release = New-Release $url $project $pat $defaultArtifacts $releaseDefinitionId "desc" $true

As you can notice, with the above script, you can start not only a draft
release, but also an ‘ordinary’ one.

At this point we are ready to set our variables. I’ll set both, one on the
release level, another one on the environment scope, then update the release.

function Add-ReleaseVariable {
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)] [object]$ReleaseVariables,
[Parameter(Mandatory = $true)] [string]$VariableName,
[Parameter(Mandatory = $true)] [string]$VariableValue
)
BEGIN { }
PROCESS {
$value = [PSCustomObject]@{ value = $VariableValue }
$ReleaseVariables | Add-Member -Name $VariableName -MemberType NoteProperty
-Value $value -Force

return $ReleaseVariables
}
END { }
}

$release.variables = Add-ReleaseVariable $release.variables "var1" "value"
$release.environments[0].variables = Add-ReleaseVariable
$release.environments[0].variables "MarioInDraftEnv" "value1"
function Add-ReleaseVariable { [CmdletBinding()] param ( [Parameter(Mandatory =
$true)] [object]$ReleaseVariables, [Parameter(Mandatory = $true)]
[string]$VariableName, [Parameter(Mandatory = $true)] [string]$VariableValue )
BEGIN { } PROCESS { $value = [PSCustomObject]@{ value = $VariableValue }
$ReleaseVariables | Add-Member -Name $VariableName -MemberType NoteProperty
-Value $value -Force return $ReleaseVariables } END { } } $release.variables =
Add-ReleaseVariable $release.variables "var1" "value"
$release.environments[0].variables = Add-ReleaseVariable
$release.environments[0].variables "MarioInDraftEnv" "value1"


function Add-ReleaseVariable {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)] [object]$ReleaseVariables,
        [Parameter(Mandatory = $true)] [string]$VariableName,
        [Parameter(Mandatory = $true)] [string]$VariableValue
    )
    BEGIN { }
    PROCESS {
        $value = [PSCustomObject]@{ value = $VariableValue }
        $ReleaseVariables | Add-Member -Name $VariableName -MemberType NoteProperty -Value $value -Force

        return $ReleaseVariables
    }
    END { }
}

$release.variables = Add-ReleaseVariable $release.variables "var1" "value"
$release.environments[0].variables = Add-ReleaseVariable $release.environments[0].variables "MarioInDraftEnv" "value1"

The above cmdlets will make things easier. In case the variable is already
declared, the value will be overwritten with the one that you set at this stage.
You can also see that I’m setting a variable on a release level and on an
environment level. Environments are set to be an array, so to find out the
desired one by name, you’ll need to search for it first. In this example, I’m
just setting it on the first (and in my demo case, only) environment in the
list. As mentioned earlier, last but not least, we will update the release.

function Update-Release {
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)] [string]$CollectionUrl,
[Parameter(Mandatory = $true)] [string]$Project,
[Parameter(Mandatory = $true)] [int]$ReleaseId,
[Parameter(Mandatory = $true)] [object]$Release,
[Parameter(Mandatory = $true)] [string]$Pat
)
BEGIN { }
PROCESS {
$headers = Get-PatHeader $Pat

$body = $Release | ConvertTo-Json -Depth 10
$body = [System.Text.Encoding]::UTF8.GetBytes($body);

$reponse = Invoke-RestMethod
"$CollectionUrl/$Project/_apis/release/releases/$($ReleaseId)?api-version=4.1-preview.6"
-Method Put -Body $body -ContentType 'application/json' -Headers $headers

return $reponse
}
END { }
}

$release = Update-Release $url $project $release.id $release $pat
function Update-Release { [CmdletBinding()] param ( [Parameter(Mandatory =
$true)] [string]$CollectionUrl, [Parameter(Mandatory = $true)] [string]$Project,
[Parameter(Mandatory = $true)] [int]$ReleaseId, [Parameter(Mandatory = $true)]
[object]$Release, [Parameter(Mandatory = $true)] [string]$Pat ) BEGIN { }
PROCESS { $headers = Get-PatHeader $Pat $body = $Release | ConvertTo-Json -Depth
10 $body = [System.Text.Encoding]::UTF8.GetBytes($body); $reponse =
Invoke-RestMethod
"$CollectionUrl/$Project/_apis/release/releases/$($ReleaseId)?api-version=4.1-preview.6"
-Method Put -Body $body -ContentType 'application/json' -Headers $headers return
$reponse } END { } } $release = Update-Release $url $project $release.id
$release $pat


function Update-Release {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)] [string]$CollectionUrl,
        [Parameter(Mandatory = $true)] [string]$Project,
        [Parameter(Mandatory = $true)] [int]$ReleaseId,
        [Parameter(Mandatory = $true)] [object]$Release,
        [Parameter(Mandatory = $true)] [string]$Pat
    )
    BEGIN { }
    PROCESS {
        $headers = Get-PatHeader $Pat

        $body = $Release | ConvertTo-Json -Depth 10
        $body = [System.Text.Encoding]::UTF8.GetBytes($body);

        $reponse = Invoke-RestMethod "$CollectionUrl/$Project/_apis/release/releases/$($ReleaseId)?api-version=4.1-preview.6" -Method Put -Body $body -ContentType 'application/json' -Headers $headers

        return $reponse
    }
    END { }
}

$release = Update-Release $url $project $release.id $release $pat

Unfortunately, we can’t update the variables and start the release in the same
call. The above call will update the variables in that specific release (not in
the release definition) and the following will start the release.

function Start-Release {
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)] [string]$CollectionUrl,
[Parameter(Mandatory = $true)] [string]$Project,
[Parameter(Mandatory = $true)] [int]$ReleaseId,
[Parameter(Mandatory = $true)] [string]$Pat
)
BEGIN { }
PROCESS {
$patch = '{ "status": "active" }'

$headers = Get-PatHeader $Pat
$reponse = Invoke-RestMethod
"$CollectionUrl/$Project/_apis/release/releases/$($ReleaseId)?api-version=4.1-preview.6"
-Method Patch -Body $patch -ContentType 'application/json' -Headers $headers

return $reponse
}
END { }
}

$release = Start-Release $url $project $release.id $pat
function Start-Release { [CmdletBinding()] param ( [Parameter(Mandatory =
$true)] [string]$CollectionUrl, [Parameter(Mandatory = $true)] [string]$Project,
[Parameter(Mandatory = $true)] [int]$ReleaseId, [Parameter(Mandatory = $true)]
[string]$Pat ) BEGIN { } PROCESS { $patch = '{ "status": "active" }' $headers =
Get-PatHeader $Pat $reponse = Invoke-RestMethod
"$CollectionUrl/$Project/_apis/release/releases/$($ReleaseId)?api-version=4.1-preview.6"
-Method Patch -Body $patch -ContentType 'application/json' -Headers $headers
return $reponse } END { } } $release = Start-Release $url $project $release.id
$pat


function Start-Release {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)] [string]$CollectionUrl,
        [Parameter(Mandatory = $true)] [string]$Project,
        [Parameter(Mandatory = $true)] [int]$ReleaseId,
        [Parameter(Mandatory = $true)] [string]$Pat
    )
    BEGIN { }
    PROCESS {
        $patch = '{ "status": "active" }'

        $headers = Get-PatHeader $Pat
        $reponse = Invoke-RestMethod "$CollectionUrl/$Project/_apis/release/releases/$($ReleaseId)?api-version=4.1-preview.6" -Method Patch -Body $patch -ContentType 'application/json' -Headers $headers

        return $reponse
    }
    END { }
}

$release = Start-Release $url $project $release.id $pat

That’s it. We now started our release with variables that are injected into it.


AZURE DEVOPS

As mentioned before, this is not necessary anymore in Azure DevOps, or to be
more precise, since version 5.0 of the REST API. In case you are looking for an
example on how to achieve the same with the Azure DevOps, the following script
will do.

function New-Release {
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)] [string]$CollectionUrl,
[Parameter(Mandatory = $true)] [string]$Project,
[Parameter(Mandatory = $true)] [string]$Pat,
[Parameter(Mandatory = $true)] $ReleaseArtifacts,
[Parameter(Mandatory = $true)] [int]$ReleaseDefinitionId,
[Parameter()] $Variables,
[Parameter()] [string]$ReleaseDescription,
[Parameter()] [bool]$IsDraft = $false,
[Parameter()] [string[]]$ManualEnvironments
)
BEGIN { }
PROCESS {
$requestBody = @{ }
$requestBody.definitionId = $ReleaseDefinitionId
$requestBody.isDraft = $IsDraft
$requestBody.description = $ReleaseDescription
$requestBody.reason = "manual"
$requestBody.manualEnvironments = $ManualEnvironments
$requestBody.artifacts = $ReleaseArtifacts
$requestBody.variables = $Variables

$headers = Get-PatHeader $Pat
$body = $requestBody | ConvertTo-Json -Depth 10
$body = [System.Text.Encoding]::UTF8.GetBytes($body);

return Invoke-RestMethod
"$CollectionUrl/$Project/_apis/release/releases?api-version=5.0" -Method Post
-Headers $headers -Body $body -ContentType "application/json"
}
END { }
}

$variables = [PSCustomObject]@{ }
$variables = Add-ReleaseVariable $variables "var2" "value"

$release = New-Release $url $project $pat $defaultArtifacts $releaseDefinitionId
$variables "desc" $false
function New-Release { [CmdletBinding()] param ( [Parameter(Mandatory = $true)]
[string]$CollectionUrl, [Parameter(Mandatory = $true)] [string]$Project,
[Parameter(Mandatory = $true)] [string]$Pat, [Parameter(Mandatory = $true)]
$ReleaseArtifacts, [Parameter(Mandatory = $true)] [int]$ReleaseDefinitionId,
[Parameter()] $Variables, [Parameter()] [string]$ReleaseDescription,
[Parameter()] [bool]$IsDraft = $false, [Parameter()]
[string[]]$ManualEnvironments ) BEGIN { } PROCESS { $requestBody = @{ }
$requestBody.definitionId = $ReleaseDefinitionId $requestBody.isDraft = $IsDraft
$requestBody.description = $ReleaseDescription $requestBody.reason = "manual"
$requestBody.manualEnvironments = $ManualEnvironments $requestBody.artifacts =
$ReleaseArtifacts $requestBody.variables = $Variables $headers = Get-PatHeader
$Pat $body = $requestBody | ConvertTo-Json -Depth 10 $body =
[System.Text.Encoding]::UTF8.GetBytes($body); return Invoke-RestMethod
"$CollectionUrl/$Project/_apis/release/releases?api-version=5.0" -Method Post
-Headers $headers -Body $body -ContentType "application/json" } END { } }
$variables = [PSCustomObject]@{ } $variables = Add-ReleaseVariable $variables
"var2" "value" $release = New-Release $url $project $pat $defaultArtifacts
$releaseDefinitionId $variables "desc" $false


function New-Release {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)] [string]$CollectionUrl,
        [Parameter(Mandatory = $true)] [string]$Project,
        [Parameter(Mandatory = $true)] [string]$Pat,
        [Parameter(Mandatory = $true)] $ReleaseArtifacts,
        [Parameter(Mandatory = $true)] [int]$ReleaseDefinitionId,
        [Parameter()] $Variables,
        [Parameter()] [string]$ReleaseDescription,
        [Parameter()] [bool]$IsDraft = $false,
        [Parameter()] [string[]]$ManualEnvironments
    )
    BEGIN { }
    PROCESS {
        $requestBody = @{ }
        $requestBody.definitionId = $ReleaseDefinitionId
        $requestBody.isDraft = $IsDraft
        $requestBody.description = $ReleaseDescription
        $requestBody.reason = "manual"
        $requestBody.manualEnvironments = $ManualEnvironments
        $requestBody.artifacts = $ReleaseArtifacts
        $requestBody.variables = $Variables

        $headers = Get-PatHeader $Pat
        $body = $requestBody | ConvertTo-Json -Depth 10
        $body = [System.Text.Encoding]::UTF8.GetBytes($body);

        return Invoke-RestMethod "$CollectionUrl/$Project/_apis/release/releases?api-version=5.0" -Method Post -Headers $headers -Body $body -ContentType "application/json"
    }
    END { }
}

$variables = [PSCustomObject]@{ }
$variables = Add-ReleaseVariable $variables "var2" "value"

$release = New-Release $url $project $pat $defaultArtifacts $releaseDefinitionId $variables "desc" $false

Important changes compared to the previous version of the cmdlet are to be found
in the extra parameter called Variables and the URL api-version parameter now
set to 5.0.

Be however aware that you need create the variables in your release definition
and mark them as ‘Settable at the release time’.

In case your variable is not defined, your API call will fail with the following
error:

> “Variable(s) another do not exist in the release pipeline at scope: Release.
> New variables cannot be added while creating a release. Check the scope of the
> variable(s) or remove them and try again.”

Previously described technique will still work, even with the Azure DevOps if
adding variables dynamically in the release is what you want. However, I would
always advise you to define them, so that they are present and do not
“materialize” from nowhere.

I hope I covered the necessary. Let me know in comments if any.

TwitterLinkedInFacebookWhatsAppShare
1 Comment

May 10, 2019November 28, 2020 Mario Majcica1 Comment


POSTS NAVIGATION

Older posts

© 2015 Mario Majčica All Rights Reserved

Decode Theme by Macho Themes

Search for:


RECENT POSTS

 * Signing Git Commits
 * Building Visual Studio projects with DevEnv.com
 * Starting a TFS release via REST API
 * Azure DevOps extension for XL Deploy
 * Download a file with TypeScript
 * Node10 provider available for Agent v2.144.0
 * Uploading XL Deploy DAR package via TypeScript
 * Living Documentation and test reports in VSTS/TFS pipelines
 * Deploy SSIS packages from TFS/VSTS Build/Release
 * Provisioning WebDeploy in VSTS/TFS release via DSC script
 * Using global application host file in Visual Studio 2015+
 * Get Users SID with PowerShell
 * Joining strings in pipeline
 * Automate setting ‘Retain indefinitely’ flag on your releases
 * Uploading build/release tasks to VSTS
 * Managing VSTS/TFS Release Definition Variables from PowerShell
 * Using Windows Machine File Copy (WinRM) VSTS extension
 * (no title)
 * Persisting sensitive information with PowerShell
 * TextTransform issues after VS Project Upgrade
 * XL Deploy in VSTS/TFS build/release pipelines
 * Using Git with self-signed certificate at the user level
 * VSTS/TFS Agents behind a proxy
 * TFS Tips from the Trenches
 * Working with TFS in IntelliJ IDEA via VSTS Plugin


CATEGORIES

 * .NET (19)
   * ASP.NET (6)
   * C# (14)
   * DevExpress (3)
   * Moq (1)
   * MSTest (2)
   * TPL (1)
   * WCF (2)
 * Articles (15)
 * Azure DevOps (4)
 * Best Practice (12)
 * Cisco (1)
   * IOS (1)
 * CodedUI (5)
 * Cryptography (4)
 * Development (4)
   * SQL (2)
 * Git (2)
 * Hardware (1)
 * IIS (2)
 * IntelliJ IDEA (1)
 * JavaScript (1)
 * Networking (1)
 * News (1)
 * Nexus (3)
 * NodeJs (2)
 * PowerShell (13)
 * Security (9)
 * SonarQube (3)
 * SpecFlow (2)
 * Testing (5)
 * TFS (29)
   * TFS 2015 (12)
   * TFS 2017 (4)
 * Tips and Tricks (2)
 * TypeScript (3)
 * Uncategorized (42)
 * Unit test (4)
 * Visual Studio (12)
   * Nuget (2)
   * Templates (1)
   * VSIX (3)
 * VSTS (9)
 * Windows 8 (1)
 * WiX (1)
 * XebiaLabs (7)
   * XL Deploy (5)
   * XL TestView (2)


META

 * Log in
 * Entries feed
 * Comments feed
 * WordPress.org

✓
Thanks for sharing!
AddToAny
More…