Using Chef to edit app.config and web.config during a run

I’ve run into an issue with our Chef automation where i’ve found it extremely tricky to update app/web.config files, so i’ve put together the dotnetconfig cookbook to take care of this.

Code available here: https://github.com/jerseyfoxcom/dotnetconfig

Installing

To use it, first download it from the Chef supermarket and unzip it to your cookbooks folder :-

knife supermarket download dotnetconfig --file dotnetconfig.tar.gz
tar -zxvf dotnetconfig.tar.gz

(you will also need to do this with nokogiri, on which it is dependent)

Use it within your own cookbooks by adding the following line to your cookbook’s metadata.rb.

depends         'dotnetconfig'

Within a recipe, add the following lines to include the recipe and import the DotNetConfig module.

include_recipe 'dotnetconfig'
::Chef::Recipe.send(:include, DotNetConfig)

Using the library

config_set_app_setting(document, 'fix-gateways', 'Replaced')
config_set_connection_string(document, 'Database1', 'Database=Partial;User Id=Replacement;Password=PasswordChanged;CommandTimeout=30;MinPoolSize=0')

Full usage instructions are available here.

Chef Knife-EC2 Windows Timeouts

We found we were getting some issues with the Knife-EC2 command KNIFE EC2 SERVER CREATE timing out when creating Windows notes on EC2.

The error we were getting was this :-

Fog::Compute::AWS::Error: RequestLimitExceeded

It seems to be due to the check_windows_password_available method having its “sleep” in the incorrect place. I’ve resolved this by editing the code for KNIFE-EC2 and moving the sleep to the top of the function.

def check_windows_password_available(server_id)
sleep 10
#Added this at the start of the loop as it doesn't fire if the return false is hit
response = connection.get_password_data(server_id)
if not response.body["passwordData"]
return false
end
response.body["passwordData"]
end

I recompiled the GEM and installed it, and it’s now working without issues. Have posted this as a bug but not sure anyone believes me.

Chef – Rebooting a node without breaking the run

We’ve found ourselves in a situation where we need to reboot a Windows node after joining the domain, and this was causing the Chef run to fail as the node can’t be contacted. To get around this, simply execute a shutdown asynchronously with the shutdown command.

#Set up reboot after 30 seconds
execute "chef-client" do
command 'shutdown /r /t 30'
action :run
end

This sets up a reboot in 30 seconds, the Chef run will finish (as long as this is the last thing left) and the machine will come up on the domain.

Connecting Amazon VPCs in different regions using OpenSwan VPN

I’ve recently been working with Chef automation, and have needed to create two VPC in different regions (for risk purposes). AWS doesn’t support VPC pairing across region yet, so we’ve had to find a workaround.

The simplest solution has been to create two Linux instances (Ubuntu 14.04) in each region, and configure a VPN tunnel. Here are the steps we took, and some configuration gotchas to help anyone who might have this issue along the way.

Create VPCs with non-overlapping subnets

I’ve created two, one with the 172.31.0.0/16 range and the other with 172.32.0.0/16.

 

Create Linux instances in each VPC to serve as our VPN tunnel.

I’ve called mine OpenSwanA and OpenSwanB for the purpose of this tutorial.

Give them BOTH an elastic public IP.

OpenSwanA (152.211.162.245)

OpenSwanB (135.156.170.225)

 

Setup AWS instances (disable source destination check)

Very important! Go to each OpenSWAN instance and go to Actions -> Change Source/Dest. Check and set to DISABLE on both instances, or they won’t be able to see anything other than each other.

 

Configure route tables to send traffic for “VPC2” to the Linux VPN instance 

VPC1

172.31.0.0/16 -> local

0.0.0.0/0 -> Internet Gateway (igw_xxxx)

172.32.0.0/16 -> remote (select your OpenSwanB AWS Linux, start typing its Id)

 

VPC2

172.32.0.0/16 -> local

0.0.0.0/0 -> Internet Gateway (igw_xxxx)

172.31.0.0/16 -> remote (select your OpenSwanA AWS Linux, start typing its Id)

 

Now, any traffic designated for the 172.32.x for any instance in VPC1 will go out to the Linux instance, and vice-versa. Now, we just need to configure the VPN tunnel for the Linux boxes and they will forward all traffic for us.

 

Configuring the OpenSwan VPN tunnel.

