Creating a config file for MSI packaging software for an executable name that varies

mugsWithPaintings

The problem

Client wants our software packaged as an MSI (Microsoft Installer).  Found some software that will do this called exemsi.  Exemsi comes in a free mode with a GUI interface and a professional mode ($200 per machine) that can be called from a build process.

The professional mode relies on a configuration file.  The name of the executable file that you want to wrap in MSI is specified in the configuration file.

If the name of the executable file changes from release to release (for example, if it has a version number as part of the file name), the configuration file is supposed to have a way of finding the file based on wildcards and regular expressions.  I could not get this to work and had extreme difficulty reaching the author of the software.  He did reply to me once, but his suggestion did not solve the problem.

So, after much frustration, I decided to just make a little script that would read from a basic template configuration file, and write out nearly every line to a temporary configuration file for the current build.  Nearly every line because one line would have a substitution.  The line that specifies the executable name would be substituted for the installer file name of the current release.

I started writing this script as a DOS batch file.  I ran into trouble trying to make a loop with multiple command lines inside the loop.  I started thinking I’d scrap DOS and do it in Python, when my boss suggested Powershell.  Either one would serve.  The two machines on which this script would need to run already had Powershell installed, so that’s what won.

Besides, it afforded me a nice opportunity to learn how to use Powershell, having never used it before.

The solution

Here’s the Powershell script, which I called makeMsiWrapperConfig.ps1:

#To call from command line:
#  powershell -noexit "& ""path\makemsiwrapperconfig.ps1"" ""path\installer executable file name"" ""path\exemsi config template file"" ""path\resultant exemsi config file"""
#
#  where path is substituted with the actual path to the powershell file,
#  and installer executable file name is substituted with the actual lite installer file name,
#  and exemsi config template file is the name of the template configuration file for the exemsi software that wraps our installer executable in an MSI package,
#  and resultant exemsi config file is the output file with the correct executable name in it.
#  The outer set of quotation marks is so that powershell won't incorrectly parse the parameters if there are any spaces in the paths or filenames.
#  The next level of quotation marks is to escape the inner-most level of quotation marks so that once inside the script, the variable will be quoted.
#
#Example: 
#  powershell -noexit "& "".\makemsiwrapperconfig.ps1"" "".\target\app-123456.exe"" ""c:\resources\msiWrapper.xml"" "".\target\msiWrapper.xml"""  

$exeFile = $args[0]
$configTemplate = $args[1]
$resultantConfig = $args[2]
write-host "Creating configuration file for exemsi, the software which wraps the lite installer in an MSI package.  Parameters:"
write-host "  exe file to wrap - " $exeFile
write-host "  config template - " $configTemplate
write-host "  resultantconfig - " $resultantConfig
" " | set-content -path $resultantConfig
foreach($line in Get-Content $configTemplate) {
    $exportLine = $line
    if ($line -like '*<Executable*') {
        $exportLine = '<Executable FileName="' + $exeFile + '"/>'
    }
    #write-host $exportLine
    $exportLine | add-content -path $resultantConfig
}
return

 

It just reads every line from the template exemsi configuration file, and when it finds the line that has “<Executable” in it, makes a substitution using the file name from the parameter passed in.  Then it spits out the lines to a tailored-to-the-current-release configuration file.

I may tweak this to customize other lines in the configuration file.  For example the version number is one of the things in the configuration file that would vary from release to release and that has its own tag.  So more work will likely need to be done but at least this proves the concept.

The next thing I needed to do was call this from the Apache Ant build.xml file.  Here’s that code (the names have been changed to remove references to proprietary software):

<!-- create MSI (Microsoft Installer) artifacts -->
<exec executable="powershell" >
    <arg line="-noexit"/>
    <arg line="&amp; c:\resources\makemsiwrapperconfig.ps1 .\target\app-123456.exe c:\resources\msiwrapper.xml .\target\msiwrapper.xml"/>
</exec>
<exec executable="c:\resources\MsiWrapperBatch.exe">
    <arg value='config=.\target\msiWrapper.xml'/>
</exec>

It works.  I would hope there are easier solutions than this.  But once I had asked my boss to purchase the exemsi software and spent some time learning it, I was kinda like a dog with a bone and determined to make this solution work.  And hey, I learned something about Powershell and how to call a Powershell script from an Apache Ant build.xml file in the process.

Advertisements

UTC date/time string to Java date in local time zone

mushroom

Reference: https://www.w3.org/TR/NOTE-datetime

Uses joda-time (because yes, I still work in Java 1.6).  There is probably a simpler way.  But this works. This assumes all parts of the string are supplied.

public static Date convertUTCStringToLocalDateTime(String utcDate)
{
    DateTime utcDateTime = new DateTime(utcDate);
    Date localDateTime = utcDateTime.toDate();
    return localDateTime;
}

Specifying headerColors for Actionscript Alert in CSS

spentroseandhip003

 

