Showing posts with label Deployment. Show all posts
Showing posts with label Deployment. Show all posts

Thursday, August 4, 2016

Application deployments, best practices


With more and more teams getting ready with practices like continuous deployment and DevOps, there are lot of questions about the best practices regarding application deployment. I've been working with projects and setting up continuous delivery and automated deployments as part of the work. Below are some of the practices that I follow for application deployments.
Automate
There is a lot of focus on automation nowadays and a lot of tools and information is available to automate the manual actions in the deployment process. Almost every deployment automation tools allows complete customization and extensibility to make use of scripting languages that are used to carry out OS specific tasks. This makes it very easy to use these tools in the process even though you have a specific task to perform that is not available out-of-the-box in the tool that you are using. For .NET applications you can use PowerShell for example to do almost anything on windows environments.
Build once, deploy it many times
For avoiding situations like, "it works in the Test environment but not in Production". you need to use the same binaries that were installed and tested on lower environments when deploying to higher environments. For automated deployments to go risk free, we need to have the confidence that we are deploying the same package that was tested and approved from a lower environment. If you are planning to recompile the code base every time a deployment is happening, there are lot of hidden changes and troubles coming along with that process, which makes the deployments unstable.
Maintain a repository for the build artifacts
Things can sometimes go wrong and sometimes its needed to roll-back to a known-working version. Having an artifact repository for your packages not only will help you in using the same version on every environment, but also to locate old versions of an application without having to build them again. Also its a stable version that was working before!!!
Change configuration variables at deployment time not at build time
One of the common challenges with applications which migrate through the usual lifecycle of environments such as development, test and production is getting the configuration context right. Some of the most common examples are connection strings, trace modes etc. There are multiple ways to handle this problem with one of them being making use of configuration file transformations as part of the build process. This can introduce the same risk as rebuilding binaries for each environment. It’s a good practice to make use of the deployment tool to apply environment specific configuration changes during deployment time to the applications. This will allow you to be flexible with the latest changes in the environments.
Don’t customize the deployment process or steps based on environments
It’s always good to treat your deployment process the same across environments. This helps you to create more reliable and predictable deployments across environments. A lot can go wrong when you try to make adjustments in the deployment process based on environments. Let’s keep it simple J

Monday, September 29, 2014

Continuous Deployment - Remote execution of PowerShell scripts from your build process


Including Windows PowerShell script as part of your build and deployment process, brings you the flexibility of easily and effectively customize your packaging and deployment process. With the proper combination of environment configuration files (XML) and PowerShell scripts you can achieve the impossible. This post will show you how to run Windows PowerShell scripts remotely from a TFS build process.
Using CredSSP for second-hop remoting

One common issue with PowerShell remoting is the “double hop” problem. When the scripts are executed remotely on a Server A and then it tries to connect from Server A to Server B, the second connection fails to send the credentials to that server. As a result the second server fails to authenticate the request and rejects the connection. To get around this issue you need to use the CredSSP authentication mechanism in PowerShell.
Credential Security Service Provider (CredSSP) is a new security service provider that is available through the Security Support Provider Interface (SSPI) in Windows. CredSSP enables an application to delegate the user’s credentials from the client (by using the client-side SSP) to the target server (through the server-side SSP).
To setup the machines, for CredSSP you can follow the below given steps:

On the local computer that needs to connect to a remote server, execute the command Enable-WSManCredSSP -Role client -DelegateComputer *. This will  make the local computer role as client since so that it can connect to remote computer which acts as server.
On the remote server run the command Enable-WSManCredSSP -Role server.

Remote execution of the PowerShell script from TFS build

For executing PowerShell scripts from the C# code you need to make use of the API’s in the System.Management.Automation.dll assembly. You can use the PowerShell instance directly but a better way is to create a Runspace instance. Every PowerShell instance works in a Runspace. You can have multiple Runspaces to connect to different remote hosts at the same time.
To get the remote execution working properly you need to first construct an instance of WSManConnectionInfo and pass that on to the RunspaceFactory.CreateRunspace(..) method. You can use the following code snippet to construct the connection object.

