Thursday, December 1, 2016

Effective Backup Strategy

Losing a hard drive, can ruin your life.  Well, maybe not that dramatic, but the loss of a drive can create sleepless nights struggling to recover lost files and get back to where you the instantaneous moment before the tragedy.  Like most mechanical devices, invariably there are signs things are about to go sideways.  One of my favorite scenes in Bruce Almighty is when Jim Carey is following the flat bed truck, all pissed off trying to get around it wondering why God is not giving him a "sign."  Warning, danger ahead, do not pass; all the signs ignored, he passes the truck and winds up in the bay.  Supposedly that scene was shot in San Diego.  Bad sectors, unusual noises, degrading performance are all signs that should be revealed before it gets to that point by adhering to a monthly schedule.

Structure!  On the first of every month, do a little preventative maintenance on your workstation or laptop.  I lost a hard drive and discovered my hap-hazard cloning was not actually working because of large-disk-drive bug in the utility I was using.  I never actually tested the cloned drives; duh.   Then the unthinkable happened, I lost a drive and thought my world was coming to an end. I finally had to accept I could not recover the drive and it was 100% my fault.

I called my co-lo provider and asked them for advice, they are linux guys and recommended Clonezilla.  Clonezilla  is an open-source, linux based utility that does several types of cloning, by device, file system, network.  Using Clonezilla and incremental backups to "the cloud."  I will never lose anything ever again.  Windows backup/restore is a royal pain, its too complicated, it is slow and requires a bootable device and is it really working, there is a better way.

An effective backup strategy includes taking periodic cold-backup of the drive, a clone, with daily incremental backups to the cloud.  I use Cox Business for my incremental backups, but there any many choices out there.

For an effective backup strategy you need at least 2 drives of the same size.  I primarily use Seagate 2TB Barracuda drives, but I also have a 2TB Western Digital in the mix of four drives.  You can use varying drive sizes, but that adds a level of complexity with a possibility of error and the goal here is to make this process simple, quick and reliable.   This is for windows, but it works with any operating system.  Windows licensing uses several devices to determine if you have a new machine or simply made a change to an existing setup.  It will not require re-licensing a windows installation cloning a drive.

Before beginning label each hardrive with its serial number, you will need this to ensure you source and target drives are correct.  You will also need a bootable Clonezilla CD.

On the first of every month do the following:


  1. Run chkdsk from a command windows on all partitions.  This will detect any file system problems.  There is no point cloning problems.  If anything is detected it must be repaired here if possible before cloning, if not possible still continue.  
  2. Run cleandisk or bleachbit to cleanup your temp directories and updates. (optional)
  3. Run harddisk vendor utility to scan the drive.
  4. Shutdown the PC
  5. Connect the target drive from the oldest drive in your rotation to your pc.  (for a laptop use a USB hardrive adapter)
  6. Boot into clonezilla.
  7. Select device to device clone.
  8. /sda is the source, /sdb is the target map /sda to the serial number of the drive you are currently using.  If you make a mistake you will wipe out your last thirty days.
  9. Skip all the file system checks, we all ready did that in the first three steps.
  10. Include sector 0 or the boot sector in the clone.
  11. It takes about an hour to do 2tb drive.  Device-to-Device is create an exact sector by sector clone from the source to the target drives.
  12. When completed, swap the drives.  This is a critical step, it shows that your clones actually work.  On a laptop best you can do is confirm the drive with the usb adapter, or open it up and swap the drives out.  








Thursday, November 3, 2016

Vinegar in the Wash

How can vinegar be a fabric softener?  It is basic high-school chemistry.  To review, what is the pH scale and what are acids and bases?  From the Chemistry Department at Elmhurst University   The pH scale measures how acidic or basic a substance is. The pH scale ranges from 0 to 14. A pH of 7 is neutral. A pH less than 7 is acidic.  Acids and bases neutralize each other.
What is soap?  Soap and bleach are bases, fabric softener and vinegar are acids. Rough fabric is from the residual build of soap, to neutralize this buildup you need an acid, both softeners and vinegar alter the pH of the water to act as an acid and remove the residual soap; either works great. pH of Downy is a 5, vinegar is a 4, neutral is 7. How you dispense is largely dependent on the pH of your tap water. On my front loader, a Kenmore HE3, essentially a Whirlpool Duet, after the wash there is a clean-water rinse, then the bleach cycle, then a final rinse. In the last rinse the fabric softener is dispensed. You can verify this yourself, during a cycle there is servo motor that moves a water director to shoot into the various dispenser compartments, pull the dispenser drawer during a fill cycle and see where the water is shooting from. According to Whirlpool, they wash-rinse-bleach-rinse because bleach is more effective in cold water and if it is not mixed with detergent. To mitigate the concern of acid remaining, I use the bleach cycle instead of the fabric softener dispenser. Using the bleach cycle to dispense the vinegar or fabric softener, you get two benefits; the washer -and- the fabric finish with a clean water rinse removing any latent soap/softener residue. For whites with bleach I put the vinegar in the fabric softener dispenser, the machine is designed for it anyway.
Know the pH of your tap water to most effectively clean your clothes, pick up some litmus paper. In San Diego the city adjusts the pH to slightly over 7, as do most municipalities, they do this is to prevent corrosion in metal pipes including copper, think Flint MI. If your water is acidic (below 7), use more soap, or an additive like Borax, to raise the pH of the water to clean better, then use less softener; flip if your water is an alkaline.
Bottom line: both vinegar and fabric softener are acids.

Sunday, October 16, 2016

Workhorse W-Series Cooling System Service

Of all the scheduled maintenance, changing the radiator coolant is one of those systems easy to neglect.  The thing is, if the cooling system is properly maintained, changing the coolant is a snap.

How to test your coolant:

To test your coolant, you will need a voltmeter, litmus paper and a refractometer.   These items are available on Amazon for under $20.


