Opps, I made that test VSTS extension public by mistake, what do I do now?

I recently, whilst changing a CI/CD release pipeline, updated what was previously a private version of a VSTS extension in the VSTS Marketplace with a version of the VSIX package set to be public.

Note, in my CI/CD process I have a private and public version of each extension (set of tasks), the former is used for functional testing within the CD process, the latter is the one everyone can see.

So, this meant I had two public versions of the same extension, confusing.

Turns out you can’t change a public extension back to be private, either via the UI or by uploading a corrected VSIX. Also you can’t delete any public extension that has ever been downloaded, and my previously private one had been downloaded once, by me for testing.

So my only option was to un-publish the previously private extension so only the correct version was visible in the public marketplace.

This meant I had to also alter my CI/CD process to change the extensionID of my private extension so I could publish a new private version of the extension.

Luckily, as all the GUIDs for the tasks within the extension did not change once I had installed the new version of the extension I had mispublished in my test VSTS instance my pipeline still worked.

Only downside is I am left with an un-publish ‘dead’ version listed in my private view of the marketplace. This is not a problem, just does not look ‘neat and tidy’

System Center Data Protection Manager 2016 Delegated Administrator

I recently had a requirement to add a delegated administrator to DPM 2016. While it’s possible to configure self-service recovery, there doesn’t appear to be any way that I can configure another user to perform the delegated admin role as there is in some other System Center products.

It is possible to configure another user to be a delegated DPM admin if you’re willing to roll up your sleeves and get a little grubby with the config however! Note that I’m fairly sure that doing this will impact support, but it’s easy to undo if required.

The problem:

  1. There’s nothing in the DPM interface that appears to allow configuration of a delegated admin.
  2. Adding a user as a local admin of the DPM server still doesn’t allow the user concerned to administer DPM. When trying to launch the DPM console, the following error is shown:
    Unable to connect to DPM server error
  3. Granting the user logon locally permissions still requires that the user elevates when launching the DPM console, so realistically they should be made a local admin on the DPM server.

The solution:

  1. Grant the user local admin rights on the DPM server. I’d strongly suggest creating a dedicated admin account for the user rather than using their day-to-day account for this purpose.
  2. On the SQL instance that DPM uses, configure the following:
    1. Create a new login for the account that will be used as a delegated admin.
    2. Right-click the new account and select ‘Properties’.
    3. Select the DPM database and in the database role membership section of the dialog, select the appropriate DPM-related permissions for the user. To give the user full admin permissions on DPM, select all of the ‘MSDPM…’ role checkboxes:
      DPM delegated admin SQL rights
    4. Click OK to close the dialog.
    5. Check that the user can a) log onto the DPM server and b) successfully launch the DPM admin console and administer the service.

Using VSTS Gates to help improve my deployment pipeline of VSTS Extensions to the Visual Studio Marketplace

My existing VSTS CI/CD process has a problem that the deployment of a VSTS extension, from the moment it is uploaded to when it’s tasks are available to a build agent, is not instantiation. The process can potentially take a few minutes to roll out. The problem this delay causes is a perfect candidate for using VSTS Release Gates; using the gate to make sure the expected version of a task is available to an agent before running the next stage of the CD pipeline e.g waiting after deploying a private build of an extension before trying to run functional tests.

The problem is how to achieve this with the current VSTS gate options?

What did not work

My first thought was to use the Invoke HTTP REST API gate, calling the VSTS API https://<your vsts instance name>.visualstudio.com/_apis/distributedtask/tasks/<GUID of Task>. This API call returns a block of JSON containing details about the deployed task visible to the specified VSTS instance. In theory you can parse this data with a JSONPATH query in the gates success criteria parameter to make sure the correct version of the task is deployed e.g. eq($.value[?(@.name == “BuildRetensionTask”)].contributionVersion, “1.2.3”)

However, there is a problem. At this time the Invoke HTTP REST API gate task does not support the == equality operator in it’s success criteria field. I understand this will be addressed in the future, but the fact it is currently missing is a block to my current needs.

Next I thought I could write a custom VSTS gate. These are basically ‘run on server’ tasks with a suitably crafted JSON manifest. The problem here is that this type of task does not allow any code (Node.JS or PowerShell) to be run. They only have a limited capability to invoke HTTP APIs or write messages to service bus. So I could not implement the code I needed to process the API response. So another dead end.

What did work

The answer, after a suggestion from the VSTS Release Management team at Microsoft, was to try the Azure Function gate.