private PSCredential BuildCredentials(string username, string domain, string password)
{
    PSCredential credential = null;
    if (String.IsNullOrWhiteSpace(username))
    {
        return credential;
    }
    if (!String.IsNullOrWhiteSpace(domain))
    {
        username = domain + "\\" + username;
    }
    var securePassword = new SecureString();
    if (!String.IsNullOrEmpty(password))
    {
        foreach (char c in password)
        {
            securePassword.AppendChar(c);
        }
    }
    securePassword.MakeReadOnly();
    credential = new PSCredential(username, securePassword);
    return credential;
}

private WSManConnectionInfo ConstructConnectionInfo()
{
    //The connection info values are created after looking into the ouput of the command in the target machine

    /*
        * dir WSMan:\localhost\Listener\Listener_*\*
        * /
    //Output           
    /* 
        * Name                      Value                                                             Type

        * ----                      -----                                                             ----

        * Address                   *                                                                 System.String

        * Transport                 HTTP                                                              System.String

        * Port                      5985                                                              System.String

        * URLPrefix                 wsman                                                             System.String

        * 

        */
   const string SHELL_URI = http://schemas.microsoft.com/powershell/Microsoft.PowerShell;
    var credentials = BuildCredentials("username", "domain", "password");
    var connectionInfo = new WSManConnectionInfo(false, "remoteserver", 5985, "/wsman", SHELL_URI, credentials);
    connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Credssp;
    return connectionInfo;
}
Next you can use this connection object to pass it to the RunspaceFactory.CreateRunspace method and invoke the PowerShell script as given below.
var scriptOutput = new ScriptOutput();
var tfsBuildHost = new TFSBuildHost(scriptOutput);
var connection = ConstructConnectionInfo();
 
using (var runspace = RunspaceFactory.CreateRunspace(tfsBuildHost, connection))
{
    runspace.Open();
    InvokePSScript(runspace, scriptOutput);
    runspace.Close();
}
 
 
private void InvokePSScript(Runspace runspace, ScriptOutput scriptOutput)
{
    using (var ps = PowerShell.Create())
    {
        ps.Runspace = runspace;
        var commandToExecute = ConstructScriptExecutionCommand("path to script file", "parameter1", "parameter2");
        ps.AddScript(commandToExecute);
        try
        {
            var results = ps.Invoke();
            foreach (var result in results)
            {
                if (result.BaseObject != null)
                {
                    scriptOutput.WriteResults(result.BaseObject.ToString());
                }
            }
        }
        catch(Exception)
        {
            if (ps.InvocationStateInfo.State != PSInvocationState.Failed)
            {
                return;
            }
            scriptOutput.WriteError(ps.InvocationStateInfo.Reason.Message); 
        }
    }
}

You can also notice the TFSBuildHost object in the code. This class provides communications between the Windows PowerShell engine and the user. The implementation details are as given below.
internal class TFSBuildHost : PSHost{
    private ScriptOutput _output;
    private CultureInfo originalCultureInfo =
        System.Threading.Thread.CurrentThread.CurrentCulture;
    private CultureInfo originalUICultureInfo =
        System.Threading.Thread.CurrentThread.CurrentUICulture;
    private Guid _hostId = Guid.NewGuid();
    private TFSBuildUserInterface _tfsBuildInterface;
 