Use a refractometer to test your water/coolant ratio.  This device, available on Amazon for under $60, has rave reviews, it is affordable and easy to use.



The concern here is freeze/boil protection with the correct water to coolant ratio and the inhibitors in the coolant.  The water to coolant ratio is a bit like Goldilocks.  Water is excellent at heat dissipation, too much coolant and you cannot bleed off enough heat from the engine, too little coolant and you do not have sufficient freeze/boil -and- insufficient inhibitors.  The goal is 50/50 which is -34 Fahrenheit.  The glycol/water ratio remains constant overtime, this test does not indicate the health of the coolant.  With one drop of coolant, the alcohol bends the light rays differently from the water and you get clear reading for freeze protection.


For background on Dex-Cool check out this guy's post:  All About Dex-Cool   A veteran GM engineer told me besides the rust inhibitors, Dex-Cool also acts to prevent electrolysis occurring generating current as the coolant flows over the varying metals in the cooling system; Iron, AL, brass and the coolant acts like a battery.  I had to see this one for myself.  With your volt-meter set to DC,  put one prob in the coolant the other to ground with the motor running and the thermostat open.  Ideally you should have < .3 volts, as the voltage increases, electrolysis is occurring and eating away the soft metals in your cooling system namely AL heads, head gaskets and the heater core.  Be mindful of the coolant temp before you remove the radiator cap.   The second test is the pH of the coolant.  In time as the inhibitors degrade the pH of the coolant becomes acidic.  Think Flint, MI as the water in the cast-iron pipes became acidic instead of an alakaline, the pipes were destroyed, acids eat metal.  Your coolant should have a pH of 8 or 9.  It is the components, not just freeze and boil over protection which remains constant over time, we want to test and ensure the coolant is doing its job protecting the engine, radiator, seals, head-gasket, water-pump and heater core from electrolysis, corrosion and ultimately failure.  This service is cheap protection.

Regardless of the above tests, if  its been five years or 150k miles, it is time to service your cooling system.  Along with a coolant flush, this is an excellent time to replace the upper and lower radiator hoses, thermostat, serpentine belt, tensioner and idler-pulley as well.  Since I am doing this myself, I use ACDelco parts and reward myself with buying any tool I may need, like a refractometer.   The good news is Amazon sells ACDelco parts at a steep discount.  This is my rationale, a failure of any of these parts and not only is my journey interrupted, but I am now paying labor charges and full-retail for no-name parts, and arranging a tow; easily avoidable. Save your money, a trip to the shop and enjoy the satisfaction of doing your own work. 

The cooling system on a W-Series 8.1 liter holds 23.5 quarts, throw in the heater core and lets round up to 6 gallons, Winnebago owners with Motor-Aid need to add more capacity for the heat exchanger in the water heater.  The 8.1 holds over two gallons of coolant in the block.  The shops I have asked, tell me whatever comes out of the radiator is what gets replaced in a simple coolant change, that is just not enough coolant.   With nearly 1/2 the coolant in the block, you need to drain block as well as the radiator it to do this job.

Tools you will need:
  1. Both a 5/16 or 8mm 1/4 socket with ratchet and nut driver. 
  2. 3/8 Ratchet with 15mm socket
  3. 1/2" or 13mm ratcheting wrench
  4. 1/2 ratchet and extension
  5. Flat screwdriver
  6. Channel Locks (optional)
You will also need two specialty tools:
  1. 1/2" 17mm hex socket (available on Amazon) to remove the engine block drain.
  2. Hose clamp removal pliers, Sears may stock this item.

Parts list for 2005 W-Series:
  1. Lower Hose ACDelco 24397L (Amazon)
  2. Upper Hose ACDelco 26163X (Amazon)
  3. Heater Bypass ACDelco 1255-8968 (Amazon)
  4. 195 Degree Thermostat ACDelco 131-121 (Amazon)
  5. Serpentine Belt ACDelco 6K1080 (Amazon)
  6. Idler Pulley ACDelco 1258077 (Amazon)
  7. Belt Tensioner ACDelco 39702  (eBay)
  8. 3 Gallons DexCool ACDelco 10-101  (Jegs best price)
  9. 8-10 Gallons distilled water. (Wolly World)
  10. 2 1-1/4 - 2-1/4" Hose Clamps (parts house)
  11. 2 1/2 - 1-1/4" Hose Clamps (parts house)
  12. "Yellow" teflon tape (hardware store)
Down to business
All work is done from below, no mess inside.   I wear a beanie to insulate my noggin from all the sharp points and edges.   I have Gladys Kravitz for a neighbor, Brad, so I take extra care to collect all coolant, which is a challenge on this chassis and the right thing to do anyway.  Start by removing the serpentine belt, tensioner and idler pulley from the passenger side.  The belt pattern is fairly simple, so dont worry about it right now.  The tensioner has a 3/8 socket opening, use it to take the tension off the belt and remove from the idler, water-pump or alternator.  Keep the belt as a spare.  This belt with the oversized alternator and power steering takes a real beating.  This tensioner is not very strong, you can do it with your hand or use a 3/8 ratchet.  Now remove the tensioner and the idler pulley, both have a 15mm bolt.  Removing the belt and idler pulley makes changing the thermostat housing  easier opening up access to the three 13mm bolts holding the thermostat housing.  

You are now ready to drop the coolant.  Do not discount the need to remove coolant from the block.  Almost 1/2 of the system's coolant is in the engine.  Start by removing the radiator cap to allow air to come in.  If you have the factor clamps, use the specialty pliers to remove the lower radiator hose from the water pump.  Have buckets ready to catch coolant from the water pump and then bend the lower hose into another bucket.  I use a 2 and 5 gal bucket.  It is a good idea to have a tarp and towels down to catch and collect the inevitable coolant that misses your buckets.  There is no peatcock on the radiator, its the lower hose and drain plug on the block.  Next remove the 17mm hex drain plug from the left side of the motor.  You are going to repeat this two-step drain process two more times, experiment...