Simple idea – you want Alert pop-ups in Actionscript to have a header color that is different from the default (which is same color as the background).  The only difficulty comes in knowing the correct syntax, because two colors are necessary.  They can be the same color if you just want a solid color, or two colors if you want one color to fade into the other.  These two colors get converted to an array at run-time.

After trying various combinations with and without quotes, square brackets, squiggly brackets, I finally got the correct syntax.  So just writing it down here to keep track.

mx|Alert
{
dropShadowEnabled: true;
headerColors: blue, blue;
headerHeight: 20;
}

It’s just a comma-delimited list of two colors – no quotes or brackets or anything fancy.  Also the numeric red green blue values may be used instead of color keywords.  The first color in the list is the top of the gradient; the second the bottom.

By the way a low-tech way to get the RGB value of a color is to copy something with that color into Paint, and then use the dropper tool to select the color, then click on “Edit colors”.

 

SQL Server datetime to int

birds

declare @currentDateTime datetime 
declare @timeStampValue int 

set @currentDateTime = SYSDATETIME(); 
print @currentDateTime 
set @timeStampValue = DATEDIFF(second, '1970-01-01', @currentDateTime) 
print @timeStampValue

 
To check the result:

https://www.epochconverter.com/

 

UTC milleseconds example:

 
declare @timeStampValue Bigint  
declare @utcDateTime datetime 

set @utcDateTime = GETUTCDATE(); 
set @timeStampValue = DATEDIFF(SECOND, '1970-01-01', @utcDateTime) 
set @timeStampValue = @timeStampValue * 1000

 

Python clean up old files

leaf

Was learning Python and wrote this script to clean up files.  It’s not awesome.  Has no error handling for when the target file is already open.  Just kinda barfs when that happens.  So it needs work.  This was written in Python 3.

#This script uses a library called path.py.  Get it from here: https://pypi.python.org/pypi/path.py
#Download the Python Wheel file.  At this writing, it is called path.py-8.2.1-py2.py3-none-any.whl.  
#Install using the command: pip install <name of wheel file>.
#You must have Python installed first.  Been a while since I installed it, but here is the official place to 
#get it from: https://www.python.org/downloads/
#Once you have everything installed and the script works, use Windows Task Scheduler to run it, so you don't forget.

import timefrom 
path import path

def main():  
  # Because in python, the \ is an escape character, we need to use two of them in directory specifications  
  paths = ["c:\\jboss\\jboss-eap-5.1\\jboss-as\\server\\default\\tmp","c:\\jboss\\jboss-eap-5.1\\jboss-as\\server\\default\\log"]  
  DAYS = 7  
  time_in_seconds = time.time() - (DAYS * 24 * 60 * 60)  
  removed = 0  
  for p in paths:    
    print (p)    
    d = path(p)      

    for f in d.walk():      
      if f.mtime <= time_in_seconds:        
        print ("Deleting " + f.basename())        
        f.remove()        
        removed += 1     
  print ("Removed " + str(removed) + " files.")
if __name__ == "__main__":  
  main()

SQL Tidbits

creek (2)

Handy little bits.  Have long-since forgotten where I got these little gems from.

Find a constraint by name

Select SysObjects.[Name] As [Constraint Name] ,
 Tab.[Name] as [Table Name],
 Col.[Name] As [Column Name]
From SysObjects Inner Join 
(Select [Name],[ID] From SysObjects) As Tab
On Tab.[ID] = Sysobjects.[Parent_Obj] 
Inner Join sysconstraints On sysconstraints.Constid = Sysobjects.[ID] 
Inner Join SysColumns Col On Col.[ColID] = sysconstraints.[ColID] And Col.[ID] = Tab.[ID]
where sysobjects.name = 'constraint name'
order by [Tab].[Name]

Find foreign-key related tables

SELECT f.name AS ForeignKey,
SCHEMA_NAME(f.SCHEMA_ID) SchemaName,
OBJECT_NAME(f.parent_object_id) AS TableName,
COL_NAME(fc.parent_object_id,fc.parent_column_id) AS ColumnName,
SCHEMA_NAME(o.SCHEMA_ID) ReferenceSchemaName,
OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName,
COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName
FROM sys.foreign_keys AS f
INNER JOIN sys.foreign_key_columns AS fc ON f.OBJECT_ID = fc.constraint_object_id
INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id
where OBJECT_NAME(f.referenced_object_id) = 'table name'
order by TableName, columnname

 

Shrink log files

Use master
declare @dbName varchar(100)
declare @recoveryModel varchar(100)
declare @sqlString varchar(100)
declare @dbId integer
declare @logFileName varchar(100)
declare dbcursor cursor for select name, recovery_model_desc from sys.databases where recovery_model_desc != 'SIMPLE'
open dbcursor
while 1=1
begin 
fetch from dbcursor into @dbName, @recoveryModel 
if @@FETCH_STATUS != 0 break 
print @recoveryModel + ' ' + @dbname + ' setting recovery model to SIMPLE.' 
SET @sqlString = 'ALTER DATABASE [' + @dbname + '] SET RECOVERY SIMPLE' 
EXECUTE (@sqlString) 
end
close dbcursor
deallocate dbcursor 

