Category Archives: Powershell

DevOps rant: TFS merge discard strategy

Series Overview

I moved to a DevOps team about a year ago and although we’re not really doing DevOps, it’s a good team and we try really hard sometimes! While trying hard I have come across all sorts of funny stuff and recently I have decided to blog about it, maybe someone reading this won’t allow folks to do the same mistakes when presented with the same funny stuff.

Overview

Today, I’m a solid believer that most TFS projects should be on Git, not TFS SVC. Yes Git does have a learning curve over the massively supported by Visual Studio UI TFS SVC, but once that learning curve is climbed, the rewards are greater.

This is especially true on projects that are using PaaS components and are built by folks that love to over-engineer, so instead of a few components, you end up with tens of components and instead of a few config files you should avoid merging, you end up with tens or even hundreds of these. If you are in a Git repo you just combine clever use of Git Attributes with Git-Filter-Branch, however if you are on a TFS repo, your options are a lot more limited.

Real Life Example

I’m currently working with two projects, one should definitely be using Git as the repo as the level of over-engineering is high, and the other fits nicely in TFS.

The super engineered project never knew how to deal with merges, basically for a very long time what they did was do a “blind merge” then manually undo the changes they thought shouldn’t go in. While this was done by a single person, it actually worked, their problems started when other folks started to merge and they didn’t really know what not to merge.

So their solution was simple: let’s create a project configuration per environment per branch. Let’s not argue about the fact that this is a lot harder to maintain, because honestly if it’s over-engineered, going down the path of arguing about maintainability indexes is purely a waste of time for everyone. But instead focus on what this prevents my DevOps team from doing in the scope of this project.

Let’s imagine DevOps is now given the time resources to build a magic button, that when you press it you get a new branch, a new set of environments and a new release pipeline (after we have built the magic buttons that bring expressos and popcorn!). Currently we aren’t very far from this, the only real automation we are missing is the release pipeline, but that’s not that hard.

When you add the fact you now need new configurations and all sorts of crap related to that, like new config transforms, new service configuration files, etc. you immediately drop the idea of automating.

I have been blabbling about the notion of controlling the merge process through scripting a set of tf merge /discard commands for a while now, but every time I mention it I get that feeling I’m talking Portuguese to a bunch of Indian folks and although they always nod saying “Yes” they are actually thinking “I have no idea what this crazy guy is babbling about“.

So the other project, that’s more on the Lean side of things had this same problem recently. But due to its simplicity I decided to step in and instead of babbling anything just write the script for the project and kick-off the merge workflow instead of giving them the chance to wonder into the realms of creating 10 more solution configurations.

Later I sent the script to the first set of guys so that they could understand what I have been babbling about all this time, but the feedback I indirectly got was that it was “technically advanced”

The tf merge /discard PowerShell script