Coolant will rush out of this drain.  There is no great way to catch it all because of the frame.  

Now its time to remove the thermostat housing.  Unless you are a masochist, this part sucks.  You may find yourself exploring some dark thoughts directed to whomever decided these hose clamps were a good idea.  I believe it normal and healthy to let a few a expletives out during this part, it gets darker getting the heater bypass hose out.   Leave the upper hose attached to the housing, it is one less (sucky-factory) clamp to remove using the tool bloodying your knuckles.  Remove the upper clamp at the radiator, remove the three thermostat housing bolts, a 13mm or 1/2" ratcheting wrench makes this part easier.  Remove the upper hose from the housing using channel locks and keep the factory netting, this netting allows the upper hose to slide over the fan shroud instead of binding.

 With the three screws  out of the housing, using your hand break the housing free and pull the housing and hose out past the fan.  Remove the old thermostat and make sure you get the old rubber seal.   Now comes the hard part, removing the heater bypass hose.  You may be tempted at this point to skip-it, but you are this far and why risk it?  Think of the stress that little hose is subject to; extreme heat from the coolant and engine all those times you had to race to the top of the hill pulling your toad.  Your here, there is no hurry, have a beer and keep going.  I needed a ball-gag at this point to suppress the f-bombs I was dropping getting this little hose out.  Clean up the housing with 2000 wet dry, clean the gunk off the hosing and the water pump for the lower hose with a wire brush or some 600 wet dry.  The hard part is over.  You have just had an abject lesson on why a shop might be tempted not to change the thermostat or bypass hose, yet charge you anyway.

Its time to put it back together.  Replace the thermostat, ensure the rubber seal it came with is properly attached to the thermostat, housing is printed side up.  Drop it in.  Replace the hosing ensuring it easily goes against the block, this will confirm you have the thermostat seated properly.  Do not force anything.  Install the heater bypass hose using your new hose clamps.

Reuse the netting from the upper hose on the new hose,  Once the thermostat hosing is in and tight, install the upper hose, now install the new idler pulley and tensioner.   Remove, empty and clean the coolant overflow tank and re-install.  Time to install the new belt referring to the serpentine pattern below.  Start from the driver side.


Duct tape or an assistant will make this part easier.  Start by placing the belt over the AC pulley, then over the power steering pulley.  Go up and around the waterpump, the ribs will face out over the water pump.  Go around the crankshaft pulley.  To avoid another round of expletives, tape the belt in place over the AC and PS.  Time for you to switch to the passenger side.  Put your 3/8 ratchet into the tensioner, play with it a little bit to get a feel about how it works.  Gingerly place the belt around the alternator.  Your goal is to avoid a trip back to the driver side.  In one move, retract the tensioner, and pull the belt below the idler.  If you are not in all the groves, thats ok.  Take a little break and come back to it.  Remove any tape.  Adjust any pulleys where the belt is not fully in-place by removing tension with the ratchet on the tensioner.

Congratulations, you are almost ready to test, the rest of the job is easy.

Install the lower, replace the drain plug in the block hand-tight and no teflon tape yet.  Go back over everything, checking the five bolts and four hose clamps.  

The next step is to flush the system using distilled water repeating the drain process above.  It is all about pH and impurities; no tap water.  If you are keeping up with coolant changes, power-flushes and additive chemicals are not necessary.  With the motor off, you can get a couple gallons in the radiator.  But wait, you still need to confirm the serpentine belt without starting the motor.  Click the key a couple of times without starting the motor to move the belt and re-inspect.  If the belt gets thrown, oh well its not going far, try again.  The belt should now be fully seated in all pulleys and look good on the idler pulley and waterpump.   If you are satisfied, start the motor and be ready with 2-3 gallons distilled.  Keep adding distilled water.  Turn on heater inside, to speed things up, place cardboard over the radiator.  Using a scantool if you have one, bring the temp to 195 to open the thermostat.  The radiator should start "burping" and take another gallon distilled water.  The goal is to dilute the remaining illusive gallon of coolant still in the system with distilled water.  I do this flush twice, never using tap water.  You want to dilute the remaining old coolant as best you can.  Collect and dispose the discharged coolant as per your local requirements.    Measure what you get out of each flush.  You should have gotten 4.5-5 gallons from the radiator and block.  The system takes 6 gallons.  Assuming you are staring with a 50/50 concentration and drain 5 gallons out, your first flush you are at 8% concentration, second flush drops down to 4%.  Point is, there is still some old coolant in the system.  Dex-Cool is not to go above 70% concentration, target is 50%.  Assume there is still 2 quarts of old coolant in the system after two 5 gallon flushes.  For your last fill, use the teflon tape on the drain-plug and tighten, start by adding all 2.5 gallons or adjusted amount of Dex-Cool to the system and top off with two gallons or more of distilled water.  Adjust the amount of Dex-Cool based on what you removed in your flushes and dilution levers.  For 50/50 you would add 12 quarts.  Using all three gallons, with two 5 gallon flushes leaves a 58% concentration.  

Re-test your voltage and pH, you should at our around .3 volts and a pH of 8.


The final test, do you have the water/coolant ratio correct?  Too much bad, too little bad; 50/50 -34F freeze protection; just right.  I am about -32F, I could put straight dex-cool in the reserve bottle, but that might be a bit obsessive.



Pat yourself on the back, put on some bandaids, document your work for your records and know the work was done and done right.

SUMMARY:

Regardless of who does the work,  Trust, but VERIFY.  If a shop does the work, demand the shop prove to you they did the work, check your own work.  Demand, in a nice way, they show you the pH and the refractometer results.  You could be saving your motor.  


Correcting too little coolant.