To do this I created a new Azure Function. I did this using the Azure Portal, picking the consumption billing model, C# and securing the function with a function key, basically the default options.

I then added the C# function code (stored in GitHub), to my newly created Azure Function. This function code takes

  • The name of the VSTS instance
  • A personal access token (PAT) to access the VSTS instance
  • The GUID of the task to check for
  • And the version to check for

It then returns a JSON block with true or false based on whether the required task version can be found. If any of the parameters are invalid an API error is returned

By passing in this set of arguments my idea was that a single Azure Function could be used to check for the deployment of all my tasks.

Note: Now I do realise I could also create a release pipeline for the Azure Function, but I chose to just create it via the Azure Portal. I know this is not best practice, but this was just a proof of concept. As usual the danger here is that this proof of concept might be one of those that is too useful and lives forever!

To use the Azure Function

Using the Azure function is simple

    • Added an Azure Function gate to a VSTS release
    • Set the URL parameter for the Azure Function. This value can be found from the Azure Portal. Note that you don’t need the Function Code query parameter in the URL as this is provided with the next gate parameter. I chose to use a variable group variable for this parameter so it was easy to reuse between many CD pipelines
    • Set the Function Key parameter for the Azure Function, again you get this from the Azure Portal. This time I used a secure variable group variable
    • Set the Method parameter to POST
    • Set the Header content type as JSON
{
      "Content-Type": "application/json"
}
    • Set the Body to contain the details of the VSTS instance and Task to check. This time I used a mixture of variable group variables, release specific variables (the GUID) and environment build/release variables. The key here is I got the version from the primary release artifact $(BUILD.BUILDNUMBER) so the correct version of the tasks is tested for automatically
{
     "instance": "$(instance)",
     "pat": "$(pat)",
     "taskguid": "$(taskGuid)",
     "version": "$(BUILD.BUILDNUMBER)"
}
  • Finally set  the Advanced/Completion Event to ApiResponse with the success criteria of
    eq(root['Deployed'], 'true')

Once this was done I was able to use the Azure function as a VSTS gate as required

image

Summary

So I now have a gate that makes sure that for a given VSTS instance a task of a given version has been deployed.

If you need this functionality all you need to do is create your own Azure Function instance, drop in my code and configure the VSTS gate appropriately.

When equality == operator becomes available for JSONPATH in the REST API Gate I might consider a swap back to a basic REST call, it is less complex to setup, but we shall see. The Azure function model does appear to work well

Renaming an In-Use Content Type in SharePoint Online

Design of SharePoint content Types for SharePoint, and in particular SharePoint Online is very important. Care must be taken to ensure that the design is appropriate for the environment as changes made later can impose significant management overheads. In particular, if a Content Type is put to use (I.e. is assigned to a list/library), this can complicate changes made at a point following initial deployment.

Some Content Type operations are simple, e.g. adding a column. This will work as expected, with the new column rippling all the way down to the in-use Content Types.

Renaming a Content Type potentially falls under the ‘more difficult’ category, in particular if it’s been assigned to a list/library. This is due to the way that SharePoint handles this process, with the Content Type that is assigned to the list/library being a child content type of that published to a site collection.

I’d still strongly recommend using the Content Type Hub (hidden site collection, available on /sites/contenttypehub) to centrally manage and publish content types. A change to the name of a content type made here, then the content type being republished will rename the content type in the content type gallery in each site collection. If the content type is attached to a list/library however as this is a child content type, this will not be renamed, so you end up in the scenario that the gallery reflects the name change, while the instance attached to the list/library does not.

Looking at the list of content types attached to a list/library, and clicking through on the content type that you wish to change does allow you to change the content type from read-only to writeable. This then allows you to change the content type’s name, however if you have lots of libraries and/or lots of content types to process, this gets laborious very quickly. PowerShell to the rescue again!

The following script is a sample that can be used to change the name of a content type that is attached to a set of lists/libraries:

[sourcecode language='powershell'  padlinenumbers='true']
$SiteUrl = "https://domain.sharepoint.com/teams/SiteCollection"  
$UserName = "Andy@o365domain.com"  
# Ask the user for the password
$Password = Read-Host -Prompt "Enter your password: " -AsSecureString

# List of lists/libraries to process
$libraries = @("Library1","Library2","Library3")

# Add references to the CSOM libraries
Add-Type -Path "C:<Path-to-CSOM-libraries>Microsoft.SharePoint.Client.dll" 
Add-Type -Path "C:<Path-to-CSOM-libraries>Microsoft.SharePoint.Client.Runtime.dll" 

