Search code examples
c#automated-testsselenium-chromedrivergroup-policy

Using Chromedriver with Developed Extension and Force Install Group Policy


I am working on a web application that has the following environment setup:

  1. The website is designed to work in Chrome
  2. The website depends on a Chrome Extension developed in-house and hosted on the Chrome Play Store
  3. To make getting the extension easier, our systems team has setup the ExtensionInstallForcelist Group Policy (more information here http://dev.chromium.org/administrators/policy-list-3#ExtensionInstallForcelist). This makes it so Chrome can get the extension and update it as needed without manual user interaction
  4. This is all setup on imaged devices (so separate devices, with separate browsers and group policies, that each get the extension on their own)

The problem I'm facing is when trying to use a Chromedriver instance with this group policy.

With the policy enabled, I am getting an error that says "Failed to load extension from: . (extension ID ) is blocked by the administrator." when running our tests.

This seems to happen because we add the extension to our Chromedriver through the Chrome Options (add Extension).

While I can manually turn off the Group Policy on our imaged devices to get around this, I was wondering if there is any setting I can add to the group policy to make it allow the installation of our extension in the Chromedriver?

I've tried a number of ways to get around this otherwise that didn't seem to work:

  1. Command Line altering of the registry values set by the Group Policy - didn't work as, although altering the registry values worked, I still recevied the error on running tests
  2. PowerShell altering of the group policy - won't work as it requires additional install of the Get Group Policy cmdlets
  3. Using the GPMC C# libraries - the libraries seem very much out of date at this point (some only use .NET 2.0). I was able to setup some code to use the classes, but I receive a "reference missing" exception for creating a GPODomain object even though I have the reference in my solution

Thank you all in advance for any suggestions on getting making Chromedriver work with the force install group policy.


Solution

  • After a lot of looking at different answers around the web, I found this solution works.

    Create a GroupPolicy class to handle interacting with it:

    using Microsoft.Win32;
    using Microsoft.Win32.SafeHandles;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace Automation.Core
    {
    [ComImport, Guid("EA502722-A23D-11d1-A7D3-0000F87571E3")]
    internal class GPClass
    {
    }
    
    [ComImport, Guid("EA502723-A23D-11d1-A7D3-0000F87571E3"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IGroupPolicyObject
    {
        uint New([MarshalAs(UnmanagedType.LPWStr)] string domainName, [MarshalAs(UnmanagedType.LPWStr)] string displayName, uint flags);
    
        uint OpenDSGPO([MarshalAs(UnmanagedType.LPWStr)] string path, uint flags);
    
        uint OpenLocalMachineGPO(uint flags);
    
        uint OpenRemoteMachineGPO([MarshalAs(UnmanagedType.LPWStr)] string computerName, uint flags);
    
        uint Save([MarshalAs(UnmanagedType.Bool)] bool machine, [MarshalAs(UnmanagedType.Bool)] bool add, [MarshalAs(UnmanagedType.LPStruct)] Guid extension, [MarshalAs(UnmanagedType.LPStruct)] Guid app);
    
        uint Delete();
    
        uint GetName([MarshalAs(UnmanagedType.LPWStr)] StringBuilder name, int maxLength);
    
        uint GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] StringBuilder name, int maxLength);
    
        uint SetDisplayName([MarshalAs(UnmanagedType.LPWStr)] string name);
    
        uint GetPath([MarshalAs(UnmanagedType.LPWStr)] StringBuilder path, int maxPath);
    
        uint GetDSPath(uint section, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder path, int maxPath);
    
        uint GetFileSysPath(uint section, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder path, int maxPath);
    
        uint GetRegistryKey(uint section, out IntPtr key);
    
        uint GetOptions(out uint options);
    
        uint SetOptions(uint options, uint mask);
    
        uint GetType(out IntPtr gpoType);
    
        uint GetMachineName([MarshalAs(UnmanagedType.LPWStr)] StringBuilder name, int maxLength);
    
        uint GetPropertySheetPages(out IntPtr pages);
    }
    
    
    public enum GroupPolicySection
    {
        Root = 0,
        User = 1,
        Machine = 2,
    }
    
    public abstract class GroupPolicyObject
    {
        protected const int MaxLength = 1024;
    
        /// <summary>
        /// The snap-in that processes .pol files
        /// </summary>
        private static readonly Guid RegistryExtension = new Guid(0x35378EAC, 0x683F, 0x11D2, 0xA8, 0x9A, 0x00, 0xC0, 0x4F, 0xBB, 0xCF, 0xA2);
    
        /// <summary>
        /// This application
        /// </summary>
        private static readonly Guid LocalGuid = new Guid(GetAssemblyAttribute<GuidAttribute>(Assembly.GetExecutingAssembly()).Value);
    
        protected IGroupPolicyObject Instance = (IGroupPolicyObject) new GPClass();
    
        static T GetAssemblyAttribute<T>(ICustomAttributeProvider assembly) where T : Attribute
        {
            object[] attributes = assembly.GetCustomAttributes(typeof(T), true);
            if (attributes.Length == 0)
                return null;
    
            return (T)attributes[0];
        }
    
        public void Save()
        {
            var result = Instance.Save(true, true, RegistryExtension, LocalGuid);
            if (result != 0)
            {
                throw new Exception("Error saving machine settings");
            }
    
            result = Instance.Save(false, true, RegistryExtension, LocalGuid);
            if (result != 0)
            {
                throw new Exception("Error saving user settings");
            }
        }
    
        public RegistryKey GetRootRegistryKey(GroupPolicySection section)
        {
            IntPtr key;
            var result = Instance.GetRegistryKey((uint)section, out key);
            if (result != 0)
            {
                throw new Exception(string.Format("Unable to get section '{0}'", Enum.GetName(typeof(GroupPolicySection), section)));
            }
    
            var handle = new SafeRegistryHandle(key, true);
            return RegistryKey.FromHandle(handle, RegistryView.Default);
        }
    }
    
    public class GroupPolicyObjectSettings
    {
        public readonly bool LoadRegistryInformation;
        public readonly bool Readonly;
    
        public GroupPolicyObjectSettings(bool loadRegistryInfo = true, bool readOnly = false)
        {
            LoadRegistryInformation = loadRegistryInfo;
            Readonly = readOnly;
        }
    
        private const uint RegistryFlag = 0x00000001;
        private const uint ReadonlyFlag = 0x00000002;
    
        internal uint Flag
        {
            get
            {
                uint flag = 0x00000000;
                if (LoadRegistryInformation)
                {
                    flag |= RegistryFlag;
                }
    
                if (Readonly)
                {
                    flag |= ReadonlyFlag;
                }
    
                return flag;
            }
        }
    }
    
    public class ComputerGroupPolicyObject : GroupPolicyObject
    {
        public readonly bool IsLocal;
    
        public ComputerGroupPolicyObject(GroupPolicyObjectSettings options = null)
        {
            options = options ?? new GroupPolicyObjectSettings();
            var result = Instance.OpenLocalMachineGPO(options.Flag);
            if (result != 0)
            {
                throw new Exception("Unable to open local machine GPO");
            }
            IsLocal = true;
        }
    
        public static void SetPolicySetting(string registryInformation, string settingValue, RegistryValueKind registryValueKind)
        {
            string valueName;
            GroupPolicySection section;
            string key = Key(registryInformation, out valueName, out section);
    
            // Thread must be STA
            Exception exception = null;
            var t = new Thread(() =>
            {
                try
                {
                    var gpo = new ComputerGroupPolicyObject();
                    using (RegistryKey rootRegistryKey = gpo.GetRootRegistryKey(section))
                    {
                        // Data can't be null so we can use this value to indicate key must be delete
                        if (settingValue == null)
                        {
                            using (RegistryKey subKey = rootRegistryKey.OpenSubKey(key, true))
                            {
                                if (subKey != null)
                                {
                                    subKey.DeleteValue(valueName);
                                }
                            }
                        }
                        else
                        {
                            using (RegistryKey subKey = rootRegistryKey.CreateSubKey(key))
                            {
                                subKey.SetValue(valueName, settingValue, registryValueKind);
                            }
                        }
                    }
    
                    gpo.Save();
                }
                catch (Exception ex)
                {
                    exception = ex;
                }
            });
            t.SetApartmentState(ApartmentState.STA);
            t.Start();
            t.Join();
    
            if (exception != null)
                throw exception;
        }
    
        public static object GetPolicySetting(string registryInformation)
        {
            string valueName;
            GroupPolicySection section;
            string key = Key(registryInformation, out valueName, out section);
    
            // Thread must be STA
            object result = null;
            var t = new Thread(() =>
            {
                var gpo = new ComputerGroupPolicyObject();
                using (RegistryKey rootRegistryKey = gpo.GetRootRegistryKey(section))
                {
                    // Data can't be null so we can use this value to indicate key must be delete
                    using (RegistryKey subKey = rootRegistryKey.OpenSubKey(key, true))
                    {
                        if (subKey == null)
                        {
                            result = null;
                        }
                        else
                        {
                            result = subKey.GetValue(valueName);
                        }
                    }
                }
            });
            t.SetApartmentState(ApartmentState.STA);
            t.Start();
            t.Join();
    
            return result;
        }
    
        public static string Key(string registryInformation, out string value, out GroupPolicySection section)
        {
            // Parse parameter of format HKCU\Software\Policies\Microsoft\Windows\Personalization!NoChangingSoundScheme
            string[] split = registryInformation.Split('!');
            string key = split[0];
            string hive = key.Substring(0, key.IndexOf('\\'));
            key = key.Substring(key.IndexOf('\\') + 1);
    
            value = split[1];
    
            if (hive.Equals(@"HKLM", StringComparison.OrdinalIgnoreCase)
                || hive.Equals(@"HKEY_LOCAL_MACHINE", StringComparison.OrdinalIgnoreCase))
            {
                section = GroupPolicySection.Machine;
            }
            else
            {
                section = GroupPolicySection.User;
            }
            return key;
        }
    }
    

    }

    Then turn off the Force Install Chrome Extension Group Policy with a call like this:

    [STAThread]
        private static bool DisabledChromeExtensionGPO()
        {
            var PolicyExists = ComputerGroupPolicyObject.GetPolicySetting(@"HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\ExtensionInstallForcelist!1");
    
            if (PolicyExists != null)
            {
    
                ComputerGroupPolicyObject.SetPolicySetting(@"HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\ExtensionInstallForcelist!1", "null", RegistryValueKind.String);
            }
    
            return true;
        }
    

    While the policy will still have its key in regedit listed, the value of the key will be set to null.

    Setting the value to null allowed the Chrome Driver to load our local Chrome Extension file without issue at this point.