declare dbcursor2 cursor for 
select name, recovery_model_desc, database_id from sys.databases 
where recovery_model_desc = 'SIMPLE'
open dbcursor2
while 1=1
begin 
fetch from dbcursor2 into @dbName, @recoveryModel, @dbId 
if @@FETCH_STATUS != 0 break
 set @logFileName = (select name from sys.master_files where database_id = @dbId and type = 1) 
print 'Shrinking log file ' + @logFileName + ' for database ' + @dbName 
set @sqlString = 'USE ' + @dbName + '; ' + 'DBCC SHRINKFILE (' + @logFileName + ')' 
execute (@sqlString)
end
close dbcursor2
deallocate dbcursor2

RESTEasy Swagger Maven Cheat Sheet

dragonFly3

Settings.xml file

  • <deploy.env> This parameter is used for many things that have nothing to do with the RESTful API, but it also plays a role in the RESTful API.  It is used to create the URLs that allow us to access Swagger for the documentation and testing of the API.

In the pom.xml file, in the generate-service-docs execution, the additional parameters docBasePath and apiBasePath rely on both the resteasy.service.host and deploy.env parameters to construct URLs used by Swagger.

RESTeasy entries in web.xml file

https://docs.jboss.org/resteasy/docs/1.0.1.GA/userguide/html/Installation_Configuration.html

<listener>

This entry identifies the RESTeasy class that will initialize RESTeasy.

<listener>
<listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>

<servlet>

This entry identifies our class that extends the javax.ws.rs.Application class.  The RestApp identifies the RESTful beans, and some other classes that are used in packaging REST responses, formatting JSON, etc.

<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.ep.ff.web.endpoint.RestApp</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>

 

<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>

<context-param>

Various parameters that control aspects of RESTeasy.  For each the parameter name and value are specified.

<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/rest</param-value>
</context-param>

<context-param>
<param-name>resteasy.jndi.resources</param-name>
<param-value>earname/beanname/local
</param-value>
</context-param>

<context-param>
<param-name>resteasy.role.based.security</param-name>
<param-value>true</param-value>
</context-param>

<security-constraint>

<security-constraint>
<web-resource-collection>
<web-resource-name>REST Resources</web-resource-name>
<url-pattern>/swagger/*</url-pattern>
<url-pattern>/apidocs/*</url-pattern>
<url-pattern>/rest/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>TRACE</http-method>
</web-resource-collection>

<auth-constraint>
<role-name>rolename</role-name>
</auth-constraint>
</security-constraint>

RESTeasy entries in pom files

web

<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>3.0.7.Final</version>
</dependency>

<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
<version>3.0.7.Final</version>
</dependency>

web-ejb

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<executions>
<execution>
<id>generate-service-docs</id>
<phase>generate-resources</phase>
<configuration>
<doclet>com.carma.swagger.doclet.ServiceDoclet</doclet>
<docletArtifact>
<groupId>com.carma</groupId>
<artifactId>swagger-doclet</artifactId>
<version>1.0.4.1</version>
</docletArtifact>
<reportOutputDirectory>output directory</reportOutputDirectory>
<useStandardDocletOptions>false</useStandardDocletOptions>
<additionalparam>-apiVersion 1 -docBasePath path/apidocs -apiBasePath path/rest</additionalparam>
</configuration>
<goals>
<goal>javadoc</goal>
</goals>
</execution>
</executions>
</plugin>

The -apiBasePath is used to resolve the URLs to the services for testing the methods via Swagger.  If the {resteasy.service.host} parameter is not supplied, either in the settings.xml file or on the maven command line, you will not be able to click into the links in Swagger to be able to see the documented API or test the services.

Steps to RESTful-ize an existing bean

  • WEB-INF\web.xml needs the web bean added to the resteasy.jndi.resources <context-param> tag in the <param-value> tag.  Follow the same pattern as the existing web beans. Comma delimit.
  • In RestApp.java, add the web bean in the setClasses() method.
  • In the web bean class itself, fix the @EJB annotations to include the name argument.
  • In the web interface class, add class-level and method-level REST annotations.
    • @GET, @POST, @DELETE, @PUT (usually @GET or @POST)
    • @Path(path name).  Take the method name, and change it to be all lower-case, hyphenated, and use that for the path name.
    • @Produces(MediaType.APPLICATION_JSON), @Consumes(MediaType.APPLICATION-JSON)
  • If the method does not have a @RolesAllowed tag, it must be added and tested.  Otherwise anyone will be able to access the method just by knowing the URL.
  • Handle cyclic references with a @JsonIgnore tag, to prevent StackOverflowError.

 

Swagger

Swagger is a tool that helps to document and test a RESTful API.

https://github.com/swagger-api/swagger-core/wiki/Swagger-Core-RESTEasy-2.X-Project-Setup-1.5

Java Classes

RestApp

CustomJacksonJsonProvider

Allows for customization of the methods used to read and write JSON strings.

JsonMappingExceptionMapper

Creates an exception for errors related to Json mapping.

RestResponseInterceptor