# Connect
$spoCtx = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)  
$spoCredentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Username, $Password)   
$spoCtx.Credentials = $spoCredentials

# Load the web context
$web = $spoCtx.web
$spoCtx.load($web)
$spoCtx.executeQuery()

# Process the lists/libraries
foreach ($lib in $libraries) {
    $list = $web.lists.getbytitle("$lib")
    $spoCtx.load($list)
    $spoCtx.executeQuery()

    # Load the content types attached to the list/library
    $CTs = $list.ContentTypes
    $spoCtx.load($CTs)
    $spoCtx.executeQuery()

    $IDToUse = ""

    Write-Host "Processing library $lib" -ForegroundColor Yellow
    foreach ($CT in $CTs) 
    { 
        Write-Host "-- " $CT.Name $Ct.Id
        if ($CT.Name -eq "Content Type To Change")
        {
            $IDToUse = $CT.Id
            Write-Host "Using this one..." -ForegroundColor Green
        }
    }

    # Grab a reference to the content type we want to change
    $CT = $list.ContentTypes.getbyid($IDToUse)
    $spoCtx.load($CT)
    $spoCtx.executeQuery()

    if ($CT -ne $null)
    {
        # Set the content type to be writeable to be able to update it
        Write-Host "Setting content type to ReadOnly = false" -ForegroundColor Green
        $CT.ReadOnly = $false
        $CT.Update($false)
        $spoCtx.load($CT)
        $spoCtx.executeQuery()

        # Modify the content type name
        Write-Host "Processing Content type..." -ForegroundColor Cyan
        $CT.Name = "Content Type That Has Been Changed"
        $CT.Update($false)
        $spoCtx.load($CT)
        $spoCtx.executeQuery()

        # Return the content type to read-only
        Write-Host "Setting content type to ReadOnly = true" -ForegroundColor Green
        $CT.ReadOnly = $true
        $CT.Update($false)
        $spoCtx.load($CT)
        $spoCtx.executeQuery()
    }
}
[/sourcecode]

Fixing a ‘git-lfs filter-process: gif-lfs: command not found’ error in Visual Studio 2017

I am currently looking at the best way to migrate a large legacy codebase from TFVC to Git. There are a number of ways I could do this, as I have posted about before. Obviously, I have ruled out anything that tries to migrate history as ‘that way hell lies’; if people need to see history they will be able to look at the archived TFVC instance. TFVC and Git are just too different in the way they work to make history migrations worth the effort in my opinion.

So as part of this migration and re-structuring I am looking at using Git Submodules and Git Large File System (LFS) to help divide the monolithic code base into front-end, back-end and shared service modules; using LFS to manage large media files used in integration test cases.

From the PowerShell command prompt, using Git 2.16.2, all my trials were successful, I could achieve what I wanted. However when I tried accessing my trial repos using Visual Studio 2017 I saw issues

Submodules

Firstly there are known limitations with Git submodules in Visual Studio Team Explorer. At this time you can clone a repo that has submodules, but you cannot manage the relationships between repos or commit to a submodule from inside Visual Studio.

This is unlike the Git command line, which allows actions to span a parent and child repo with a single command, Git just works it out if you pass the right parameters

There is a request on UserVoice to add these functions to Visual Studio, vote for it if you think it is important, I have.

Large File System

The big problem I had was with LFS, which is meant to work in Visual Studio since 2015.2.

Again from the command line operations were seamless, I just installed Git 2.16.2 via Chocolaty and got LFS support without installing anything else. So I was able to enable LFS support on a repo

git lfs install
git lfs track '*.bin'
git add .gitattributes

and manage standard and large (.bin) files without any problems

However, when I tried to make use of this cloned LFS enabled repo from inside Visual Studio by staging a new large .bin file I got an error ‘git-lfs filter-process: gif-lfs: command not found’

image

On reading around this error it suggested that the separate git-lfs package needed to be installed. I did this, making sure that the path to the git-lfs.exe (C:Program FilesGit LFS) was in my path, but I still had the problem.

This is where I got stuck and hence needed to get some help from the Microsoft Visual Studio support team.

After a good deal tracing they spotted the problem. The path to git-lfs.exe was at the end of my rather long PATH list. It seems Visual Studio was truncating this list of paths, so as the error suggested Visual Studio could not find git-lfs.exe.

It is unclear to me whether the command prompt just did not suffer this PATH length issue, or was using a different means to resolve LFS feature. It should be noted from the command line LFS commands were available as soon as I installed Git 2.16.2. I did not have to add the Git LFS package.

