Thursday, May 14, 2009

techNote_Append Registry Via Visual Studio Setup Project


How can I append a directory to the PATH environment variable when creating a Visual Studio Setup project.


Objective: To be able to APPEND Path Environment Variable by changing Windows Registry setting,  using a MSI Windows Installer created by a Visual Studio Setup project.

End Product: An exe and a msi file to install a program and also change registry setting automatically. When the program is uninstalled (via Add or Remove programs) then the registry setting will be changed to the pre-installation state.

Reference: This article is based on the article and code found below, but with necessary modifications.
http://askville.amazon.com/append-directory-PATH-environment-variable-Visual-Studio-Deployment-installation-Project/AnswerViewer.do?requestId=4628663

The code presented in askville, assumes that the path to be added is only the path where the programs are installed. For example if pre-install path = "c:\temp", and the program is to be installed in "c:\programs", then the final path would be "c:\temp;c:\programs".

With the modifications I made to the code, it allows the user to setup multiple additional paths, not only where the program is installed. The scenario is like this:
Preinstall path variable: c:\temp
Additional paths: c:\dirA;c:\dirB
Installation path: c:\programs
Intended final path: c:\temp;c:\dirA;c:\dirB;c:\programs
After uninstall path: c:\temp

Basically, the idea is to create a custom action class and then configure the Setup project.
Here are the detailed steps of how to construct the Visual Studio setup project.

1. Copy the code below and put into an empty C# class and called in Installer1.cs. Need to change the Namespace in the code to tailor to the namespace of your project.

Basically, the C# code override the Install, Uninstall, and Rollback methods of the standard Installer class.

2. The C# class file in step 1 need to be put into a C# project. It could be a new project or it could one of your working project to be included in the Setup Project.

3. Create the Visual Studio Setup Project. Add the C# project in step 2 to the setup. This is done in the normal way by right clicking the setup project in Solution Explorer. Click Add -> Project Output. Then select that C# project and select Primary Output, then click OK.

4. Right click on your Setup project, select View|Custom Actions, and then right-click on Install, Rollback, and Uninstall to add the assembly containing the custom action class to each one.

5. Finally add any additional directories to the user path via the Registry by the following:
i) Assume that the new variable to be appended to the User Path is called "MySetup". This name "MySetup"
is hard coded to the function GetAddedPath, which can be changed as desired.
ii) Right click on the setup project, choose View -> Registry.
iii) In the Registry editor, go to HKEY_CURRENT_USER and add new Key "Environment" if it is not there already.
iv) Click on the new "Environment" entry and ADD -> New -> Environment String Value.
v) on the right hand pane, name the new variable as "MySetup".
vi) Click on the entry "MySetup" and in its property box, write the value of any subdirectories you wish to
include in the user path.
---------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using Microsoft.Win32;

namespace YOUR_OWN_NAMESPACE
{
    /// <summary>
    /// A Custom Action class that provides action to append registry for MS Setup project.
    /// </summary>
    [RunInstaller(true)]
    public partial class Installer1 : Installer
    {
        public Installer1()
        {
            InitializeComponent();
        }

        public override void Install(System.Collections.IDictionary stateSaver)
        {
            base.Install(stateSaver);
            string curPath = GetUserPath();
            stateSaver.Add("previousPath", curPath);
            string newPath;
            if (curPath.Length == 0 ) {
                newPath = AddPath(GetAddedPath() , MyPath());
            }else{
                newPath = AddPath(curPath, GetAddedPath()+";"+MyPath());
            }
           
            if (curPath != newPath)
            {
                stateSaver.Add("changedPath", true);
                SetPath(newPath);
            }
            else
                stateSaver.Add("changedPath", false);
        }

        public override void Uninstall(System.Collections.IDictionary savedState)
        {
            base.Uninstall(savedState);
            if ((bool)savedState["changedPath"])
            {
                SetPath(RemovePath(GetUserPath(), MyPath()));
                SetPath(RemovePath(GetUserPath(), GetAddedPath()));
            }
        }

        public override void Rollback(System.Collections.IDictionary savedState)
        {
            base.Rollback(savedState);
            if ((bool)savedState["changedPath"])
                SetPath((string)savedState["previousPath"]);
        }

        internal static string MyPath()
        {
            string myFile = System.Reflection.Assembly.GetExecutingAssembly().Location;
            string myPath = System.IO.Path.GetDirectoryName(myFile);
            return myPath;
        }

        private static RegistryKey GetPathRegKey(bool writable)
        {
            // for the user-specific path...
            return Registry.CurrentUser.OpenSubKey("Environment", writable);

            // for the system-wide path...
            //return Registry.LocalMachine.OpenSubKey(
            //    @"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", writable);
        }

        private static void SetPath(string value)
        {
            using (RegistryKey reg = GetPathRegKey(true))
            {
                reg.SetValue("Path", value, RegistryValueKind.ExpandString);
            }
        }

        internal static string GetUserPath()
        {
            using (RegistryKey reg = GetPathRegKey(false))
            {
                return (string)reg.GetValue("Path", "", RegistryValueOptions.DoNotExpandEnvironmentNames);
            }
        }
        internal static string GetAddedPath()
        {
            using (RegistryKey reg = GetPathRegKey(false))
            {
                return (string)reg.GetValue("MySetup", "", RegistryValueOptions.DoNotExpandEnvironmentNames);
            }
        }


        private static string AddPath(string list, string item)
        {
            List<string> paths = new List<string>(list.Split(';'));

            foreach (string path in paths)
                if (string.Compare(path, item, true) == 0)
                {
                    // already present
                    return list;
                }

            paths.Add(item);
            return string.Join(";", paths.ToArray());
        }

        private static string RemovePath(string list, string item)
        {
            List<string> paths = new List<string>(list.Split(';'));

            for (int i = 0; i < paths.Count; i++)
                if (string.Compare(paths[i], item, true) == 0)
                {
                    paths.RemoveAt(i);
                    return string.Join(";", paths.ToArray());
                }

            // not present
            return list;
        }

    }
}


No comments: