DSCResources/ArcGIS_Tomcat/ArcGIS_Tomcat.psm1
function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [parameter(Mandatory = $true)] [System.String] $ExternalDNSName, [parameter(Mandatory = $true)] [System.String] $Version, [parameter(Mandatory = $true)] [System.String] $ServiceName, [parameter(Mandatory = $true)] [System.String] $InstallerArchivePath, [parameter(Mandatory = $false)] [System.String] $InstallerArchiveOverrideFolderName, [parameter(Mandatory = $true)] [System.String] $InstallDirectory, [System.String] $CertificateFileLocation, [System.Management.Automation.PSCredential] $CertificatePassword, [System.String] $SSLProtocols = "TLSv1.3,TLSv1.2" ) $null } function Set-TargetResource { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [System.String] $ExternalDNSName, [parameter(Mandatory = $true)] [System.String] $Version, [parameter(Mandatory = $true)] [System.String] $ServiceName, [parameter(Mandatory = $true)] [System.String] $InstallerArchivePath, [parameter(Mandatory = $false)] [System.String] $InstallerArchiveOverrideFolderName, [parameter(Mandatory = $true)] [System.String] $InstallDirectory, [System.String] $CertificateFileLocation, [System.Management.Automation.PSCredential] $CertificatePassword, [System.String] $SSLProtocols = "TLSv1.3,TLSv1.2" ) $HttpPort = 80 $HttpsPort = 443 $RestartTomcat = $False $ServerXMLNeedsUpdate = $False if(-not(Test-ApacheTomcatInstall -TomcatVersion $Version -InstallDirectory $InstallDirectory -TomcatServiceName $ServiceName)){ Write-Verbose "Installing Apache Tomcat $($TomcatVersion) - '$ServiceName' service. Removing existing installation if present." if(Test-Path $InstallDirectory){ if(Get-Service $ServiceName -ErrorAction Ignore) { Stop-Service -Name $ServiceName -Force Write-Verbose 'Stopping the service' Wait-ForServiceToReachDesiredState -ServiceName $ServiceName -DesiredState 'Stopped' Write-Verbose 'Stopped the service' $service = Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'" $service.delete() } Remove-Item -Recurse -Path "$InstallDirectory\*" -Force -ErrorAction SilentlyContinue } Expand-Archive -Path $InstallerArchivePath -DestinationPath $InstallDirectory -Force | Out-Null if([string]::IsNullOrEmpty($InstallerArchiveOverrideFolderName)){ $InstallerArchiveOverrideFolderName = "apache-tomcat-$($Version)" } $ArchiveContentPath = Join-Path $InstallDirectory $InstallerArchiveOverrideFolderName Move-Item -Path $ArchiveContentPath\* -Destination $InstallDirectory -Force Remove-Item -Path $ArchiveContentPath -Force $WebAppFolder = (Join-Path $InstallDirectory "webapps") foreach($FolderNameToDelete in @("manager","host-manager","examples", "docs")){ $FolderToDeletePath = Join-Path $WebAppFolder $FolderNameToDelete if(Test-Path $FolderToDeletePath){ Remove-Item -Path $FolderToDeletePath -Force -Recurse } } Invoke-StartProcess -ExecPath "$InstallDirectory\\bin\\service.bat" -Arguments "install $ServiceName" -CatalinaHome $InstallDirectory -AddJavaEnvironmentVariables $True -Verbose Write-Verbose "Setting '$ServiceName' service startup to Automatic" Set-Service -Name $ServiceName -StartupType Automatic $ServerXMLNeedsUpdate = $True } $KeyStoreName = "arcgis.keystore" $TomcatConf = (Join-Path $InstallDirectory "conf") $TomcatServerXML = Join-Path $TomcatConf "server.xml" $KeyStorePath = Join-Path $TomcatConf $KeyStoreName $Base64KeyStorePass = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($ExternalDNSName)) if(-not($ServerXMLNeedsUpdate)){ $ServerXMLNeedsUpdate = -not( Test-ApacheTomcatServerXML -TomcatServerXML $TomcatServerXML -Base64KeyStorePass $Base64KeyStorePass ` -HttpPort $HttpPort -HttpsPort $HttpsPort -CertificateFileLocation $CertificateFileLocation ` -CertificatePassword $CertificatePassword -SSLProtocols $SSLProtocols -KeyStorePath $KeyStorePath ) } if($ServerXMLNeedsUpdate){ $SampleServerXML = Join-path $PSScriptRoot "server.xml" Copy-Item -Path $SampleServerXML -Destination $TomcatServerXML -Force [xml]$ServerXML = Get-Content $TomcatServerXML $Connectors = ($ServerXML.Server.Service | Where-Object { $_.name -ieq "Catalina" }).Connector foreach($Connector in $Connectors){ if($Connector.scheme -ieq "https"){ $Connector.SetAttribute("port", $HttpsPort); $Connector.SetAttribute("sslEnabledProtocols", $SSLProtocols); $Connector.SetAttribute("keystoreFile", $KeyStorePath); $Connector.SetAttribute("keystorePass", $Base64KeyStorePass); }else{ $Connector.SetAttribute("port", $HttpPort); } } $ServerXML.Save($TomcatServerXML) $RestartTomcat = $True } $CreateKeyStore = if(-not(Test-Path $KeyStorePath)){ $True }else{ $False } $UserProvidedCertificate = $($CertificateFileLocation -and ($null -ne $CertificatePassword) -and (Test-Path $CertificateFileLocation)) $CertAliasInKeyStore = $ExternalDNSName if($UserProvidedCertificate){ $CertToInstall = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $CertToInstall.Import($CertificateFileLocation, $CertificatePassword.GetNetworkCredential().Password, 'DefaultKeySet') $CertAliasInKeyStore = $CertToInstall.Thumbprint } if(-not($CreateKeyStore)){ Write-Verbose "Key Store Exists. Testing if the key store accessible and certificate is already in the key store" try{ $CertificateInKeyStore = Invoke-StartProcess -ExecPath "keytool.exe" -Arguments " -list -keystore $KeyStoreName -storepass $Base64KeyStorePass -alias $CertAliasInKeyStore" -AddJavaEnvironmentVariables $true -WorkingDirectory $TomcatConf -Verbose if($CertificateInKeyStore -match "^$CertAliasInKeyStore"){ Write-Verbose "Certificate with thumbprint $CertAliasInKeyStore found in the key store." }else{ Write-Verbose "Certificate does not exist in the key store. Deleting the key store" Remove-Item $KeyStorePath -Force $CreateKeyStore = $True } }catch{ Write-Verbose "Key Store is not accessible. Deleting the key store. Error - $_" } } if($CreateKeyStore){ Write-Verbose "Creating Key Store" if($UserProvidedCertificate){ $srcStorePass = $CertificatePassword.GetNetworkCredential().Password # Get Alias from the certificate $Certificate = Invoke-StartProcess -ExecPath "keytool.exe" -Arguments " -v -list -keystore `"$CertificateFileLocation`" -storepass $srcStorePass" -Verbose $CertificateAlias = (($Certificate -split "`r`n") | Where-Object { $_ -match "Alias name: " } | Select-Object -First 1 | ForEach-Object { $_.Split(":")[1].Trim() }) $Arguments = [System.String]::Join("", @(" -importkeystore -noprompt", " -srcstoretype pkcs12 -alias `"$CertificateAlias`" -srckeystore `"$CertificateFileLocation`" -srcstorepass `"$srcStorePass`"", " -deststoretype pkcs12 -destalias `"$CertAliasInKeyStore`" -destkeystore `"$KeyStoreName`" -storepass `"$Base64KeyStorePass`"")) Write-Verbose $Arguments }else{ $Arguments = [System.String]::Join("", @(" -genkeypair -keystore `"$KeyStoreName`" -keyalg RSA -keysize 2048 -validity 1825", " -storetype PKCS12 -storepass `"$Base64KeyStorePass`" -alias `"$CertAliasInKeyStore`"", " -dname `"CN=$ExternalDNSName`"")) } $CertificateInKeyStore = Invoke-StartProcess -ExecPath "keytool.exe" -Arguments $Arguments -WorkingDirectory $TomcatConf -AddJavaEnvironmentVariables $true -Verbose $RestartTomcat = $True } if($RestartTomcat){ Write-Verbose "Stop Service '$ServiceName'" Stop-Service -Name $ServiceName -Force Write-Verbose 'Stopping the service' Wait-ForServiceToReachDesiredState -ServiceName $ServiceName -DesiredState 'Stopped' Write-Verbose 'Stopped the service' Write-Verbose "Restarting Service '$ServiceName' to pick up property change" Start-Service $ServiceName Wait-ForServiceToReachDesiredState -ServiceName $ServiceName -DesiredState 'Running' Write-Verbose "Restarted Service '$ServiceName'" } } function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [parameter(Mandatory = $true)] [System.String] $ExternalDNSName, [parameter(Mandatory = $true)] [System.String] $Version, [parameter(Mandatory = $true)] [System.String] $ServiceName, [parameter(Mandatory = $true)] [System.String] $InstallerArchivePath, [parameter(Mandatory = $false)] [System.String] $InstallerArchiveOverrideFolderName, [parameter(Mandatory = $true)] [System.String] $InstallDirectory, [System.String] $CertificateFileLocation, [System.Management.Automation.PSCredential] $CertificatePassword, [System.String] $SSLProtocols = "TLSv1.3,TLSv1.2" ) $HttpPort = 80 $HttpsPort = 443 $result = $True $JAVA_HOME = [environment]::GetEnvironmentVariable("JAVA_HOME","Machine") $JRE_HOME = [environment]::GetEnvironmentVariable("JRE_HOME","Machine") if (-not((-not([string]::IsNullOrEmpty($JAVA_HOME)) -and (Test-Path -Path "$($JAVA_HOME)")) -or (-not([string]::IsNullOrEmpty($JRE_HOME)) -and (Test-Path -Path "$($JRE_HOME)")))) { throw "Java not installed." } if(Test-ApacheTomcatInstall -TomcatVersion $Version -TomcatServiceName $ServiceName -InstallDirectory $InstallDirectory){ $TomcatConf = (Join-Path $InstallDirectory "conf") $KeyStoreName = "arcgis.keystore" $KeyStorePath = Join-Path $TomcatConf $KeyStoreName $TomcatServerXML = Join-Path $TomcatConf "server.xml" $Base64KeyStorePass = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($ExternalDNSName)) $result = Test-ApacheTomcatServerXML -TomcatServerXML $TomcatServerXML -Base64KeyStorePass $Base64KeyStorePass ` -HttpPort $HttpPort -HttpsPort $HttpsPort -CertificateFileLocation $CertificateFileLocation ` -CertificatePassword $CertificatePassword -SSLProtocols $SSLProtocols -KeyStorePath $KeyStorePath if($result){ if(Test-Path $KeyStorePath){ Write-Verbose "Key Store Exists. Testing if the key store accessible and certificate is already in the key store" if($CertificateFileLocation -and ($null -ne $CertificatePassword) -and (Test-Path $CertificateFileLocation)){ $CertToInstall = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $CertToInstall.Import($CertificateFileLocation, $CertificatePassword.GetNetworkCredential().Password, 'DefaultKeySet') $CertAliasInKeyStore = $CertToInstall.Thumbprint }else{ $CertAliasInKeyStore = $ExternalDNSName } try{ $CertificateInKeyStore = Invoke-StartProcess -ExecPath "keytool.exe" -Arguments " -list -keystore `"$KeyStoreName`" -storepass `"$Base64KeyStorePass`" -alias `"$CertAliasInKeyStore`"" -AddJavaEnvironmentVariables $true -WorkingDirectory $TomcatConf if($CertificateInKeyStore -imatch "^$CertAliasInKeyStore"){ Write-Verbose "Certificate with alias $CertAliasInKeyStore found in the key store." }else{ $result = $false } }catch{ Write-Verbose "Key Store is not accessible. Error - $_" $result = $false } }else{ Write-Verbose "Key Store does not exist." $result = $false } }else{ Write-Verbose "Apache tomcat Server.xml config not as expected." } }else{ $result = $False } $result } function Test-ApacheTomcatInstall { [CmdletBinding()] [OutputType([System.Boolean])] param ( [System.String] $TomcatVersion, [System.String] $InstallDirectory, [System.String] $TomcatServiceName ) $result = $False if(Get-Service $TomcatServiceName -ErrorAction Ignore) { $ServiceExePathObj = (Get-CimInstance -ClassName win32_service | Where-Object { $_.Name -imatch $TomcatServiceName}) if(($ServiceExePathObj | Measure-Object).Count -ge 0){ $TomcatExePath = ($ServiceExePathObj.PathName -Split ".exe")[0].TrimStart('"') + ".exe" Write-Verbose "Apache Tomcat with Service Name $($TomcatServiceName) found. Installed Apache Tomcat Exe Path - $($TomcatExePath). Expected Install Dir - $($InstallDirectory)" if((Test-FileInSubPath -Dir $InstallDirectory -File "$TomcatExePath")){ Write-Verbose "Apache Tomcat with Service Name $($TomcatServiceName) found and installed in $($InstallDirectory)" $VersionOutput = Invoke-StartProcess -ExecPath "$InstallDirectory\\bin\\version.bat" -AddJavaEnvironmentVariables $true -CatalinaHome $InstallDirectory -Verbose if($VersionOutput -imatch "Server version: Apache Tomcat/$($TomcatVersion)"){ $result = $True Write-Verbose "Apache Tomcat $($TomcatVersion) found and installed in $($InstallDirectory)" } }else{ Write-Verbose "Apache Tomcat found installed in $($InstallDirectory) but not version $($TomcatVersion)" } }else{ Write-Verbose "Apache Tomcat with service name $($TomcatServiceName) found but not installed in $($InstallDirectory)" } }else{ Write-Verbose "Apache Tomcat service $($TomcatServiceName) not found" } $result } function Test-ApacheTomcatServerXML { [CmdletBinding()] [OutputType([System.Boolean])] param ( [System.String] $TomcatServerXML, [System.String] $KeyStorePath, [System.String] $Base64KeyStorePass, [parameter(Mandatory = $false)] [System.Int32] $HttpPort = 80, [parameter(Mandatory = $false)] [System.Int32] $HttpsPort = 443, [System.String] $CertificateFileLocation, [System.Management.Automation.PSCredential] $CertificatePassword, [System.String] $SSLProtocols = "TLSv1.3,TLSv1.2" ) $result = $true [xml]$ServerXML = Get-Content $TomcatServerXML $Connectors = ($ServerXML.Server.Service | Where-Object { $_.name -ieq "Catalina" }).Connector $HttpConnector = $Connectors | Where-Object { $_.scheme -eq "http"} if(($HttpConnector | Measure-Object).Count -eq 0){ Write-Verbose "Http Connector not found" $result = $false }else{ if($HttpConnector.port -ne $HttpPort){ Write-Verbose "Http Port $($HttpConnector.port) not set as expected $($HttpPort)" $result = $false }else{ Write-Verbose "Http Port $($HttpConnector.port) set as expected" } } $HttpsConnector = $Connectors | Where-Object { $_.scheme -eq "https"} if(($HttpsConnector | Measure-Object).Count -eq 0){ Write-Verbose "Https Connector not found" $result = $false }else{ if($HttpsConnector.port -ne $HttpsPort){ Write-Verbose "Https Port not set as expected $($HttpsConnector.port)" $result = $false } if($HttpsConnector.sslEnabledProtocols -ine $SSLProtocols){ Write-Verbose "SSL Protocols $($HttpsConnector.sslEnabledProtocols) not set as expected $($SSLProtocols)" $result = $false } if($HttpsConnector.keystoreFile -ine $KeyStorePath){ Write-Verbose "Keystore file reference $($HttpsConnector.keystoreFile) not set as expected $($KeyStorePath)" $result = $false } if($HttpsConnector.keystorePass -ine $Base64KeyStorePass){ Write-Verbose "Keystore password not set as expected" $result = $false } } return $result } function Test-FileInSubPath { [CmdletBinding()] [OutputType([System.Boolean])] param( [System.IO.DirectoryInfo] $Dir, [System.IO.FileInfo] $File ) $File.FullName.StartsWith($Dir.FullName) } function Invoke-StartProcess { [CmdletBinding()] param( [System.String] $ExecPath, [System.String] $Arguments, [System.Boolean] $AddJavaEnvironmentVariables = $False, [Parameter(Mandatory = $false)] [System.String] $WorkingDirectory, [Parameter(Mandatory = $false)] [System.String] $CatalinaHome = $null ) $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.EnvironmentVariables["PATH"] = [environment]::GetEnvironmentVariable("PATH","Machine") if($AddJavaEnvironmentVariables){ $JAVA_HOME = [environment]::GetEnvironmentVariable("JAVA_HOME","Machine") if($JAVA_HOME){ $psi.EnvironmentVariables["JAVA_HOME"] = $JAVA_HOME } $JRE_HOME = [environment]::GetEnvironmentVariable("JRE_HOME","Machine") if($JRE_HOME){ $psi.EnvironmentVariables["JRE_HOME"] = $JRE_HOME } if($null -ne $CatalinaHome){ $psi.EnvironmentVariables["CATALINA_HOME"] = $CatalinaHome } } $psi.FileName = $ExecPath if($null -ne $WorkingDirectory){ $psi.WorkingDirectory = $WorkingDirectory } $psi.Arguments = $Arguments $psi.UseShellExecute = $false #start the process from it's own executable file $psi.RedirectStandardOutput = $true #enable the process to read from standard output $psi.RedirectStandardError = $true #enable the process to read from standard error $p = [System.Diagnostics.Process]::Start($psi) $p.WaitForExit() $op = $p.StandardOutput.ReadToEnd() Write-Verbose "Exit Code - $($p.ExitCode), Standard Output - $op" if($p.ExitCode -eq 0) { $op }else { $err = $p.StandardError.ReadToEnd() if($err -and $err.Length -gt 0) { Write-Verbose $err } throw "$($ExecPath) failed. Process exit code:- $($p.ExitCode). Error - $($err)" } } Export-ModuleMember -Function *-TargetResource |