On my Sierra Duramax I had freeze protection of -25F, target for 50/50 is -34F.  How to correct this:   The capacity for a Duramax pickup up with auto trans is 25.4 quarts, round up to 26 quarts, 50/50 is 13 quarts coolant.  Using the chart below, capacity 13quarts x2, I determined I had was missing two quarts of coolant and the ratio was roughly 46%, probably not a big deal.  To jump the missing one quart, I removed one gallon of coolant from the truck, then added a three quarts coolant and topped off with distilled.









Tuesday, April 5, 2016

Gear Oil

I enjoy doing mechanical things working with my hands.  Ten years ago, taking my new duramax in for service and finding the dealer had elected to forgo lubricating the chassis, a decision they say was made by a rogue mechanic, I decided to take the maintenance of my vehicles on myself.  This way, I would become more familiar with my vehicles and right or wrong I became responsible.  Not to mention saving money.

I simply do not drive that much, most of my maintenance comes down to annual vs by mile.   Everything gets an annual service from my two vehicles, motorhome and generators.  I have stayed ontop of all schedules maintenance including transmission service,  radiator flushes, replacing radiator hoses, thermostats, idler/tensioner pulley and serpentine belts at five years.  It is cheap insurance to avoid both costly repairs and breakdowns.  I can do this work for 25%, with AC/Delco and Mopar parts, verses taking it to the dealer.  Moreover, I have the pride in doing it myself.

The subject of this post is differentials.  It is easy to ignore the differential in your vehicle.  Front wheel drive with a transaxle is handled by scheduled transmission fluid changes, but on a rear wheel drive or 4x you have one and two traditional differentials.  For me, I have a 3/4 GM Sierra, a TJ Rubicon and a Workhorse w-20.  GM built the differential, but the Jeep and W-20 have Dana, specifically Dana 44 and Dana Spicer S110.

There are three types of differentials, open, limited slip, and gear based posi-traction.  A differentials job is to compensate of the delta in wheel speed when you go around a corner.  Without this, the inside tire would have to break traction.  From "My Cousin Vinnie" an open differential is when you get the stuck in the mud and one tire spins and spins and the other tire does 'nothen.  Limited slip attempts to provide traction to both wheels when one side loses traction.  Performance cars do this, it helps distribute power output to both wheels.  Off-road vehicles do this to provide better traction.  Locking differentials, unlike limited slip, use a collar that make the axle solid, this makes going around a corner nearly impossible with out something giving like snow, rain or dirt or the tread of your tire.  This is why it hard on your 4x to drive in four wheel drive on dry pavement.  Mechanical devices suffer from non-use as much as abuse.  It is important to make it habit to shift your 4x in and out of four wheel drive and high and low range.  Rainy days are a good time to run in four wheel drive.

Fluid.  Unlike motoroil the type of fluid is not backward compatible.  Manufacturers will recommend a weight, 75w90 synthetic GL/5.  Oil comes in thickness, the lower the number the thinner the oil.    GL/5 is not an improvement of GL/4.  In fact using GL/5 in a GL/4 application can destroy the "soft or yellow" metals like brass found in older vw differentials and manual transmissions.  Manual transmissions contain soft metal "synchros" these floating gears help to align the gear to shift without grinding, it makes shifting smooth and requires less "double-clutching" techniques.  

Clutch based differentials require a limited slip agent.  Regardless of what Jeep says, I finally called Dana and gave them the build numbers from the diffs on my Jeep.  Because of the air-lockers these differentials are open and have no limited slip, therefore, the additive is a waste.  On a light duty vehicle, the additive on an open differential will not make any difference.  Keep in mind this is not the only error Chrysler has in the owners manual.  They list GL/5 for a manual transmission, the sulfer in GL/5 which helps the oil stick to the gear will eat the syncrhos.  Two strikes...

The Dana Spicer 110 is a different animal, this is heavy duty axle rated at 15,500#.  This is an open differential and used in a variety of over the road (OTR) applications.  What Dana recommends is GL/5 75W-90 synthetic, no additive.  In a heavy duty environment the LS additive can oxidize and has a shorter life with diminished efficacy cushioning the gears.  Only problem is trying to find it without buying a 50 gallon drum.  I called Mobile1, Lucas, Royal Purple, they all arbitrarily add some ls additive.  Finally, I stumbled onto Mobile Delvac in one gallon at Zoro (my fav anti-amazon site).  This is rated to 500k miles between drains.  For an RV this interval would never happen, but oil breaks down over time.  My rig has 40k miles on it, but its 10 years old.  I was shocked at the sludge and gunk that I vacuumed out of my s110.  Part of this may have been related to the sloppy stove pipe vent that was updated, that I never knew about.




Be kind to your differential.


Using google business and drive to automate db backup

In this example, I create a Windows service that manages Google Drive.  The service calls a batch file to create an Oracle backup using EXP, manages space on Google Drive and then uploads the backup to google, finally sending an html email via smtp.

The school of hard knocks is a great teacher.  Having a hosting provide fail can be a nightmare.  Invariably this happens without notice and suddenly you are in scramble mode and without sleep for days moving.  There are ways to hedge the dependency on your hosting provider.   One way is to host your DNS with a third party.  This way should things go sideways, you can readily change your DNS settings.  Another good practice is to keep a scheduled cold backup of your database independent of your hosting provider.  Google Drive is a great choice for this.  Not only has google made a commitment to putting their servers up on 10gbs, if your servers are on 1gbs or 10gbs copying large files to google is smoking fast.  The db using the code in this example creates an 800gb cold backup, it takes under 15 seconds to put up on google drive.

Automating this backup process with google drive and Google Business is a bit convoluted, there are security issues and settings that are simpler with a gmail account, or no associated account.  Using Google Business to host your transnational mail is a smart move.  No one messes with Google for mail delivery into the inbox.  Why struggle with a shared ip or building an ip reputation for transnational mail, marketing mail is a different story, but for transnational mail there is no better choice then Google for Business for a small company.  Drive allocation on Google Business, per user, is 30gb vs 15 with gmail.