Start off with a base Linux instance (14.04 max supported version at time of writing).

Run the following command to install OpenSwan.

apt-get install openswan

 

Edit /etc/ipsec.conf (sudo nano /etc/ipsec.conf) add following line

include /etc/ipsec.d/*.conf

Replace the following line

#plutostderrlog=/dev/null

with

plutostderrlog=/tmp/pluto.log

 

This will cause logging to end up in /tmp/pluto.log, which is exceptionally handy for debugging issues.

 

 

Edit /etc/ipsec.secrets

Comment out any previous includes (/var/lib/openswan/ipsec.secrets.inc)

 

add following line

include /etc/ipsec.d/*.secrets

The above basically redirects all ipsec settings to look within the ipsec.d folder, in which we will put our customisations below.

Private key exists in /etc/ipsec.d/private/ip-x-x-x-xKey.pem but we won’t use it here as authby=secret not rsakey

VPC 1

Create /etc/ipsec.d/vpc1-to-vpc2.conf

conn vpc1-to-vpc2
type=tunnel
authby=secret
left=172.31.39.196
leftid=152.211.162.245
leftnexthop=172.31.39.196
leftsubnet=172.31.0.0/16
right=135.156.170.225
rightsubnet=172.32.0.0/16
pfs=yes
auto=start

Create /etc/ipsec.d/vpc1-to-vpc2.secrets

152.211.162.245 135.156.170.225: PSK "myprivatetopsecretcode"

VPC2
(Same baseline configuration as above, up to the VPC1 line)

Create /etc/ipsec.d/vpc2-to-vpc1.conf

conn vpc2-to-vpc1
type=tunnel
authby=secret
left=172.32.43.98
leftid=135.156.170.225
leftnexthop=172.32.43.98
leftsubnet=172.32.0.0/16
right=152.211.162.245
rightsubnet=172.31.0.0/16
pfs=yes
auto=start

Create /etc/ipsec.d/vpc2-to-vpc1.secrets

135.156.170.225 152.211.162.245: PSK "myprivatetopsecretcode"

 

Configure routing

The following settings will turn the Linux instance into a router, accepting all incoming packets and routing them via the OpenSwan VPN.
Edit /etc/sysctl.conf, add the following settings

net.ipv4.ip_forward = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0

Persist the settings

sysctl -p /etc/sysctl.conf

You can check a value has persisted post reboot with this command:-

cat /proc/sys/net/ipv4/ip_forward

 

Set up Firewall rules and NAT on both Linux VPCs

This must be configured on both Linux VPCs to forward to the other. Replace [VPNHostIP] with the corresponding side of the VPN.

iptables -t nat -A POSTROUTING -o eth0 ! -p esp -j SNAT --to-source [VPNHostIP]

iptables -A INPUT -p udp --dport 500 --j ACCEPT
iptables -A INPUT -p udp --dport 4500 --j ACCEPT
iptables -A INPUT -p esp -j ACCEPT

 

Restart IPSEC

sudo service ipsec restart

Checking VPN Status
The following commands can be helpful in checking or troubleshooting your VPN status:

sudo ipsec verify

(checks the status of the services required for OpenSWAN to run properly)

sudo service ipsec status

(checks the status of the OpenSWAN service and the VPN tunnels)

 

Automatically resizing Windows EC2, using Knife command -ebs-size

We’ve discovered an issue when trying to use the -ebs-size command within Chef. The instances are created, but due to a limitation with the AMIs they won’t use the full amount of disk space without a resize.

I’ve got a “Base” Windows recipe which I use across all Windows instances. I’ve applied the following Powershell code to resolve this problem for us :-

It checks to ensure that there is enough space to meet minimum requirements, then automatically extends the boot volume to the greatest value available.

powershell_script 'Extending primary volume' do
  code <<-EOH
	
	$drive = (Get-WmiObject Win32_OperatingSystem).SystemDrive.Replace(":", "")
	$minBytesToActivate = 1048576 #1MB minimum in Windows, you may wish to change this.

	$volume = Get-Volume -DriveLetter $drive
	$actualSize = $volume.Size
	$sizeMax = (Get-PartitionSupportedSize -DriveLetter $drive).SizeMax
	$diff = $sizeMax - $actualSize
	if ($diff -gt $minBytesToActivate)
	{
		$diff, $minBytesToActivate
		Resize-Partition -DriveLetter $drive -Size $sizeMax
	}
	
  EOH
end

Now, if we choose to resize our EBS to 60GB, the drive is automatically resized for us.

Generic delimited parsing algorithm

Hi all,

You can use the following extension method to parse any string that is split by a delimiter. It will take care of most issues associated with splitting strings (quotes, etc) and works very well in almost every scenario I have used it in.

Good one for the library of extension methods, definitely.

        /// <summary>

        /// This method will attepmt to parse the string into its delimited parts, taking a delimiter string as a parameter.

        /// </summary>

        /// <param name="strLine">The string to parse.</param>

        /// <param name="fieldDelimiter">The string used as a delimiter in the parse string.</param>

        /// <returns></returns>

        public static IEnumerable<string> ParseDelimited(this string strLine, string fieldDelimiter)

        {

            string separatorEscaped = Regex.Escape(fieldDelimiter);

            string regularExpression = @"^(?:""(?<item>[^""]*)""|(?<item>[^{0}]*))(?:{0}(?:""(?<item>[^""]*)""|(?<item>[^{0}]*)))*$";

 

            var regex = new Regex(string.Format(regularExpression, separatorEscaped));

 

            var split = regex

                .Match(strLine)

              .Groups["item"]

              .Captures

              .Cast<Capture>()

              .Select(c => c.Value)

              .ToArray();

 

            return split;

        }

FTP – Get directory listing

Haven’t written anything on here for a while – so I am going to start getting back on it.

Here’s some nice code to get a directory listing from FTP. You get back a list of FTPListDetail objects which can be used to pass to further implementations, such as a download / downloadasync etc. It uses some nice regex to parse the FTP response.

        ///<summary>

        /// Returns a directory listing of the remote FTP host.

        ///</summary>

        ///<returns></returns>

        public IEnumerable<FTPListDetail> GetDirectoryListing()

        {

            var result = new StringBuilder();

            var request = GetWebRequest(WebRequestMethods.Ftp.ListDirectoryDetails);

            using (var response = request.GetResponse())

            {

                using (var reader = new StreamReader(response.GetResponseStream()))

                {

                    string line = reader.ReadLine();

                    while (line != null)

                    {

                        result.Append(line);

                        result.Append(“\n”);

                        line = reader.ReadLine();

                    }

                    result.Remove(result.ToString().LastIndexOf(‘\n’), 1);

                    var results = result.ToString().Split(‘\n’);

                    string regex =

                        @”^” +               //# Start of line

                        @”(?<dir>[\-ld])” +          //# File size          

                        @”(?<permission>[\-rwx]{9})” +            //# Whitespace          \n

                        @”\s+” +            //# Whitespace          \n

                        @”(?<filecode>\d+)” +

                        @”\s+” +            //# Whitespace          \n

                        @”(?<owner>\w+)” +

                        @”\s+” +            //# Whitespace          \n

                        @”(?<group>\w+)” +

                        @”\s+” +            //# Whitespace          \n

                        @”(?<size>\d+)” +

                        @”\s+” +            //# Whitespace          \n

                        @”(?<month>\w{3})” +          //# Month (3 letters)   \n

                        @”\s+” +            //# Whitespace          \n

                        @”(?<day>\d{1,2})” +        //# Day (1 or 2 digits) \n

                        @”\s+” +            //# Whitespace          \n

                        @”(?<timeyear>[\d:]{4,5})” +     //# Time or year        \n

                        @”\s+” +            //# Whitespace          \n

                        @”(?<filename>(.*))” +            //# Filename            \n

                        @”$”;                //# End of line

                    foreach (var parsed in results)

                    {

                        var split = new Regex(regex)

                            .Match(parsed);

                        var dir = split.Groups[“dir”].ToString();

                        var permission = split.Groups[“permission”].ToString();

                        var filecode = split.Groups[“filecode”].ToString();

                        var owner = split.Groups[“owner”].ToString();

                        var group = split.Groups[“group”].ToString();

                        var size = split.Groups[“size”].ToString();

                        var month = split.Groups[“month”].ToString();

                        var timeYear = split.Groups[“timeyear”].ToString();

                        var day = split.Groups[“day”].ToString();

                        var filename = split.Groups[“filename”].ToString();

                        yield return new FTPListDetail()

                        {

                            Dir = dir,

                            Filecode = filecode,

                            Group = group,

                            FullPath = CurrentRemoteDirectory + “/” + filename,

                            Name = filename,

                            Owner = owner,

                            Permission = permission,

                            Size = size.ToInt() ?? 0,

                            Month = month,

                            Day = day,

                            YearTime = timeYear

                        };

                    };

                }

            }

        }

        ///<summary>

        /// Get the request using a specific URI

        ///</summary>

        ///<param name=”method”></param>

        ///<param name=”uri”></param>

        ///<returns></returns>

        private FtpWebRequest GetWebRequest(string method, string uri)

        {

            Uri serverUri = new Uri(uri);

            if (serverUri.Scheme != Uri.UriSchemeFtp)

            {

                return null;

            }

            var reqFTP = (FtpWebRequest)FtpWebRequest.Create(serverUri);

            reqFTP.Method = method;

            reqFTP.UseBinary = true;

            reqFTP.Credentials = new NetworkCredential(Connection.Username, Connection.Password);

            reqFTP.Proxy = null;

            reqFTP.KeepAlive = false;

            reqFTP.UsePassive = false;

            return reqFTP;

        }

    public class FTPListDetail

    {

        public bool IsDirectory

        {

            get

            {

                return !string.IsNullOrWhiteSpace(Dir) && Dir.EqualsIgnoreCase(“D”);

            }

        }

        internal string Dir { get; set; }

        public string Permission { get; set; }

        public string Filecode { get; set; }

        public string Owner { get; set; }

        public string Group { get; set; }

        public int Size { get; set; }

        public string Name { get; set; }

        public string FullPath { get; set; }

        internal string Month { get; set; }

        internal string Day { get; set; }

        internal string YearTime { get; set; }

        public DateTime Date

        {

            get

            {

                var month = DateTime.ParseExact(Month, “MMM”, CultureInfo.CurrentCulture).Month;

                if (!YearTime.Contains(“:”))

                {

                    return new DateTime(YearTime.ToInt() ?? 0, month, Day.ToInt() ?? 0);

                }

                else

                {

                    var dateTime = YearTime.Split(new string[] { “:” }, StringSplitOptions.RemoveEmptyEntries);

                    if (dateTime.Count() == 2)

                    {

                        int hour = dateTime[0].ToInt() ?? 0;

                        int minute = dateTime[1].ToInt() ?? 0;

                        return new DateTime(DateTime.Now.Year, month, Day.ToInt() ?? 0, hour, minute, 0);

                    }

                    return new DateTime(DateTime.Now.Year, Month.ToInt() ?? 0, Day.ToInt() ?? 0);

                }

            }

        }

        public override string ToString()

        {

            return string.Format(“{0} {1} {2} {3} {4} {5} {6} {7}”,

                Dir,

                Permission,

                Filecode,

                Owner,

                Group,

                Size,

                Date.ToShortDateString(),

                Name

                );

        }

    }

Refresh rate switching app

Hi all,

I was looking around for an application which sits in the system tray and watches for key combinations to automatically change your monitor refresh rate – I assumed somebody would have written something to do this, but couldn’t find a single app, so I wrote something.

I have uploaded it here :-

http://downloads.seesharpdot.net/files/RefreshSwitcher.zip

It creates an icon in the system tray – if you right click you will get a mapping of your current resolution to the different refresh rates supported .. if you change resolution it will update itself to the new available refresh rates. Just add it to your startup section or HKLM/Software/Microsoft/CurrentVersion/Run/ in the  registry to get it to auto-launch at startup.

To use it – simple note the key combinations .. they will be in the range of :-

Control+Alt+ F1 to Control+Alt+F10

If I get enough coffees bought for me – I might consider adding some sort of configuration section to it with user configurable key mappings and multi resolution support.

PS. It uses the Low Level Keyboard Hook which is mentioned in the following post :-

http://www.seesharpdot.net/?p=96

Copy paste from Excel into a DataTable

Ever wanted to copy and paste from Excel into a System.Data.DataTable for displaying in a DataGrid? Here’s some quick and dirty code to do just that :-

private void PasteFromExcel()

        {
            DataTable tbl = new DataTable();
            tbl.TableName = "ImportedTable";
            List<string> data = new List<string>(ClipboardData.Split('\n'));
            bool firstRow = true;
 
            if (data.Count > 0 && string.IsNullOrWhiteSpace(data[data.Count - 1]))
            {
                data.RemoveAt(data.Count - 1);
            }
 
            foreach (string iterationRow in data)
            {
                string row = iterationRow;
                if (row.EndsWith("\r"))
                {
                    row = row.Substring(0, row.Length - "\r".Length);
                }
 
                string[] rowData = row.Split(new char[] { '\r', '\x09' });
                DataRow newRow = tbl.NewRow();
                if (firstRow)
                {
                    int colNumber = 0;
                    foreach (string value in rowData)
                    {
                        if (string.IsNullOrWhiteSpace(value))
                        {
                            tbl.Columns.Add(string.Format("[BLANK{0}]", colNumber));
                        }
                        else if (!tbl.Columns.Contains(value))
                        {
                            tbl.Columns.Add(value);
                        }
                        else
                        {
                            tbl.Columns.Add(string.Format("Column {0}", colNumber));
                        }
                        colNumber++;
                    }
                    firstRow = false;
                }
                else
                {
                    for (int i = 0; i < rowData.Length; i++)
                    {
                        if (i >= tbl.Columns.Count) break;
                        newRow[i] = rowData[i];
                    }
                    tbl.Rows.Add(newRow);
                }
            }
 
            this.WorkingTableElement.WorkingTable = tbl;
 
            tableImportGrid.DataSource = null;
            tableImportGrid.RefreshDataSource();
            
            tableImportGrid.DataSource = tbl;
            tableImportGrid.RefreshDataSource();
            tableImportGrid.Refresh();
        }

Custom WebBrowser Control with Zooming and CSS Injection

I thought I would post this custom browser I wrote for a project I am working on. It allows you to access the zoom function of the Internet Explorer browser (AxWebBrowser) by calling a Zoom method (passing in a whole number, expressed as percentage). I also added an InjectCSS() method which allows you to insert some extra CSS into the page (after it is loaded). I used this in a project recently where I didn’t want to mess with my generated page CSS or HTML, but simple wanted to add a couple of styles for display only purposes.

    public partial class CustomBrowser : WebBrowser
    {
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern short GetAsyncKeyState(int keyCode);
        public bool IsKeyDown(Keys key)
        {
            return (GetAsyncKeyState((int)key) & 0x8000) != 0;
        }
 
        #region enums
        public enum OLECMDID
        {
            // ...
            OLECMDID_OPTICAL_ZOOM = 63,
            OLECMDID_OPTICAL_GETZOOMRANGE = 64,
            // ...
        }
 
        public enum OLECMDEXECOPT
        {
            // ...
            OLECMDEXECOPT_DONTPROMPTUSER,
            // ...
        }
 
        public enum OLECMDF
        {
            // ...
            OLECMDF_SUPPORTED = 1
        }
        #endregion
 
        #region IWebBrowser2
        [ComImport, /*SuppressUnmanagedCodeSecurity,*/
         TypeLibType(TypeLibTypeFlags.FOleAutomation |
                     TypeLibTypeFlags.FDual |
                     TypeLibTypeFlags.FHidden),
         Guid("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E")]
        public interface IWebBrowser2
        {
            [DispId(100)]
            void GoBack();
            [DispId(0x65)]
            void GoForward();
            [DispId(0x66)]
            void GoHome();
            [DispId(0x67)]
            void GoSearch();
            [DispId(0x68)]
            void Navigate([In] string Url,
                          [In] ref object flags,
                          [In] ref object targetFrameName,
                          [In] ref object postData,
                          [In] ref object headers);
            [DispId(-550)]
            void Refresh();
            [DispId(0x69)]
            void Refresh2([In] ref object level);
            [DispId(0x6a)]
            void Stop();
            [DispId(200)]
            object Application
            {
                [return:
                 MarshalAs(UnmanagedType.IDispatch)]
                get;
            }
            [DispId(0xc9)]
            object Parent
            {
                [return:
                 MarshalAs(UnmanagedType.IDispatch)]
                get;
            }
            [DispId(0xca)]
            object Container
            {
                [return:
                 MarshalAs(UnmanagedType.IDispatch)]
                get;
            }
            [DispId(0xcb)]
            object Document
            {
                [return:
                 MarshalAs(UnmanagedType.IDispatch)]
                get;
            }
            [DispId(0xcc)]
            bool TopLevelContainer { get; }
            [DispId(0xcd)]
            string Type { get; }
            [DispId(0xce)]
            int Left { get; set; }
            [DispId(0xcf)]
            int Top { get; set; }
            [DispId(0xd0)]
            int Width { get; set; }
            [DispId(0xd1)]
            int Height { get; set; }
            [DispId(210)]
            string LocationName { get; }
            [DispId(0xd3)]
            string LocationURL { get; }
            [DispId(0xd4)]
            bool Busy { get; }
            [DispId(300)]
            void Quit();
            [DispId(0x12d)]
            void ClientToWindow(out int pcx, out int pcy);
            [DispId(0x12e)]
            void PutProperty([In] string property,
                             [In] object vtValue);
            [DispId(0x12f)]
            object GetProperty([In] string property);
            [DispId(0)]
            string Name { get; }
            [DispId(-515)]
            int HWND { get; }
            [DispId(400)]
            string FullName { get; }
            [DispId(0x191)]
            string Path { get; }
            [DispId(0x192)]
            bool Visible { get; set; }
            [DispId(0x193)]
            bool StatusBar { get; set; }
            [DispId(0x194)]
            string StatusText { get; set; }
            [DispId(0x195)]
            int ToolBar { get; set; }
            [DispId(0x196)]
            bool MenuBar { get; set; }
            [DispId(0x197)]
            bool FullScreen { get; set; }
            [DispId(500)]
            void Navigate2([In] ref object URL,
                           [In] ref object flags,
                           [In] ref object targetFrameName,
                           [In] ref object postData,
                           [In] ref object headers);
            [DispId(0x1f5)]
            OLECMDF QueryStatusWB([In] OLECMDID cmdID);
            [DispId(0x1f6)]
            void ExecWB([In] OLECMDID cmdID,
                        [In] OLECMDEXECOPT cmdexecopt,
                        ref object pvaIn, IntPtr pvaOut);
            [DispId(0x1f7)]
            void ShowBrowserBar([In] ref object pvaClsid,
                                [In] ref object pvarShow,
                                [In] ref object pvarSize);
            [DispId(-525)]
            WebBrowserReadyState ReadyState { get; }
            [DispId(550)]
            bool Offline { get; set; }
            [DispId(0x227)]
            bool Silent { get; set; }
            [DispId(0x228)]
            bool RegisterAsBrowser { get; set; }
            [DispId(0x229)]
            bool RegisterAsDropTarget { get; set; }
            [DispId(0x22a)]
            bool TheaterMode { get; set; }
            [DispId(0x22b)]
            bool AddressBar { get; set; }
            [DispId(0x22c)]
            bool Resizable { get; set; }
        }
        #endregion
 
        private IWebBrowser2 axIWebBrowser2;
 
        public CustomBrowser()
        {
        }
 
        ~CustomBrowser()
        {
        }
 
        protected override void AttachInterfaces(
            object nativeActiveXObject)
        {
            base.AttachInterfaces(nativeActiveXObject);
            this.axIWebBrowser2 = (IWebBrowser2)nativeActiveXObject;
        }
 
        protected override void DetachInterfaces()
        {
            base.DetachInterfaces();
            this.axIWebBrowser2 = null;
        }
 
        protected override void OnDocumentCompleted(WebBrowserDocumentCompletedEventArgs e)
        {
            base.OnDocumentCompleted(e);
        }
 
        public void InjectCSS()
        {
            try
            {
                mshtml.HTMLDocument test = (mshtml.HTMLDocument)this.Document.DomDocument;
                //inject CSS
                if (test.styleSheets.length < 31)
                { // createStyleSheet throws "Invalid Argument if >31 stylesheets on page
 
                    mshtml.IHTMLStyleSheet css = (mshtml.IHTMLStyleSheet)test.createStyleSheet("", 0);
                    css.cssText = "//Insert Custom CSS here!";
                    // CSS should now affect page
                }
                else
                {
                    System.Console.WriteLine("Could not inject CSS due to styleSheets.length greater than 31");
                    return;
                }
            }
            catch { }
        }
 
        int currentZoom = 100;
        public void Zoom(int factor)
        {
            object pvaIn = factor;
            try
            {
                this.axIWebBrowser2.ExecWB(OLECMDID.OLECMDID_OPTICAL_ZOOM,
                   OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER,
                   ref pvaIn, 
                   IntPtr.Zero);
            }
            catch (Exception)
            {
                throw;
            }
        }
    }