    public TFSBuildHost(ScriptOutput output)
    {
        this._output = output;
        _tfsBuildInterface = new TFSBuildUserInterface(_output);
    }
    public override System.Globalization.CultureInfo CurrentCulture
    {
        get { return this.originalCultureInfo; }
    }
    public override System.Globalization.CultureInfo CurrentUICulture
    {
        get { return this.originalUICultureInfo; }
    }
    public override Guid InstanceId
    {
        get { return this._hostId; }
    }
    public override string Name
    {
        get { return "TFSBuildPowerShellImplementation"; }
    }
    public override Version Version
    {
        get { return new Version(1, 0, 0, 0); }
    }
    public override PSHostUserInterface UI
    {
        get { return this._tfsBuildInterface; }
    }
    public override void NotifyBeginApplication()
    {
        _output.Started = true;
    }
    public override void NotifyEndApplication()
    {
        _output.Started = false;
        _output.Stopped = true;
    }
    public override void SetShouldExit(int exitCode)
    {
        this._output.ShouldExit = true;
        this._output.ExitCode = exitCode;
    }
    public override void EnterNestedPrompt()
    {
        throw new NotImplementedException(
                "The method or operation is not implemented.");
    }
    public override void ExitNestedPrompt()
    {
        throw new NotImplementedException(
                "The method or operation is not implemented.");
    }
} 

internal class TFSBuildUserInterface : PSHostUserInterface
{
    private ScriptOutput _output; 
    private TFSBuildRawUserInterface _tfsBuildRawUi = new TFSBuildRawUserInterface();
 
    public TFSBuildUserInterface(ScriptOutput output)
    {
        _output = output;
    }
    public override PSHostRawUserInterface RawUI
    {
        get { return this._tfsBuildRawUi; }
    }
    public override string ReadLine()
    {
        return Environment.NewLine;
    }
    public override void Write(string value)
    {
        _output.Write(value);
    }
    public override void Write(
                                ConsoleColor foregroundColor,
                                ConsoleColor backgroundColor,
                                string value)
    {
        //Ignore the colors for TFS build process.
        _output.Write(value);
    }
    public override void WriteDebugLine(string message)
    {
        Debug.WriteLine(String.Format("DEBUG: {0}", message));
    }
    public override void WriteErrorLine(string value)
    {
        _output.WriteError(value);
    }
    public override void WriteLine()
    {
        _output.WriteLine();
    }
    public override void WriteLine(string value)
    {
        _output.WriteLine(value);
    }
    public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
    {
        //Ignore the colors for TFS build process.
        _output.WriteLine(value);
    }
}

internal class TFSBuildRawUserInterface : PSHostRawUserInterface
{
    private ConsoleColor _backColor = ConsoleColor.Black;
    private ConsoleColor _foreColor = ConsoleColor.White;
    private Size _bufferSize = new Size(300, 900);
    private Size _windowSize = new Size(100, 400);
    private Coordinates _cursorPosition = new Coordinates { X = 0, Y = 0 };
    private Coordinates _windowPosition = new Coordinates { X = 50, Y = 10 };
    private int _cursorSize = 1;
    private string _title = "TFS build process";
     
    public override ConsoleColor BackgroundColor
    {
        get { return _backColor; }
        set { _backColor = value; }
    }
    public override Size BufferSize
    {
        get { return _bufferSize; }
        set { _bufferSize = value; }
    }
    public override Coordinates CursorPosition
    {
        get { return _cursorPosition; }
        set { _cursorPosition = value; }
    }
    public override int CursorSize
    {
        get { return _cursorSize; }
        set { _cursorSize = value; }
    }
    public override ConsoleColor ForegroundColor
    {
        get { return _foreColor; }
        set { _foreColor = value; }
    }
    public override bool KeyAvailable
    {
        get { return false; }
    }
    public override Size MaxPhysicalWindowSize
    {
        get { return new Size(500, 2000); }
    }
    public override Size MaxWindowSize
    {
        get { return new Size(500, 2000); }
    }
    public override Coordinates WindowPosition
    {
        get { return _windowPosition; }
        set { _windowPosition = value; }
    }
    public override Size WindowSize
    {
        get { return _windowSize; }
        set { _windowSize = value; }
    }
    public override string WindowTitle
    {
        get { return _title; }
        set { _title = value; }
    }
}