将 Chromedriver 与开发的扩展和强制安装组策略一起使用
Using Chromedriver with Developed Extension and Force Install Group Policy
我正在开发具有以下环境设置的 Web 应用程序:
- 该网站设计用于 Chrome
- 该网站依赖 Chrome 开发的扩展 in-house 并托管在 Chrome Play 商店
- 为了更轻松地获取扩展,我们的系统团队设置了 ExtensionInstallForcelist 组策略(更多信息请点击此处 http://dev.chromium.org/administrators/policy-list-3#ExtensionInstallForcelist)。这使得 Chrome 可以获取扩展并根据需要更新它,而无需手动用户交互
- 这都是在镜像设备上设置的(所以单独的设备,有单独的浏览器和组策略,每个设备都有自己的扩展)
我遇到的问题是尝试将 Chromedriver 实例与此组策略一起使用时。
启用该策略后,我在 运行 我们的测试时收到 "Failed to load extension from: . (extension ID ) is blocked by the administrator." 错误消息。
这似乎是因为我们通过 Chrome 选项(添加扩展名)将扩展名添加到我们的 Chromedriver。
虽然我可以手动关闭映像设备上的组策略来解决这个问题,但我想知道是否有任何设置可以添加到组策略中以允许在 Chromedriver?
我已经尝试了很多方法来解决这个问题,否则似乎不起作用:
- 命令行更改由组策略设置的注册表值 - 不起作用,因为虽然更改注册表值有效,但我仍然在 运行 测试
上收到错误
- PowerShell 更改组策略 - 将不起作用,因为它需要额外安装获取组策略 cmdlet
- 使用 GPMC C# 库 - 这些库在这一点上似乎已经过时了(有些只使用 .NET 2.0)。我能够设置一些代码来使用 类,但是我收到了创建 GPODomain object 的 "reference missing" 异常,即使我在我的解决方案中有引用
提前感谢大家提出有关让 Chromedriver 使用强制安装组策略的任何建议。
在网上看了很多不同的答案后,我发现这个解决方案有效。
创建一个 GroupPolicy class 来处理与之交互:
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;
}
}
}
然后通过如下调用关闭强制安装 Chrome 扩展组策略:
[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;
}
虽然该策略仍会在注册表编辑器中列出其键,但该键的值将设置为空。
将值设置为 null 允许 Chrome 驱动程序在此时加载我们的本地 Chrome 扩展文件而不会出现问题。
我正在开发具有以下环境设置的 Web 应用程序:
- 该网站设计用于 Chrome
- 该网站依赖 Chrome 开发的扩展 in-house 并托管在 Chrome Play 商店
- 为了更轻松地获取扩展,我们的系统团队设置了 ExtensionInstallForcelist 组策略(更多信息请点击此处 http://dev.chromium.org/administrators/policy-list-3#ExtensionInstallForcelist)。这使得 Chrome 可以获取扩展并根据需要更新它,而无需手动用户交互
- 这都是在镜像设备上设置的(所以单独的设备,有单独的浏览器和组策略,每个设备都有自己的扩展)
我遇到的问题是尝试将 Chromedriver 实例与此组策略一起使用时。
启用该策略后,我在 运行 我们的测试时收到 "Failed to load extension from: . (extension ID ) is blocked by the administrator." 错误消息。
这似乎是因为我们通过 Chrome 选项(添加扩展名)将扩展名添加到我们的 Chromedriver。
虽然我可以手动关闭映像设备上的组策略来解决这个问题,但我想知道是否有任何设置可以添加到组策略中以允许在 Chromedriver?
我已经尝试了很多方法来解决这个问题,否则似乎不起作用:
- 命令行更改由组策略设置的注册表值 - 不起作用,因为虽然更改注册表值有效,但我仍然在 运行 测试 上收到错误
- PowerShell 更改组策略 - 将不起作用,因为它需要额外安装获取组策略 cmdlet
- 使用 GPMC C# 库 - 这些库在这一点上似乎已经过时了(有些只使用 .NET 2.0)。我能够设置一些代码来使用 类,但是我收到了创建 GPODomain object 的 "reference missing" 异常,即使我在我的解决方案中有引用
提前感谢大家提出有关让 Chromedriver 使用强制安装组策略的任何建议。
在网上看了很多不同的答案后,我发现这个解决方案有效。
创建一个 GroupPolicy class 来处理与之交互:
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;
}
}
}
然后通过如下调用关闭强制安装 Chrome 扩展组策略:
[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;
}
虽然该策略仍会在注册表编辑器中列出其键,但该键的值将设置为空。
将值设置为 null 允许 Chrome 驱动程序在此时加载我们的本地 Chrome 扩展文件而不会出现问题。