Automatically Upgrading MojoPortal Using PowerShell

Posted by Shaun Geisert Monday, October 31, 2011 10:18:00 AM Categories: MojoPortal

After originally creating a Java-based auto-upgrader to handle upgrading my installations of mojoPortal, I had heard about something called PowerShell.  PowerShell is sort of like command prompt on steroids, and it is already integrated into Windows 7. It can manipulate file contents akin to .NET, and otherwise perform administrative tasks with ease.  To me it made more sense to use it rather than a Java app (which would need to be recompiled), since the basic gist of upgrading mojo is to simply copy/paste files to the correct location.  The script also upgrades Form Wizard Pro, Mobile Kit Pro, and Event Calendar Pro, but it could easily be edited to accommodate different scenarios.  Here's what the script does:

  • It times how long the process takes and saves to a log file
  • It saves the machine key and pastes it back into the new web.config file
  • It saves the name of your auth cookie and pastes it back into the new web.config file
  • If an "App_Online.htm" file exists in the root directory, it is renamed to "App_Offline.htm" to tell people app is offline in case they hit the site during upgrade.
  • Checks if core upgrade is even necessary.  If it is, it will go ahead and copy/paste files
  • Checks if site uses Form Wizard Pro
  • Checks if upgrade is even necessary for FWP
  • Upgrades FWP if needed
  • Does the same for Event Calender Pro and Mobile Kit Pro
  • Enables set up in web.config/user.config
  • Changes your app_offline file back to app_online
  • Hit the setup url to execute setup
  • Disables setup in web.config/user/config
  • Hits the main site url to restart the .NET app
  • Adds an entry to the log file denoting the version we upgraded to, how long it took, and on what date we did the upgrade

 

Important: Before Executing A Site Upgrade, Be Sure That Your Source Files Are As You Want Them.  Specifically, be sure to consider the following recommendations.

Note:  Occasionally post-upgrade I have seen errors along the lines of "Type X exists in both ___ and ___".  This error can be resolved by temporarily setting batch="false" on the "compilation" element of the web.config file.

 

Anyways, here's the script (named mojoDoUpgrade.ps1) I use to upgrade my installations of mojoPortal (or download it):

#       MojoRising PowerShell Upgrade Script
# Created by Shaun Geisert on 10/28/2011
#
# This script is provided "as is" without warranty of any kind, either express or implied, including without
# limitation any implied warranties of condition, uninterrupted use, merchantability, fitness for a
#       particular purpose, or non-infringement.  In other words, don't blame me if things go wrong and always
#       makes backups and/or use a #test server before executing the script against a production installation of
#       mojoPortal


# Get log path
$logFilePath = "C:\mojorising\log.txt" #Resolve-Path "log.txt"


