100% Automation of Java Updates

Oracle’s Java technology has become a favorite target of hackers and malware writers over the past few years. In response, Oracle has increased the frequency of Java updates in an effort to battle them.

For home users this is easy, you can use Java’s built-in auto update mechanism. But as IT Admins you probably want to do this in a controlled manner as Java updates sometimes break applications.

Unfortunately the process of obtaining the Java installables from Oracle isn’t really “Enterprise ready” because the normal download is a web installer. And to make it worse, Oracle tries to push the Ask toolbar every time, even if you declined that in the previous install: 

The alternative is using the manual download page where you can download Offline installers:


Java offline updates

This will download an executable that can be used for unattended installation as documented here.

But how to automate the whole process? 

Automate it
But what if we want to automate this download, so that Java updates are automatically downloaded and ready for integration in your favorite deployment tool?

Unfortunately there is no standard method for determing the current Java version and/or a link to the most recent Java version (eg java.com/download/current). Therefore I decided to parse the download page using PowerShell. The Invoke-WebRequest cmdlet is ideal for such purposes however as the url extension indicates we do not get HTML but Java Server Pages so we need a browser to interpret it (I guess it’s logical for Java to use Java but it doesn’t make our job easier…).

Luckily we can automate Internet Explorer easily with PowerShell, just change the path of $downloads to a folder of choice and launch the script. If the version already exists it will not be overwritten:  
 
# Download folder
$downloads = "C:\Users\Remko\Downloads\Java"

function DownloadFile([string]$url)
{
	$response = Invoke-WebRequest -Uri $url
	# get filename from the link
	$filename = Split-Path -Path $response.BaseResponse.ResponseUri.AbsolutePath -Leaf
	$filename = "$downloads\$filename"

	if (Test-Path $filename)
	{
		Write-Host "File $filename already exists"
	}
	else
	{
		# Download the file
		Write-Host ("Downloading {0} to {1}" -f $update, $filename)
		Invoke-WebRequest -Uri $url -OutFile $filename -DisableKeepAlive
	}
}

$url = "http://www.java.com/en/download/manual.jsp"

if (!(Test-Path -Path $downloads))
{
	# Create folder (silent)
	Write-Host "Creating folder $downloads" 
	New-Item -ItemType Directory -Path $downloads | Out-Null
}

# Using IE because the page is rendered using JavaScript so we need a browser
# to execute that
Write-Host ("Navigating to {0}" -f $url)
$ie = New-Object -ComObject "InternetExplorer.Application"

# Hide the IE Window
$ie.visible = $false

#Navigate to URL
$ie.navigate($url)

while ($ie.Busy)
{
	# Wait for Internet Explorer to finish (parsing JavaScript)
	Start-Sleep -Milliseconds 100
}
Write-Host ("Downloaded {0} bytes" -f $ie.Document.fileSize)
 
However an executable installer is not the preferred option because not all deployment tools are able to handle executables and there is no error control like with an MSI file where you a meaningful error code, logging and so on.

Oracle has documented a method to extract the MSI and CAB files from the exe installer and they must like manual work! In summary, the procedure is to launch the exe and fetch the files from a temp location before closing the exe.

So let’s automate that!

More Automation!
Since there is no documented method of extracting the installer files we have to reverse Java’s installer to see what it’s doing. I opened the executable in Ida Pro and search for “.msi” in the executables Strings, finding one:


Ida Pro | Java Installer | Strings | MSI


I tracked it’s references (Ctrl-X) and there was only one, pointing to WinMain (the entry point of an executable):


Ida Pro | References


In the code we can see that some arguments are being pushed on the stack (the .msi being one of them) and then function “sub_40F2A5” is called:


Ida Pro Disassembly Java #1


This function extracts resources and writes them to disk:


Ida Pro Disassembly Java #2


The Windows API’s used here like FindResource, LoadResource indicate that the msi and cab file are standard windows resources. An executable can not only embed icons, cursors, bitmaps but in fact any data of choice. Using a tool like Resource Hacker we can see the resources which are in a “folder” called JAVA_INSTALLER:


Java Installer | Resource hacker | Screenshot


So let’s look back to the code, the string JAVA_INSTALLER is pushed on stack, followed by hex 6A, which is 106 decimal. This means that resource id
106 is requested from “JAVA_INSTALLER”:


Ida Pro Disassembly Java #3


I have examined all the resources and the code and there are two msi’s present, one for Patch-in-Place installation and one for Static installation (see here for the differences).