So the fix was simple, move the entry for ‘C:Program FilesGit LFS’ to the start of my PATH list and everything worked in Visual Studio.

It should be noted I really need to look at whether I need everything in my somewhat long PATH list. It’s been too long since I re-paved my laptop, there is a lot of strange bits installed.

Thanks again to the Visual Studio Support team for getting me unblocked on this.

DDD is now live for Session Submission!

I am delighted to announce the DDD site is now live for session submissions! Get in early to make sure you don’t forget. Always a great opportunity to get in front of an enthusiastic crowd.

Please do submit, and we would welcome first-timers. The audience is friendly, and so many speakers, that are now on the international circuit got their start at a DDD event. We hope to be able to do something more to support first-time speakers, so let us know if you fall into that category.

Once again DDD is free for all to attend, thanks to some great sponsors – if you would like to be sponsor DDD, please email ddd@blackmarble.com. Every bit of sponsorship helps make this a great event.

Building private VSTS build agents using the Microsoft Packer based agent image creation model

Background

Having automated builds is essential to any good development process. Irrespective of the build engine in use, VSTS, Jenkins etc. you need to have a means to create the VMs that are running the builds.

You can of course do this by hand, but in many ways you are just extending the old ‘it works on my PC – the developer can build it only on their own PC’ problem i.e. it is hard to be sure what version of tools are in use. This is made worse by the fact it is too tempting for someone to remote onto the build VM to update some SDK or tool without anyone else’s knowledge.

In an endeavour to address this problem we need a means to create our build VMs in a consistent standardised manner i.e a configuration as code model.

At Black Marble we have been using Lability to build our lab environments and there is no reason we could not use the same system to create our VSTS build agent VMs

  • Creating base VHDs disk images with patched copies of Windows installed (which we update on a regular basis)
  • Use Lability to provision all the required tools – this would need to include all the associated reboots these installers would require. Noting that rebooting and restarting at the correct place, for non DSC based resources, is not Lability’s strongest feature i.e. you have to do all the work in custom code

However, there is an alternative. Microsoft have made their Packer based method of creating VSTS Azure hosted agents available on GitHub. Hence, it made sense to me to base our build agent creation system on this standardised image; thus allowing easier migration of builds between private and hosted build agent pools whether in the cloud or on premises, due to the fact they had the same tools installed.

The Basic Process

To enable this way of working I forked the Microsoft repo and modified the Packer JSON configuration file to build Hyper-V based images as opposed to Azure ones. I aimed to make as few changes as possible to ease the process of keeping my forked repo in sync with future changes to the Microsoft standard build agent. In effect replacing the builder section of the packer configuration and leaving the providers unaltered

So, in doing this I learnt a few things

Which ISO to use?

Make sure you use a current Operating System ISO. First it save time as it is already patched; but more importantly the provider scripts in the Microsoft configuration assume certain Windows features are available for installation (Containers with Docker support specifically) that were not present on the 2016 RTM ISO

Building an Answer.ISO

In the sample I found for the Packer hyperv-iso builder the AutoUnattended.XML answers file is provided on an ISO (as opposed to a virtual floppy as floppies are not support on Gen2 HyperV VMs). This means when you edit the answers file you need to rebuild the ISO prior to running Packer.

The sample script to do this has lines to ‘Enable UEFI and disable Non EUFI’; I found that if these lines of PowerShell were run the answers file was ignored on the ISO. I had to comment them out. It seems an AutoUnattended.XML answers file edited in VSCode is the correct encoding by default.

I also found that if I ran the PowerShell script to create the ISO from within VSCode’s integrated terminal the ISO builder mkisofs.exe failed with an internal error. However, it worked fine from a default PowerShell windows.

Installing the .NET 3.5 Feature

When a provider tried to install the .NET 3.5 feature using the command

Install-WindowsFeature -Name NET-Framework-Features -IncludeAllSubFeature

it failed.

Seems this is a bug in Windows 2016 and the workaround is to specify the –Source location on the install media

Install-WindowsFeature -Name NET-Framework-Features -IncludeAllSubFeature -Source “D:sourcessxs”

Once the script was modified in this manner it ran without error

Well how long does it take?

The Packer process is slow, Microsoft say for an Azure VM it can take up to over 8 hours. A HyperV VM is no faster.