This examples uses the Google REST API v3, Visual Studio 2012 in C#
Reference https://developers.google.com/drive/v3/web/about-sdk

In a user scenario Google authenticates via a challenge via the browser, unfortunately, that will not work here because we are creating a Service Application, in this case a Windows Service.  Services do not have access to an active user session, fortunately, Google anticipated this need and supports offline authentication for service accounts.

Setup a Service Account on Google
Access console.developers.google.com using admin credentials for your business account.
Locate (the every changing) section for Google Apps APIs
Select Drive (formerly google docs)
Enable Google drive from the API Manager.
Select Credentials (left pane)
Create Credentials as a Service Account key using a project name, this project uses "ServerBackup" but you can name it whatever you want just set in the config.
Under permissions, assign the user name user@yourdomain Permission to use "ServerBackups"  this is how google knows which user account for drive.  From this step you need the internal service-account google created, this is in the form of an email address, it is not the same as a user email address, which is assigned permission to use the service account.  These values go in the config file. This is the connection between the internal service account and the user account.  It is possible to omit the user account in your code when you create the credentials, the service account is created with 15gb of storage, but there is no way to access the service account other than the api.  This is the only way I could this to work was without the user name until I figured out how to associate a user name to the service account.   If you do not care, nor need to access what you are uploading via drive.google.com and 15gb is fine, skip it.  The final step is to create the p12 key for the service account.  Under credentials, select the service account, then manage service accounts, then select create key and download in p12 format.  This is the private key, handle as you wish.  Because google is forever changing their interfaces, screen shots have the lifespan of a fruit-fly.

To implement, this example creates a basic windows service which calls an assembly.

This example only shows how to create the assembly using VS 2012 and 4.0 of the assembly for x64.

Create a new class library and open the Package Manager Console.
PM>Install-Package Google.Apis.Drive.v3
(only do this for the assembly. not the service exe)

Below is the code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Configuration;
using System.IO;
using System.Data;
using System.Net;
using System.Net.Mail;
using System.Threading;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v3;
using Google.Apis.Drive.v3.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
 
namespace GPHousekeepingServer
{
 
    public class SMTPRec
    {
        public bool processConfirmations;
        public string host;
        public string hostUserName;
        public string hostPassword;
        public int hostPort;
        public string to;
        public string from;
        public string htmlSource;
        public string rawHTML;
        public string bcc;
        public string subject;
        public bool useSSL;
        public SMTPRec(string passedhtml, string passedSubject)
        {
            try
            {
                //because this is running from an assembly, cannot use ConfigurationManager directly.
                useSSL = false;
                string path = System.Reflection.Assembly.GetExecutingAssembly().Location;
                Configuration config = ConfigurationManager.OpenExeConfiguration(path);
 
                host = config.AppSettings.Settings["SmtpHost"].Value;
                string sPort = config.AppSettings.Settings["SmtpPort"].Value;
                if (sPort.Equals(string.Empty))
                    throw new Exception("SmtpPort is null");
                if (!int.TryParse(sPort, out hostPort))
                    throw new Exception("Unable to convert SmtpPort to an int.");
 
                if (config.AppSettings.Settings["SmtpHostUserName"] != null)
                {
                    hostUserName = config.AppSettings.Settings["SmtpHostUserName"].Value;
                    if (config.AppSettings.Settings["SmtpHostPassword"] == null)
                        throw new Exception("SmtpHostUserName set, however SmtpHostPassword is null");
                    hostPassword = config.AppSettings.Settings["SmtpHostPassword"].Value;
                }
 
                if (config.AppSettings.Settings["SmtpUseSSL"] != null)
                    useSSL = config.AppSettings.Settings["SmtpUseSSL"].Value.Equals("1");
 
                to = config.AppSettings.Settings["ToEmail"].Value;
                from = config.AppSettings.Settings["FromEmail"].Value;
                bcc = string.Empty;
 
                if (passedSubject.Equals(string.Empty))
                    subject = config.AppSettings.Settings["subject"].Value;
                else
                    subject = passedSubject;
 
                rawHTML = passedhtml;
 
            }
            catch (Exception E)
            {
                throw new Exception("Exception raised in SMTPRec constructor, " + E.Message);
            }
        }
    }
 
    public static class HouseKeeping
    {
 
        private static CollateralClass c = null;
        private static Thread thread = null;
 
        public static void Start()
        {
            try
            {
                if (c != null)
                    throw new Exception("Thread is already running.");
 
                c = new CollateralClass();
                c.bTerminated = false;
 
                thread = new Thread(new ThreadStart(c.Worker));
                thread.Start();
 
            }
            catch (Exception E)
            {
                Utility.SendEventLogError("Fatal error in Housekeeping (stopping), " + E.Message);
            }
        }
 