Function Upgrade($mojoPath, $mojoUrl)
{
#If (IsOkayToProceed)    #Uncomment if you want to be prompted to confirm upgrade.
#{
# BEGIN USER-DEFINED SECTION - REVIEW/MODIFY ALL VARIABLES IN THIS SECTION
# Get log path
$logFilePath = "C:\mojorising\log.txt" #Resolve-Path "log.txt"


#Get origin paths
$coreOriginPath = "C:\Users\sgeisert\Documents\MojoRelease\*"
$fwpOriginPath = "C:\Users\sgeisert\Documents\MojoReleaseFWP\*"
$ecpOriginPath = "C:\Users\sgeisert\Documents\MojoReleaseECP\*"
$mkpOriginPath = "C:\Users\sgeisert\Documents\MojoReleaseMKP\*"


#Get destination paths, etc.
$destinationPath = $mojoPath #   BE SURE THESE VARIABLES INCLUDE A TRAILING SLASH!!!
$productionUrl = $mojoUrl #   BE SURE THESE VARIABLES INCLUDE A TRAILING SLASH!!!
$setupUrl = [String]::Format("{0}{1}", $mojoUrl, "Setup")


# END USER-DEFINED SECTION.


# BEGIN "CONSTANT" VARIABLES. These should normally remain unchanged for all mojo instances

#Core
$webConfigPath = [String]::Format("{0}{1}", $destinationPath, "Web.config")
$usrConfigPath = [String]::Format("{0}{1}", $destinationPath, "user.config")


$coreSchemaUpgradePath = "Setup\applications\mojoportal-core\SchemaUpgradeScripts\mssql"
$coreTestUpgradeNecessaryOriginPath = [String]::Format("{0}{1}", $coreOriginPath.Replace("*",""), $coreSchemaUpgradePath)
$coreTestUpgradeNecessaryDestPath = [String]::Format("{0}{1}", $destinationPath, $coreSchemaUpgradePath)
$coreIsUpgradeNecessary = "False"

#Form Wizard Pro
$fwpExistsPath = [String]::Format("{0}{1}", $destinationPath, "bin\sts.FormWizard.Business.dll")
$fwpSchemaUpgradePath = "Setup\applications\sts-FormWizard\SchemaUpgradeScripts\mssql"
$fwpTestUpgradeNecessaryOriginPath = [String]::Format("{0}{1}", $fwpOriginPath.Replace("*",""), $fwpSchemaUpgradePath)
$fwpTestUpgradeNecessaryDestPath = [String]::Format("{0}{1}", $destinationPath, $fwpSchemaUpgradePath)
$fwpIsUpgradeNecessary = "False"

#Event Calendar Pro
$ecpExistsPath = [String]::Format("{0}{1}", $destinationPath, "bin\sts.Events.Business.dll")
$ecpSchemaUpgradePath = "Setup\applications\STS_EventCalendar\SchemaUpgradeScripts\mssql"
$ecpTestUpgradeNecessaryOriginPath = [String]::Format("{0}{1}", $ecpOriginPath.Replace("*",""), $ecpSchemaUpgradePath)
$ecpTestUpgradeNecessaryDestPath = [String]::Format("{0}{1}", $destinationPath, $ecpSchemaUpgradePath)
$ecpIsUpgradeNecessary = "False"

#Mobile Kit Pro
$mkpExistsPath = [String]::Format("{0}{1}", $destinationPath, "bin\sts.MobileKit.Web.dll")

$appOnlineName = "App_Online.htm"
$appOnlinePath = [String]::Format("{0}{1}", $destinationPath, $appOnlineName)
$appOfflineName = "App_Offline.htm"
$appOfflinePath = [String]::Format("{0}{1}", $destinationPath, $appOfflineName)

# END "CONSTANT" VARIABLES



# Start timer
$ElapsedTime = [System.Diagnostics.Stopwatch]::StartNew()

# Find/store the existing machinekey attributes from web.config in memory to paste back later (so that you don't have to manually reinsert the machinekey)
$webConfigData = [xml](Get-Content $webConfigPath)
$validationKey = $webConfigData.configuration."system.web".machineKey.validationKey
$decryptionKey = $webConfigData.configuration."system.web".machineKey.decryptionKey


# Find/store the existing auth cookie attribute from web.config in memory to paste back later
$authCookieName = $webConfigData.configuration."system.web".authentication.forms.name


# If an "App_Online.htm" file exists in the root directory, it is renamed to "App_Offline.htm" to tell people app is offline in case they hit the site during upgrade.
if (Test-Path $appOnlinePath) {
Rename-Item $appOnlinePath $appOfflineName
}

#Check if core upgrade is even necessary
if (IsUpgradeNecessary $coreTestUpgradeNecessaryOriginPath $coreTestUpgradeNecessaryDestPath)
{
# Copy/replace core files from predefined source directory to destination.  This is the meat of the script and the part that takes far and away the most time.
DoCopy $coreOriginPath $destinationPath
$coreIsUpgradeNecessary = "True"
}
else
{
Add-Content $logFilePath "`nUpgrade of core files at $destinationPath is unnecessary."
}


# Check if site uses Form Wizard Pro
if (Test-Path $fwpExistsPath) {

#Check if FWP upgrade is even necessary
if (IsUpgradeNecessary $fwpTestUpgradeNecessaryOriginPath $fwpTestUpgradeNecessaryDestPath)
{
# It is, so also upgrade Form Wizard Pro
DoCopy $fwpOriginPath $destinationPath
$fwpIsUpgradeNecessary = "True"
}
else
{
Add-Content $logFilePath "`nUpgrade of FWP files at $destinationPath is unnecessary."
}
}


# Check if site uses Event Calendar Pro
if (Test-Path $ecpExistsPath) {

#Check if ECP upgrade is even necessary
if (IsUpgradeNecessary $ecpTestUpgradeNecessaryOriginPath $ecpTestUpgradeNecessaryDestPath)
{
# it is, so also upgrade Event Calendar Pro
DoCopy $ecpOriginPath $destinationPath
$ecpIsUpgradeNecessary = "True"
}
else
{
Add-Content $logFilePath "`nUpgrade of ECP files at $destinationPath is unnecessary."
}
}


# Check if site uses Mobile Kit Pro
if (Test-Path $mkpExistsPath) {
# it does, so also upgrade Mobile Kit Pro (no big need to check if upgrade is necessary since there's only one dll file to copy over
Copy-Item $mkpOriginPath $destinationPath -recurse
}


# Next we enable set up in both user.config and web.config in anticipation of hitting setup url.
# Consider rewriting to instead parse as XML (eg, using xpath)
(Get-Content $usrConfigPath) | ForEach-Object { $_ -replace '<add key="DisableSetup" value="true" />', '<add key="DisableSetup" value="false" />' } | Set-Content $usrConfigPath


# Enable set up in web.config
(Get-Content $webConfigPath) | ForEach-Object { $_ -replace '<add key="DisableSetup" value="true" />', '<add key="DisableSetup" value="false" />' } | Set-Content $webConfigPath


# Make sure the changes were committed, so wait a sec
Start-Sleep 1

# Then MojoRising changes the forms auth cookie from .mojochangeme to whatever it was prior to the upgrade
$newWebConfigData = [xml](Get-Content $webConfigPath) #Remember, we have to re-retrieve the new contents of web.config!
$newWebConfigData.configuration."system.web".authentication.forms.SetAttribute("name", $authCookieName)

# Replace the machinekey in the new web.config file with the original machinekey
$newWebConfigData.configuration."system.web".machineKey.SetAttribute("validationKey", $validationKey)
$newWebConfigData.configuration."system.web".machineKey.SetAttribute("decryptionKey", $decryptionKey)
$newWebConfigData.Save($webConfigPath)


# If an "App_Offline.htm" file exists in the root directory, it is renamed to "App_Online.htm".
if (Test-Path $appOfflinePath) {
Rename-Item $appOfflinePath $appOnlineName
}


# Now we hit the set up URL for the site: http://www.yoursite.com/setup
[System.Diagnostics.Process]::Start($setupUrl )

# Stop the timer
$elapsedTime = $ElapsedTime.Elapsed.ToString()


# Sleep for 40 seconds to ensure that the setup executed
Start-Sleep 40


# Once the site has upgraded successfully, we disable set up in web.config and user.config.
(Get-Content $usrConfigPath) | ForEach-Object { $_ -replace '<add key="DisableSetup" value="false" />', '<add key="DisableSetup" value="true" />' } | Set-Content $usrConfigPath


# Enable set up in web.config
(Get-Content $webConfigPath) | ForEach-Object { $_ -replace '<add key="DisableSetup" value="false" />', '<add key="DisableSetup" value="true" />' } | Set-Content $webConfigPath


# Finally, we hit the site's main page to restart the .NET app.
[System.Diagnostics.Process]::Start($productionUrl)


# Add an entry to the log file with core version
$coreVersion = GetHighestVersionFromDirectory($coreTestUpgradeNecessaryOriginPath)
$coreVersion = $coreVersion.Replace(".config","")

if ($coreIsUpgradeNecessary -eq "True")
{
Add-Content $logFilePath "`nThe installation of mojoPortal at $destinationPath was upgraded in $elapsedTime to version $coreVersion on the date $(Get-Date)"
}

if ($fwpIsUpgradeNecessary -eq "True")
{
Add-Content $logFilePath "`nThe installation of Form Wizard Pro at $destinationPath was upgraded to the latest version on the date $(Get-Date)"
}

if ($ecpIsUpgradeNecessary -eq "True")
{
Add-Content $logFilePath "`nThe installation of Event Calendar Pro at $destinationPath was upgraded to the latest version on the date $(Get-Date)"
}

#}
}