I also found the process a bit brittle. I had to restart the process a good few times as….

  • I ran out of disk space (no unsurprising this broke the process)
  • The new VM did not get a DHCP assigned IP address when connected to the network via the HyperV Default Switch. A reboot of my HyperV host PC fixed this.
  • Packer decided the VM had rebooted when it had not – usually due to a slow install of some feature or network issues
  • My Laptop went to sleep and caused one of the above problems

So I have a SysPrep’d VHD now what do I do with it now?

At this point I have options of what to do with this new exported HyperV image. I could manually create build agent VM instances.

However, it appeals to me to use this new VHD as a based image for Lability, replacing our default ‘empty patched Operating System’ image creation system, so I have a nice consistent way to provision VMs onto our Hyper-V servers.

Yorkshire Venue for the Global DevOps BootCamp 2018

I am really pleased that we at Black Marble are again the first UK location announcing that we are hosting an event on June 16th as part of the 2018 Global DevOps BootCamp. As the event’s site says…

“The Global DevOps Bootcamp takes place once a year on venues all over the world. The more people joining in, the better it gets! The Global DevOps Bootcamp is a free one-day event hosted by local passionate DevOps communities around the globe and centrally organized by Xpirit & Solidify and sponsored by Microsoft. This event is all about DevOps on the Microsoft Stack. It shows the latest DevOps trends and insights in modern technologies. It is an amazing combination between getting your hands dirty and sharing experience and knowledge in VSTS, Azure, DevOps with other community members.” 

Global DevOps Bootcamp 2018 @ Black Marble

For more details of the planned event content see the central Global DevOps Bootcamp site and to register for the Black Marble hosted venue click here

New release of my ‘Generate Parameters.xml tools to add support for app.config files

I recently released an updated version of my Generate Parameters.XML tool for Visual Studio. This release adds support for generating parameters.xml files from app.config files as well as web.config files

Why you might ask why add support for app.config files when the parameters.xml model is only part of WebDeploy?

Well, at Black Marble we like the model of updating a single file using a tokenised set of parameters from within our DevOps CI/CD pipelines. It makes it easy to take release variables and write them, at deploy time, into a parameters.xml file to be injected into a machine’s configuration. We wanted to extend this to configuring services and the like where for example a DLL based service is configured with a mycode.dll.config file

The injection process of the parameters.xml into a web.config file is automatically done as part of the WebDeploy process (or a VSTS extension wrapping WebDeploy), but if you want to use a similar model for app.config files then you need some PowerShell.

For example, if we have the app.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <applicationSettings>
     <Service.Properties.Settings>
       <setting name="Directory1" serializeAs="String">
         <value>C:ABC1111</value>
       </setting>
       <setting name="Directory2" serializeAs="String">
         <value>C:abc2222</value>
       </setting>
     </Service.Properties.Settings>
   </applicationSettings>
   <appSettings>
     <add key="AppSetting1" value="123" />
     <add key="AppSetting2" value="456" />
   </appSettings>
     <startup> 
         <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
     </startup>
</configuration>

My extension generates a tokenised parameters.xml file

<parameters>
  <parameter name="AppSetting1" description="Description for AppSetting1" defaultvalue="__APPSETTING1__" tags="">
     <parameterentry kind="XmlFile" scope="\App.config$" match="/configuration/appSettings/add[@key='AppSetting1']/@value" />
  </parameter>
  <parameter name="AppSetting2" description="Description for AppSetting2" defaultvalue="__APPSETTING2__" tags="">
     <parameterentry kind="XmlFile" scope="\App.config$" match="/configuration/appSettings/add[@key='AppSetting2']/@value" />
  </parameter>
  <parameter name="Directory1" description="Description for Directory1" defaultvalue="__DIRECTORY1__" tags="">
     <parameterentry kind="XmlFile" scope="\App.config$" match="/configuration/applicationSettings/Service.Properties.Settings/setting[@name='Directory1']/value/text()" />
  </parameter>
  <parameter name="Directory2" description="Description for Directory2" defaultvalue="__DIRECTORY2__" tags="">
     <parameterentry kind="XmlFile" scope="\App.config$" match="/configuration/applicationSettings/Service.Properties.Settings/setting[@name='Directory2']/value/text()" />
  </parameter>
 </parameters>

The values in this parameters.xml file can be updated using a CI/CD replace tokens task, we use Colin’s ALM Corner Build & Release Tools, Replace Tokens, in exactly the same way as we would for a web.config

Finally the following PowerShell can be used to update the app.config from this parameters.xml

Thus giving a consistent way of updating configuration files for both web.config and app.config files