At work we need to sometimes install a several tomcat instances on multiple machines. This was quite tedious to do manually so in order to speed things up, I wrote a powershell script which automates the installation. So the only thing I would need to do is to download the specific version that is needed and then run the script like:

 .\install-tomcat.ps1 .\apache-tomcat-8.5.43-windows-x64.zip c:\applications\

and it would install the X number of instances.

Below is a simplified script which installs one instance automatically.

Parameters

Let’s create a new powershell file called install-tomcat.ps1 which takes two parameters, the zip-file (of tomcat) and the $destination. Save the directory where the script is for later use.

1
2
param( $tomcatZip, $destination )
$currentDir = $PSScriptRoot

This is all good but lets add some parameter validation so that the file and destination has to exist

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
param(
      [Parameter(Mandatory=$True)]
      [ValidateNotNullorEmpty()]
      [ValidateScript({
        if( -Not ($_ | Test-Path) ){
          throw "File '$_' does not exist"
        }
        if(-Not ($_ | Test-Path -PathType Leaf) ){
          throw "The tomcatZip argument must be a file. Folder paths are not allowed."
        }
        if($_ -notmatch "(\.zip)"){
          throw "The file specified in the path argument must be of type zip"
        }
        return $true
      })]
      [System.IO.FileInfo]$tomcatZip
    ,
      [Parameter(Mandatory=$True)]
      [ValidateNotNullorEmpty()]
      [ValidateScript({
       if( -Not ($_ | Test-Path) ){
         throw "File or folder '$_' does not exist"
       }
        return $true
      })]
      [System.IO.FileInfo]$Destination
    )

$currentDir = $PSScriptRoot

Unzip tomcat-zip

First we need to get the version number from the zip-filename to use later.

29
30
31
32
33
$currentDir = $PSScriptRoot

# Get version number from tomcatZip
$tomcatZip.BaseName -match "apache-tomcat-(?<content>.*)-windows*"
$versionNumber = $matches['content']

Now we can unzip the tomcat-zip file to a temporary directory

35
36
37
38
39
# Check if temporary directory exist and expand archive
$unpackedTomcatExists = Get-ChildItem -Directory -Filter apache-tomcat-*
if (!$unpackedTomcatExists) {
    Expand-Archive "$($tomcatZip.FullName)" -DestinationPath "$currentDir\"
}

Copy directory to destination

Now we can copy the tomcat directory to the destination with the corret name.

41
42
# Copy to destination
Copy-Item -Path "$currentDir\$unpackedTomcat" -Recurse -Destination "$destination\Tomcat$versionNumber"

In case we dont want to use the default ports of 8080/8005/8009 we need to change the values in tomcats server.xml.

We can enter our desired values in a json-file in the same directory.

ports.json:

1
2
3
4
5
{
  "port": "8383",
  "shutdownPort" : "8335",
  "ajpPort" : "8339"
}

Now we need to read the json-file from powershell

43
$ports = Get-Content "$PSScriptRoot\ports.json" -raw | ConvertFrom-Json

What we now need to do is to change the values in server.xml

45
46
47
48
49
50
# Change ports in server.xml
(Get-Content "$destination\Tomcat$versionNumber\conf\server.xml")
        .replace('8005', "$($ports.shutdownPort)")
        .replace('8080', "$($ports.port)")
        .replace('8009', "$($ports.ajpPort)") |
        Set-Content "$destination\Tomcat$versionNumber\conf\server.xml"

Since I wanted to use the version number in the service name I had to rename tomcat8w.exe to the correct name also:

52
53
# Rename tomcatXw.exe to correct name.
Rename-Item -Path "$destination\Tomcat$versionNumber\bin\tomcat8w.exe" -NewName "Tomcat$versionNumberw.exe"

Now the only thing left is to install the services. Now you could make this value a parameter but like the other two but lets make it a promtable choice.

55
56
57
# Ask if to install service
$choices  = '&Yes', '&No'
$installService = $Host.UI.PromptForChoice('', 'Do you want to install services?', $choices, 1)