#Function used to see if an upgrade is necessary
Function IsUpgradeNecessary($originPath, $destinationPath)
{
# Get version of origin folder
$originVersion = GetHighestVersionFromDirectory($originPath)

# Get version of destination folder
$destinationVersion = GetHighestVersionFromDirectory($destinationPath)

# Compare versions
$comparison = $originVersion.CompareTo($destinationVersion)

# If version of origin folder is higher than version of destination folder, return true
if ($comparison -eq 1)
{
#Add-Content $logFilePath "`nIsNecessary - OriginVersion: $originVersion | DestinationVersion: $destinationVersion | Comparison: $comparison"
Return $true
}
else # Otherwise upgrade isn't necessary
{
#Add-Content $logFilePath "`nIsNOTNecessary - OriginVersion: $originVersion | DestinationVersion: $destinationVersion | Comparison: $comparison"
Return $false
}


}


#Function to recursively copy files/folders
Function DoCopy($from, $to)
{
try {
Copy-Item $from $to -recurse
#Add-Content $logFilePath "`nThe copy operation from $from to $to successfully completed at $(Get-Date)"
Return $true
}
catch [System.Exception] {
Add-Content $logFilePath "`nCopy Error:$_.Exception.ToString() at $(Get-Date)"
Return $false
}
}


