Implementing a password field in a TFS build definition
Here’s another TFS build related thing I’ve been asked on and off and still cannot find a sample on the web to refer to.
The scenario in this case is pretty obvious; we want a field in a TFS build definition to hold sensitive information that shows a character mask instead of the text value in clear text, i.e. the Password field in the image below:
Click on the […] button and a custom dialog is shown where we type in the password:
In this example I’ve chosen an approach where the build template is extended with a custom argument type representing the sensitive value and displays a custom formatted string instead of the underlying value. The value is edited using a custom editor in the build definition. This is a technique that’s been around since TFS 2010 and Jason Pricket has written a post on the basics of creating a custom editor (http://blogs.msdn.com/b/jpricket/archive/2010/01/18/tfs-2010-custom-process-parameters-part-3-custom-editors.aspx) a long time ago.
You need to create a custom TFS build activity to hold the code for the extension. If you want to learn how to create a TFS build activity see http://msdn.microsoft.com/en-us/library/hh850441.aspx for an example. The custom activity in this example is in the Solidify.RM.Build.Activity namespace.
First add a class in the custom activity to hold the password field. Mark the class Serializable and I also use field attributes to provide a display name and set the RefreshProperties to All (this will make the grid in the build definition refresh when the underlying value changes).
The real logic in this extension is to override the ToString method that renders the value as stars instead of the sensitive string. Here’s the code for the password field:
[Serializable]
public class PasswordField
{
[DisplayName(“Password”), RefreshProperties
(System.ComponentModel.RefreshProperties.All)]
public string Value { get; set; }
public override string ToString()
{
if (this.Value != null)
{
return new string(‘*’, this.Value.Length);
}
return string.Empty;
}
}
Next add a WinForm dialog to the activity that will be used to capture the password, something like this will do fine:
Here’s the simple code for the dialog:
public partial class PasswordDialog : Form
{
public PasswordDialog(string password)
{
InitializeComponent();
passwordTextBox.Text = password;
}
public string Password
{
get
{
return passwordTextBox.Text;
}
set
{
passwordTextBox.Text = value;
}
}
private void okButton_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.OK;
this.Close();
}
private void cancelButton_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}
}
Then to connect the dialog to the build process parameter we need a custom UITypeEditor to display the dialog. The code for the UITypeEditor is more or less boilerplate code in this case, it takes a PasswordField argument as an obect parameter and initializes the PasswordDialog with that value. The we pick up the result and assign it back to the process parameter.
public class PasswordEditor : UITypeEditor
{
public override object EditValue(ITypeDescriptorContext
context, IServiceProvider provider, object value)
{
if (provider != null)
{
var editorService = (IWindowsFormsEditorService)
provider.GetService(typeof(IWindowsFormsEditorService));
if (editorService != null)
{
var password = value as PasswordField;
using (var dialog = new PasswordDialog
(password.Value))
{
dialog.Password = password.Value;
if (editorService.ShowDialog(dialog) ==
DialogResult.OK)
{
password.Value = dialog.Password;
}
}
}
}
return value;
}
public override UITypeEditorEditStyle GetEditStyle
(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
}
That’s the custom activity part. Next we need to customize a build definition to use the password type. First add a new argument to the build process of type Solidify.RM.Build.Activity.PasswordField. I also initialize the argument with a new object of the same type to make sure it’s always defined:
Then configure the metadata for the password argument, this is where we bind the argument to the custom editor. The important detail here is to provide the password editor using the full name of the type and assembly (Solidify.RM.Build.Activity.PasswordEditor,Solidify.RM.Build.Activity):
Finally use the password field as desired in the build template, save, check in the customized template into TFS and you’re ready to go.