The following ID’s can be used:
 
        public static int JavaCabId = 0x66;
        public static int JavaMsiStaticId = 0x67;
        public static int JavaMsiId = 0x65;

That leads to the following script to automatically extract the MSI and Data1.cab file:
 
$definition = @"
using System;
using System.Runtime.InteropServices;

namespace Java
{
    public class Installer
    {
        public static int JavaCabId = 0x66;
        public static int JavaMsiStaticId = 0x67;
        public static int JavaMsiId = 0x65; 

        // Java stores the resources in a named resource type JAVA_INSTALLER
        private static string JavaInstaller = "JAVA_INSTALLER";

        public static bool ExtractFile(string dllName, int id, string fileName)
        {
            IntPtr hModule;
            IntPtr lpJavaInstaller;
            IntPtr hResource;
            IntPtr hGlobal;
            IntPtr Buffer;
            uint dwSize;

            // Load file as dll but only for resource loading
            hModule = LoadLibraryEx(dllName, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE);
            if (hModule == IntPtr.Zero)
            {
                return false;
            }

            try
            {
                // Get pointer to resource type string
                lpJavaInstaller = (IntPtr)Marshal.StringToHGlobalAnsi(JavaInstaller);

                // Find the resource location
                hResource = FindResource(hModule, (IntPtr)id, lpJavaInstaller);

                // We can release the pointer to the resource type string now
                Marshal.FreeHGlobal(lpJavaInstaller);

                if (hResource == IntPtr.Zero)
                {
                    return false;
                }

                // Get resource size
                dwSize = SizeofResource(hModule, hResource);
                if (dwSize > 0)
                {

                    // Get a handle to the first byte of the resource
                    hGlobal = LoadResource(hModule, hResource);
                    if (hGlobal == IntPtr.Zero)
                    {
                        return false;
                    }

                    // Get a pointer to the resource
                    Buffer = LockResource(hGlobal);
                    if (Buffer == IntPtr.Zero)
                    {
                        return false;
                    }

                    // Copy the resource to a byte array
                    byte[] b = new byte[dwSize];
                    Marshal.Copy(Buffer, b, 0, b.Length);

                    // Save the byte array to disk
                    System.IO.File.WriteAllBytes(fileName, b);

                }
            }
            finally
            {
                // Unload
                FreeLibrary(hModule);
            }

            return true;
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, EntryPoint = "LoadLibrary", SetLastError = true)]
        private static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPTStr)]string lpFileName);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool FreeLibrary(IntPtr hModule);

        private static uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr LoadLibraryEx([MarshalAs(UnmanagedType.LPTStr)]string lpFileName, IntPtr hReservedNull, UInt32 dwFlags);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType);

		[DllImport("kernel32.dll", SetLastError = true)]
        private static extern uint SizeofResource(IntPtr hModule, IntPtr hResource);

	    [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResource);

    	[DllImport("Kernel32.dll", EntryPoint = "LockResource")]
        private static extern IntPtr LockResource(IntPtr hGlobal);
	}
}
"@

Add-Type -TypeDefinition $definition -PassThru


And here’s a sample to use it:
 
[Java.Installer]::ExtractFile("c:\Temp\Java\jre-7u51-windows-i586.exe", [Java.Installer]::JavaCabId, "c:\temp\java\data1.cab")
[Java.Installer]::ExtractFile("c:\Temp\Java\jre-7u51-windows-i586.exe", [Java.Installer]::JavaMsiStaticId, "c:\temp\java\data1.msi")

3 Comments

Dan Gough
Hi Remko, I have modified your script and made a .cmd file wrapper to enable automatic extraction of the MSI package by a simple drag'n'drop operation!

I've used it in my guide to sequencing Java with App-V here: http://packageology.com/2014/02/sequencing-java-the-definitive-guide-part-1/

And here's a direct link to the modified script if anyone wants it:
http://packageology.com/wp-content/uploads/2014/02/ExtractJava.zip
Radjin Missier
Hi Remko,

function DownloadFile([string]$url) does not work.
It needs a global variable $update, which is not defined anywhere.

Regards,
Radjin
Someone
Thanks for a great script.
It works perfectly, however only on Java 7. The Java 8 installer has been changed, so your code doesn't work to extract the MSI anymore.

Any chance you could update the code to work with Java 8 as well?

Thanks again for a job well done!

Not Published

0/1000 characters
Go Top