Now the only thing left is to install the services. Now you could make this value a parameter but like the other two but lets make it a promtable choice. Then we install it as a service and at the same time change some of the default configuration for it as well.

59
60
61
62
63
64
65
66
67
68
if ($installService -eq 0) {
        #Installing Tomcat$versionNumber as a service"
        Set-Location $destination\Tomcat$versionNumber\bin
        cmd.exe /c "service.bat install Tomcat$($versionNumber)"
        $logConfig = $($logbackFile.FullName)

        # Change default configuration of service
        cmd.exe /c "tomcat8.exe //US//Tomcat$($versionNumber) --Startup=auto --JvmMs=256 --JvmMx=1024"
        Set-Location $currentDir
    }

Complete script

Here’s the complete script in it’s glory.

install-tomcat.ps1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
param(
      [Parameter(Mandatory=$True)]
      [ValidateScript({
        if( -Not ($_ | Test-Path) ){
          throw "File '$_' does not exist"
        }
        if(-Not ($_ | Test-Path -PathType Leaf) ){
          throw "The tomcatZip argument must be a file. Folder paths are not allowed."
        }
        if($_ -notmatch "(\.zip)"){
          throw "The file specified in the path argument must be of type zip"
        }
        return $true
      })]
      [System.IO.FileInfo]$tomcatZip
    ,
      [Parameter(Mandatory=$True)]
      [ValidateScript({
       if( -Not ($_ | Test-Path) ){
         throw "File or folder '$_' does not exist"
       }
        return $true
      })]
      [System.IO.FileInfo]$destination
    )

$currentDir = $PSScriptRoot

# Get version number from tomcatZip
$tomcatZip.BaseName -match "apache-tomcat-(?<content>.*)-windows*"
$versionNumber = $matches['content']

Write-Output $versionNumber

# Check if temporary directory exist and expand archive
$unpackedTomcatExists = Get-ChildItem -Directory -Filter apache-tomcat-*
if (!$unpackedTomcatExists) {
    Expand-Archive "$($tomcatZip.FullName)" -DestinationPath "$currentDir\"
}

# Copy to destination
Copy-Item -Path "$currentDir\$unpackedTomcat" -Recurse -Destination "$destination\Tomcat$versionNumber"

$ports = Get-Content "$PSScriptRoot\ports.json" -raw | ConvertFrom-Json

# Change ports in server.xml
(Get-Content "$destination\Tomcat$versionNumber\conf\server.xml")
        .replace('8005', "$($ports.shutdownPort)")
        .replace('8080', "$($ports.port)")
        .replace('8009', "$($ports.ajpPort)") |
        Set-Content "$destination\Tomcat$versionNumber\conf\server.xml"

# Rename tomcatXw.exe to correct name.
Rename-Item -Path "$destination\Tomcat$versionNumber\bin\tomcat8w.exe" -NewName "Tomcat$versionNumberw.exe"

# Ask if to install service
$choices  = '&Yes', '&No'
$installService = $Host.UI.PromptForChoice('', 'Do you want to install services?', $choices, 1)

if ($installService -eq 0) {
  #Installing Tomcat$versionNumber as a service"
  Set-Location $destination\Tomcat$versionNumber\bin
  cmd.exe /c "service.bat install Tomcat$($versionNumber)"
  $logConfig = $($logbackFile.FullName)

  # Change default configuration of service
  cmd.exe /c "tomcat8.exe //US//Tomcat$($versionNumber) --Startup=auto --JvmMs=256 --JvmMx=1024"
  Set-Location $currentDir
}

ports.json:

1
2
3
4
5
{
  "port": "8383",
  "shutdownPort" : "8335",
  "ajpPort" : "8339"
}

If you need to install several tomcat instances, then you would need to loop through the number of instances and copy unpacked the tomcat-folder to the destination (with different names of course) to save some time and not need to unpack it several times