function ApplyMergeDiscard
{
[cmdletbinding(SupportsShouldProcess=$true)]
param
(
[Parameter(Mandatory=$true)]
[string] $LocalPath,
[Parameter(Mandatory=$true)]
[ValidateSet("MainIntoDev", "DevIntoMain")]
[string] $Direction,
[Parameter(Mandatory=$false)]
[string] $BaseDevBranch = "$/YOUR PROJECT/BRANCH1/",
[Parameter(Mandatory=$false)]
[string] $BaseMainBranch = "$/YOUR PROJECT/BRANCH2/"
)
$env:Path = $env:Path + ";C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE"
$discards = @( `
# Some stuff you shouldn't merge
"Stuff1.publish.proj", `
"Stuff2.publish.proj", `
# Some more stuff you shouldn't merge
"Some.Project/AConfiguration.Debug.config", `
"Some.Project/AConfiguration.Release.config" `
)
Set-Location $LocalPath
$discards | ForEach-Object {
if($Direction -eq "MainIntoDev") {
$sourcePath = $BaseMainBranch + $_
$targetPath = $BaseDevBranch + $_
}
else {
$sourcePath = $BaseDevBranch + $_
$targetPath = $BaseMainBranch + $_
}
if($WhatIfPreference -eq $false) {
Write-Verbose "Discarding $sourcePath into $targetPath"
& tf merge /discard $sourcePath $targetPath
}
else {
Write-Host "WhatIf: Discarding $sourcePath into $targetPath"
}
}
}

This scrip supports both -Verbose and -WhatIf commandlet bindings and it’s written in a way that the only thing you actually need to maintain is the array of strings of the sub paths of stuff you don’t want to merge.

So, unlike the feedback I got, this is definitely not rocket science to maintain and it’s a good starting foundation to deal with merges.

You run the script before you actually do the merge, if you didn’t have it right you can simply undo pending changes, tweak the script, and check again. When you’re happy with the discards you perform the merge and then check in.

Managing an Application’s ADLDS through Powershell

Sometimes, an application requires an Authentication provider that both uses an Enterprise’s Active Directory and at the same time stores application scope accounts for external users. Microsoft recommends using Active Directory Lightweight Directory Services, or ADLDS, to accomplish this.

We have a scenario where we have a WPF application that is authenticating in an ADLDS through a WCF Web Service, this application has a specific set of groups, and requires the ADLDS to be properly configured.

To accomplish these tasks, yesterday I had to create 2 powershell scrips, one to setup the entire ADLDS with the default groups and a default user per group for the test team to use during testing, and a second one to add a single user to a specific group.

1. Setting up our entire ADLDS

This script creates a set of defined application groups, and populates each one of them with a test user. It also creates a general admin user. I’m using the ` line escape to make the script readable in the blog.

Import-Module ActiveDirectory

##############################################################################################
# Array with groups for AD LDS
$ADGroups = @("GROUP1", "GROUP2", "GROUP3",
              "GROUP4", "GROUP5", "GROUP6",
              "GROUP7", "GROUP8", "GROUP9",
              "GROUP10", "GROUP11", "GROUP12")
##############################################################################################

##############################################################################################
# Username, Password of an admin account for the AD LDS and the location of the AD LDS
$credUsername = 'MyDomain\MyUser'
$credPassword = 'MyPassword'
$server = 'Myserver'
##############################################################################################

$cred = New-Object System.Management.Automation.PSCredential -ArgumentList `
        @($credUsername,(ConvertTo-SecureString -String $credPassword -AsPlainText -Force))

Write-Host
Write-Host "Creating User Groups ..."

foreach ($element in $ADGroups)
{
    New-ADGroup -Name $element -SamAccountName $element -DisplayName $element `
        -GroupCategory Distribution -GroupScope DomainLocal -Path "CN=Roles,CN=MyCN,DC=PT" `
        -OtherAttributes @{isCriticalSystemObject='TRUE'} -Server $server -Credential $cred
}

Write-Host "Creating the user my_admin ..."

$userName = "my_admin"
New-ADUser -Name $userName -SamAccountName $userName -DisplayName $userName `
    -Path "CN=Users,CN=MyCN,DC=PT" `
    -AccountPassword (ConvertTo-SecureString -AsPlainText "MyPassword" -Force) -Enabled $true `
    -PasswordNeverExpires $true -Server $server -Credential $cred

$user = Get-ADUser -Filter {cn -eq $userName} -SearchBase "CN=Users,CN=MyCN,DC=PT" `
    -server $server -Credential $cred 

Get-ADGroup -Filter {cn -eq "ADMINISTRADOR"} -SearchBase "CN=Roles,CN=MyCN,DC=PT" `
    -server $server -Credential $cred
    | Add-ADGroupMember -Members $user -Server $server -Credential $cred

Write-Host "Creating test users ..."

foreach ($element in $ADGroups)
{
    $userName = "mytest_" + $element.ToLower()
    New-ADUser -Name $userName -SamAccountName $userName -DisplayName $userName `
        -Path "CN=Users,CN=MyCN,DC=PT" `
        -AccountPassword (ConvertTo-SecureString -AsPlainText "testuserpwd" -Force) `
        -Enabled $true -PasswordNeverExpires $true -Server $server -Credential $cred

    $user = Get-ADUser -Filter {cn -eq $userName} -SearchBase "CN=Users,CN=MyCN,DC=PT" `
        -server $server -Credential $cred

    Get-ADGroup -Filter {cn -eq $element} -SearchBase "CN=Roles,CN=MyCN,DC=PT" `
        -server $server -Credential $cred
        | Add-ADGroupMember -Members $user -Server $server -Credential $cred
}

Write-Host
Write-Host "ADLDS sucessfuly configured" -foregroundcolor green

2. Script to create a specific ADLDS user and assign it to a group

This scrip prompts for the user group, the user name and the user password, creates the user and assigns it to the group. I’m using the ` line escape to make the script readable in the blog.

Import-Module ActiveDirectory

##############################################################################################
# Array with groups for AD LDS
$ADGroups = @("GROUP1", "GROUP2", "GROUP3",
              "GROUP4", "GROUP5", "GROUP6",
              "GROUP7", "GROUP8", "GROUP9",
              "GROUP10", "GROUP11", "GROUP12")
##############################################################################################

##############################################################################################
# Username, Password of an admin account for the AD LDS and the location of the AD LDS
$credUsername = 'MyDomain\MyUser'
$credPassword = 'MyPassword'
$server = 'Myserver'
##############################################################################################

$cred = New-Object System.Management.Automation.PSCredential -ArgumentList `
        @($credUsername,(ConvertTo-SecureString -String $credPassword -AsPlainText -Force))

Clear-Host
Write-Host "=================";
Write-Host "= Chose a group =";
Write-Host "=================";

[char]$startNumber = 'A'
[char]$lastNumber = 'L'
[char]$groupNumber = $startNumber

foreach ($element in $ADGroups)
{
    Write-Host $groupNumber : $element
    $groupNumber = [char]([int]$groupNumber +1)
}

$input = Read-Host "Enter the Group Letter"

$groupPosition = ([int]($input.ToUpper().ToCharArray())[0]) - [int]$startNumber
$ADGroup = $ADGroups[$groupPosition]

Write-Host
Write-Host "Creating user in group", $ADGroups[$groupPosition]

$userName = Read-Host "User Name (without spaces)"
$userPassword = Read-Host "User Password"

New-ADUser -Name $userName -SamAccountName $userName -DisplayName $userName `
    -Path "CN=Users,CN=MyCN,DC=PT" `
    -AccountPassword (ConvertTo-SecureString -AsPlainText $userPassword -Force) -Enabled $true `
    -PasswordNeverExpires $true -Server $server -Credential $cred

$user = Get-ADUser -Filter {cn -eq $userName} -SearchBase "CN=Users,CN=MyCN,DC=PT" `
    -server $server -Credential $cred 

Get-ADGroup -Filter {cn -eq $ADGroup} -SearchBase "CN=Roles,CN=MyCN,DC=PT" `
    -server $server -Credential $cred
    | Add-ADGroupMember -Members $user -Server $server -Credential $cred

Write-Host
Write-Host "User created sucessfully" -foregroundcolor green