#Function used to capture highest version from a provided directory
Function GetHighestVersionFromDirectory($mojoPath)
{
#Get items in directory
$items = Get-ChildItem -Path $mojoPath


#Sort items
[Array]::Sort([array]$items)

#Get/return last item in array
Return $items[-1].Name
}


Function IsOkayToProceed()
{
param([string]$title="Confirm mojoPortal Upgrade",[string]$message="Are you sure you want to upgrade your installation(s) of mojoPortal?")
$choiceYes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Answer Yes."
$choiceNo = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Answer No."
$options = [System.Management.Automation.Host.ChoiceDescription[]]($choiceYes, $choiceNo)
$result = $host.ui.PromptForChoice($title, $message, $options, 1)
switch ($result)
    {
0
{
Return $true
}
 
1
{
Return $false
}
}
}

 

And here's the batch file (named initiateMojoUpgrade.bat) I use to execute the above script (so that you could theoretically upgrade all of your mojoPortal sites at the same time):

:: This calls PowerShell script, passing in two parameters (the destination of where to upgrade mojo and the site's root url.
:: BE SURE TO INCLUDE TRAILING SLASHES!!

::  Directions.
::  1. First edit this file by adding in all of your mojo Sites.  Eg:
:: powershell ". .\mojoDoUpgrade.ps1; Upgrade -mojoPath 'YOUR_DESTINATION\' -mojoUrl 'YOUR_SITE_URL/'".  Remove the double colons to uncomment each line.
::  2. Next edit the user-defined variables in mojoDoUpgrade.ps1 and save.  Specifically, you'll need to edit the paths which specify from where you'll be copying the mojoPortal files.  Ie, $coreOriginPath, $fwpOriginPath, $ecpOriginPath, $mkpOriginPath
::  3. Finally, save and double-click this file to begin the upgrade process.  I highly recommend executing the script against a test server first to make sure it functions correctly in your environment.

:: Site #1
powershell ". .\mojoDoUpgrade.ps1; Upgrade -mojoPath '\\wsnet\cwis53\WWWROOT\mojo\' -mojoUrl 'http://aac.colostate.edu/'"

:: Site #2
powershell ". .\mojoDoUpgrade.ps1; Upgrade -mojoPath '\\wsnet\cwis165\WWWROOT\mojo\' -mojoUrl 'http://apacc.colostate.edu/'"

:: etc etc

Finally, the script utilizes a log file (log.txt).  Here's an example of the output:

The installation of mojoPortal at \\wsnetdev\cwis463\WWWROOT\ was upgraded in 00:01:38.8141665 on the date 10/27/2011 07:52:52
The installation of mojoPortal at \\wsnet\cwis328\WWWROOT\mojo\ was upgraded in 00:01:57.0460661 on the date 10/27/2011 08:34:10
The installation of mojoPortal at \\wsnet\cwis259\WWWROOT\mojo\ was upgraded in 00:02:13.1648451 on the date 10/27/2011 08:43:16
The installation of mojoPortal at \\wsnet\cwis466\WWWROOT\veterans\ was upgraded in 00:01:40.3943152 to version 2.3.7.0 on the date 10/29/2011 23:51:12
The installation of Form Wizard Pro at \\wsnet\cwis466\WWWROOT\veterans\ was upgraded to the latest version on the date 10/29/2011 23:51:12
The installation of Event Calendar Pro at \\wsnet\cwis466\WWWROOT\veterans\ was upgraded to the latest version on the date 10/29/2011 23:51:12
Upgrade of core files at \\wsnet\cwis199\WWWROOT\mojo\ is unnecessary.
Upgrade of FWP files at \\wsnet\cwis199\WWWROOT\mojo\ is unnecessary.

Comments are closed on this post.