        public static void Stop()
        {
            try
            {
 
                if (c == null)
                    throw new Exception("Thread is not running, call Execute to start thread.");
                c.bTerminated = true;
 
            }
            catch (Exception E)
            {
                Utility.SendEventLogError("Exception raised in Stop, " + E.Message);
            }
        }
 
 
        class CollateralClass
        {
            public bool bTerminated = false;
            EventLog eventLog = new EventLog();
            Hashtable htReported = new Hashtable();
            public void Worker()
            {
 
                int iLongerJobCounter = 361;
                try
                {
                    while (!bTerminated)
                    {
                        Thread.Sleep(1000 * 5);
                        iLongerJobCounter++;
                        //runs every five minutes
                        if (iLongerJobCounter > 60)
                        {
                            iLongerJobCounter = 0;
                            RunDBBackupJob();
                        }
                    }                 }                 catch (Exception E)                 {                     Utility.SendEventLogError("Exception raised in HousekeepingsServer (thread), " + E.Message);                 }             }         }         private static void RunDBBackupJob()         {             try             {                 DateTime dtLastTime = new DateTime(Utility.GetLastBackup());                 bool bBackup = (DateTime.Now > dtLastTime.AddMinutes(Utility.GetBackupEvery()));                 if (bBackup)                 {                     string sFileName = Utility.GetBackupPrefix()+ DateTime.Now.ToString("yyyyMMdd_HHmm");                     string sPathName = Utility.GetBackupLocation();                     Utility.SendEventLogInfo("GPHousekeepingServer:RunDBBackUpJob to "+sPathName+sFileName);                     var proc = new Process                     {                         StartInfo =                             new ProcessStartInfo                             {                                 FileName = Utility.GetBackupBatchFile(),                                    Arguments = sPathName+sFileName,                                 UseShellExecute = false,                                 CreateNoWindow = false                             }                     };                     proc.Start();                     proc.WaitForExit();                     int successExitCode = 0;                     if (proc.ExitCode == successExitCode)                     {                         Utility.SendEventLogInfo("GPHousekeepingServer:RunDBBackUpJob done with exp, setting up drive");                         var certificate = new X509Certificate2(Utility.GetBackupP12File(), "notasecret"X509KeyStorageFlags.Exportable);                         var serviceAccountEmail = Utility.GetBackupServiceAccount();                         var userAccountEmail = Utility.GetBackupUserAccount();                         ServiceAccountCredential credential = new ServiceAccountCredential(                             new ServiceAccountCredential.Initializer(serviceAccountEmail)                             {                                 Scopes = new[] { Google.Apis.Drive.v3.DriveService.Scope.Drive },                                 User = userAccountEmail                             }.FromCertificate(certificate));                         // Create the service.                         using (DriveService driveService = new DriveService(new BaseClientService.Initializer()                             {                                 HttpClientInitializer = credential,                                 ApplicationName = Utility.GetApplicationName()                             }))                         {                             var about = driveService.About.Get();                             about.Fields = "storageQuota,user";                             User user = about.Execute().User;                             StringBuilder sb = new StringBuilder("<!DOCTYPE><html><head><title></title></head><body>");                             sb.Append("User " + user.DisplayName + ", " + user.EmailAddress + "<br/>");                             if (!System.IO.File.Exists(sPathName + sFileName + ".dmp"))                                 throw new Exception("Unable to location: " + sPathName + sFileName + ".dmp, did " + Utility.GetBackupBatchFile() + " run?");                             FileInfo fi = new FileInfo(sPathName + sFileName + ".dmp");                             //upload to drive.google                             var storageQuota = about.Execute().StorageQuota;                             if (storageQuota.Limit == null)                                 sb.Append("StorageUsage: " + Math.Round((double)(storageQuota.Usage / Math.Pow(1024, 2)), 1).ToString() + "mb, StorageLimit: Unlimited<br/>");                             else                             {                                 sb.Append("StorageUsage: " + Math.Round((double)(storageQuota.Usage / Math.Pow(1024, 2)), 1).ToString() + "mb, StorageLimit: " + (storageQuota.Limit / Math.Pow(1024, 3)) + "gb, Space Available: " + Math.Round((double)((storageQuota.Limit - storageQuota.Usage) / Math.Pow(1024, 2)), 1).ToString() + "mb<br/>");                                 if (fi.Length > (storageQuota.Limit - storageQuota.Usage))                                 {                                     //clean up some space by removing the oldest files.  need to remove fi.length                                     sb.Append("drive.google is full, making space by removing:<br/>");                                     FilesResource.ListRequest listDelete = driveService.Files.List();                                     // listRequest.Spaces = "appDataFolder";                                     listDelete.PageSize = 100;                                     listDelete.Fields = "nextPageToken, files(id, name, size, createdTime)";                                     listDelete.OrderBy = "createdTime asc";                                     IList<Google.Apis.Drive.v3.Data.File> files = listDelete.Execute().Files;                                     long fucking=(long)storageQuota.Limit;                                     long stupid = (long)storageQuota.Usage;                                     long lBytesDeleted = fucking-stupid;                                     if (files != null)                                     {                                         foreach (var file in files)                                         {                                             if (file.Name.ToUpper().Contains("BBB_"))                                             {                                                 sb.Append("Deleted: " + ((DateTime)file.CreatedTime).ToString("MM/dd/yyyy HHmm") + " " + file.Name + " " + file.Id + " size: " + file.Size + "<br/>");                                                 driveService.Files.Delete(file.Id).Execute();                                                 lBytesDeleted += (long)file.Size;                                                 if (lBytesDeleted > fi.Length)                                                     break;                                             }                                         }                                     }                                 }                             }                             //upload file                             Utility.SendEventLogInfo("GPHousekeepingServer:RunDBBackUpJob " + sFileName + " uploading to drive");                             Google.Apis.Drive.v3.Data.File fileMetaData = new Google.Apis.Drive.v3.Data.File();                             fileMetaData.Name = sFileName + ".dmp";                             using (FileStream uploadStream = new FileStream(sPathName + sFileName + ".dmp"FileMode.Open, FileAccess.Read))                             {                                 FilesResource.CreateMediaUpload request = driveService.Files.Create(fileMetaData, uploadStream, "application/octet-stream");                                 request.Fields = "id,size";                                 request.Upload();                                 uploadStream.Close();                                 uploadStream.Dispose();                                 Utility.SendEventLogInfo("GPHousekeepingServer:RunDBBackUpJob File Uploaded to drive, ID: " + request.ResponseBody.Id + ", size: " + request.ResponseBody.Size + "<br/>");                                 sb.Append("File Uploaded, ID: " + request.ResponseBody.Id + ", size: " + request.ResponseBody.Size + "<br/>");                             }                             {                                 FilesResource.ListRequest listRequest = driveService.Files.List();                                 // listRequest.Spaces = "appDataFolder";                                 listRequest.PageSize = 100;                                 listRequest.Fields = "nextPageToken, files(id, name, size, createdTime)";                                 listRequest.OrderBy = "createdTime desc";                                 IList<Google.Apis.Drive.v3.Data.File> files = listRequest.Execute().Files;                                 if (files != null)                                 {                                     sb.Append("FILES on drive.google.com<br/>");                                     foreach (var file in files)                                         sb.Append(((DateTime)file.CreatedTime).ToString("MM/dd/yyyy HHmm") + " " + file.Name + " " + file.Id + " size: " + file.Size + "<br/>");                                 }                             }                             Utility.SetLastBackup(DateTime.Now.Ticks);                             sb.Append("</body></html>");                             SendEMail(new SMTPRec(sb.ToString(), "dbBackup"));                             driveService.Dispose();                         }                     }                     else                     {                         throw new Exception(string.Format("EXP FAILED!  Process exit code not same as successExitCode specified.!! Process ExitCode={0} and successExitCode={1}", proc.ExitCode, successExitCode));                     }                 }             }             catch (Exception E)             {                 Utility.SendEventLogError("Fatal Exception raised in HousekeepingServer.RunDBBackupJob (not-stopping), " + E.Message);             }         }         private static void SendEMail(SMTPRec smtpInfo)         {             try             {                 string sBody = smtpInfo.rawHTML;                 MailAddress maFrom = new MailAddress(smtpInfo.from, "Housekeeping"Encoding.UTF8);                 MailAddress maTo = new MailAddress(smtpInfo.to, smtpInfo.to, Encoding.UTF8);                 MailMessage mailMessage = new MailMessage(maFrom, maTo);                 if (!smtpInfo.bcc.Equals(string.Empty))                     mailMessage.Bcc.Add(smtpInfo.bcc);                 mailMessage.Body = sBody;                 mailMessage.BodyEncoding = Encoding.UTF8;                 mailMessage.IsBodyHtml = true;                 mailMessage.Subject = smtpInfo.subject;                 mailMessage.SubjectEncoding = Encoding.UTF8;                 SmtpClient smtpClient = new SmtpClient(smtpInfo.host, smtpInfo.hostPort);                 smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;                 smtpClient.Timeout = 20000;                 if (smtpInfo.useSSL)                 {                     Utility.SendEventLogInfo("Using SSL");                     smtpClient.EnableSsl = true;                 }                 if (!string.IsNullOrEmpty(smtpInfo.hostUserName))                 {                     Utility.SendEventLogInfo("Setting credentials smtpClient, " + smtpInfo.hostUserName + " password " + smtpInfo.hostPassword);                     smtpClient.UseDefaultCredentials = false;                     smtpClient.Credentials = new NetworkCredential(smtpInfo.hostUserName, smtpInfo.hostPassword); ;                 }                 smtpClient.Send(mailMessage);             }             catch (Exception E)             {                 Utility.SendEventLogError("GPHousekeeping:SendEMail Exception Raised: "+ E.Message);             }         }     } }

Utility.cs
using System;
using System.Data;
using System.Configuration;
using System.Text.RegularExpressions;
using System.IO;
using System.IO.Ports;
using System.Net;
using System.Net.Sockets;
using Oracle.DataAccess.Client;
using System.Diagnostics;
 
 
/// <summary>
/// Summary description for Utility
/// </summary>
public static class Utility
{
    public static System.Diagnostics.EventLog myEventLog;
 
    static Utility()
    {
        if (!EventLog.SourceExists("GPHousekeepingServer"))
            EventLog.CreateEventSource("GPHousekeepingServer""Application");
        myEventLog = new EventLog("Application");
        myEventLog.Source = "GPHousekeepingServer";
    }
 
    public static void SendEventLogError(string errorMessage)
    {
        try
        {
            if (myEventLog != null)
                myEventLog.WriteEntry(errorMessage, EventLogEntryType.Error);
        }
        catch (Exception)
        {
            //nothing to do with this... :(
        }
    }
 
    public static void SendEventLogInfo(string infoMessage)
    {
        try
        {
            if (myEventLog != null)
                myEventLog.WriteEntry(infoMessage, EventLogEntryType.Information);
        }
        catch (Exception)
        {
            //nothing to do with this... :(
        }
    }
 
    public static bool isEmail(string inputEmail)
    {
        string strRegex = @"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}" +
              @"\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\" +
              @".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$";
        Regex re = new Regex(strRegex);
        if (re.IsMatch(inputEmail))
            return (true);
        else
            return (false);
    }
 
 
    public static uint IPDottedToUint(string dottedIP)
    {
        uint uIP = 0;
        try
        {
            string[] sNumbers = dottedIP.Split('.');
            if (sNumbers.GetLength(0) != 4)
                throw new Exception("invalid format, " + dottedIP);
 
            uIP = (uint.Parse(sNumbers[0]) * 16777216) + (uint.Parse(sNumbers[1]) * 65536) + (uint.Parse(sNumbers[2]) * 256) + (uint.Parse(sNumbers[3]));
 
        }
        catch (Exception E)
        {
            throw new Exception("Exception raised in IPDottedToUint, " + E.Message);
        }
        return uIP;
    }
 
 
    public static long GetLastBackup()
    {
        long lResult=0;
        try
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
            if (config.AppSettings.Settings["lastBackupRun"] == null)
            {
                //to force initial run, arbitrarily subtract one day
                lResult = DateTime.Now.AddDays(-1).Ticks;
            }
            else
            {
                if (!long.TryParse(config.AppSettings.Settings["lastBackupRun"].Value, out lResult))
                    throw new Exception("Trouble parsing lastBackupRun (ticks) from config, " + config.AppSettings.Settings["lastBackupRun"].Value);
            }
        }
        catch (Exception E)
        {
            throw new Exception("Exception raised in GetLastBackup, " + E.Message);
        }
        return lResult;
    }
 
    public static void SetLastBackup(long ticks)
    {
        try
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
            if (config.AppSettings.Settings["lastBackupRun"] == null)
                config.AppSettings.Settings.Add("lastBackupRun", ticks.ToString());
            else
                config.AppSettings.Settings["lastBackupRun"].Value = ticks.ToString();
            config.Save(ConfigurationSaveMode.Minimal, false);
        }
        catch (Exception E)
        {
            throw new Exception("Exception raised in SetLastBackup, " + E.Message);
        }
    }
 
    public static string GetBackupLocation()
    {
        string sResult = string.Empty;
        try
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
            if (config.AppSettings.Settings["backuplocation"] == null)
                throw new Exception("backuplocation is not set in App.config");
            sResult = config.AppSettings.Settings["backuplocation"].Value;
 
        }
        catch (Exception E)
        {
            throw new Exception("Exception raised in GetBackupLocation, " + E.Message);
        }
        return sResult;
    }
 
    public static string GetBackupServiceAccount()
    {
        string sResult = string.Empty;
        try
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
            if (config.AppSettings.Settings["backupserviceAccount"] == null)
                throw new Exception("backupserviceAccount is not set in App.config");
            sResult = config.AppSettings.Settings["backupserviceaccount"].Value;
 
        }
        catch (Exception E)
        {
            throw new Exception("Exception raised in GetBackupServiceAccount, " + E.Message);
        }
        return sResult;
    }
 
    public static string GetBackupUserAccount()
    {
        string sResult = string.Empty;
        try
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
            if (config.AppSettings.Settings["backupuseraccount"] == null)
                throw new Exception("backupuseraccount is not set in App.config");
            sResult = config.AppSettings.Settings["backupuseraccount"].Value;
 
        }
        catch (Exception E)
        {
            throw new Exception("Exception raised in GetBackupUserAccount, " + E.Message);
        }
        return sResult;
    }
 
    public static string GetBackupPrefix()
    {
        string sResult = string.Empty;
        try
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
            if (config.AppSettings.Settings["backupprefix"] == null)
                throw new Exception("backupprefix is not set in App.config");
            sResult = config.AppSettings.Settings["backupprefix"].Value;
 
        }
        catch (Exception E)
        {
            throw new Exception("Exception raised in GetBackupPrefix, " + E.Message);
        }
        return sResult;
    }
 
 
    public static string GetApplicationName()
    {
        string sResult = string.Empty;
        try
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
            if (config.AppSettings.Settings["backupapplicationname"] == null)
                throw new Exception("backupapplicationname is not set in App.config");
            sResult = config.AppSettings.Settings["backupapplicationname"].Value;
 
        }
        catch (Exception E)
        {
            throw new Exception("Exception raised in GetApplicationName, " + E.Message);
        }
        return sResult;
    }
 
 
 
    public static string GetBackupBatchFile()
    {
        string sResult = string.Empty;
        try
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
            if (config.AppSettings.Settings["backupbatch"] == null)
                throw new Exception("backupbatch is not set in App.config");
            sResult = config.AppSettings.Settings["backupbatch"].Value;
            if (!File.Exists(sResult))
                throw new Exception("unable to locate file, " + sResult);
        }
        catch (Exception E)
        {
            throw new Exception("Exception raised in GetBackupBatchFile, " + E.Message);
        }
        return sResult;
    }
 
    public static string GetBackupP12File()
    {
        string sResult = string.Empty;
        try
        {
            //determine the code for the current provider
            Configuration config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
            if (config.AppSettings.Settings["backupp12"] == null)
                throw new Exception("backupp12 is not set in App.config");
            sResult = config.AppSettings.Settings["backupp12"].Value;
            if (!File.Exists(sResult))
                throw new Exception("unable to locate file, " + sResult);
        }
        catch (Exception E)
        {
            throw new Exception("Exception raised in GetBackupP12File, " + E.Message);
        }
        return sResult;
    }
 
    public static int GetBackupEvery()
    {
        int iResult = 0;
        try
        {
            //determine the code for the current provider
            Configuration config = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
            if (config.AppSettings.Settings["backupevery"] == null)
                throw new Exception("backupevery is not set in App.config");
            if (!int.TryParse(config.AppSettings.Settings["backupevery"].Value, out iResult))
                throw new Exception("unable to convert to int, " + config.AppSettings.Settings["backupevery"].Value);
        }
        catch (Exception E)
        {
            throw new Exception("Exception raised in GetBackupEvery, " + E.Message);
        }
        return iResult;
    }
 
}



Below is the assemblyname.config file
<configuration>
  <appSettings>
    <add key="SmtpHost" value="smtp.gmail.com" />
    <add key="SmtpPort" value="587" />
    <add key="SmtpHostUserName" value="" />
    <add key="SmtpHostPassword" value="" />
    <add key="SmtpUseSSL" value="1"/>
    <add key="FromEmail" value="" />
    <add key="ToEmail" value="" />
    <add key="subject" value="DB Backup"/>
    <add key="backuplocation" value="c:\garbage\"/>
    <add key="backupbatch" value="c:\GPUtilities\cmdbackup.bat"/>
    <add key="backupevery" value="480"/> 
    <add key="backupp12"   value="c:\GPUtilities\ServerBackups.p12"/>
    <add key="backupserviceaccount" value="...@...gserviceaccount.com"/>
    <add key="backupuseraccount" value=""/>
    <add key="backupapplicationname" value=""/>
    <add key="backupprefix" value="BBB_"/>
  </appSettings>
</configuration>

Example of  batch file called in backupbatch for Oracle, this could be anything so long as it ultimately creates a single file %1 passed in from the service.
@ECHO OFF

SET filename=%1.dmp
SET logname=%1.log

EXP credentials FILE=%filename% OWNER=schema LOG=%logname% STATISTICS=NONE