Saturday, 13 October 2012

Install and uninstall MSI remotely using PowerShell

In yesterday's post I discussed how to uninstall a msi using powershell in today's post I present a script to remotely install and uninstall msi to a list of servers.

In this project I'm working on at the moment we have two builds: one for the application servers and another for the database servers. As I mentioned yesterday we seem to be applying way too many builds so the whole business of logging on to the various servers (four in total), uninstalling the old build and installing the new build, so I've written this script to automate the whole lot.

Now it's simply a case of copying the zip file with the MSIs and this script to server one, extract them and run the script.

See below for an example server file.
 
Install Script:
 param ($serverfile, $targetdir, $domainname, $username, $password,$db)
 
 if (-not($serverfile) -or -not($targetdir) -or -not($domainname) -or -not($username) -or -not ($password) -or -not($db)) 
 {
   echo 'Script should be called like this:'
   echo 'Install -serverfile servers -targetdir "c:\PSS\" -domainname "pss" -username "pss_svc" -password "notsecure" -db "db instance"'
   exit
 }
 
 #default to c:\temp, this needs to be in the server
 $dest = 'c$\temp\'
 
 #This is really good as it allows us to have some sort of type safety
 $srvs = Import-Csv $serverfile
 
 foreach ($item in $srvs) 
 { 
  if ($item.Type -eq "App" )
  {
     $name = $item.Hostname
     $path = [string]::Format("\\{0}\{1}", $item.HostName,$dest)
 
     New-Item -ItemType directory -Path $path -Force > $null
    
     echo "Copying PSS Deployment.msi to $name"
 
     Copy-Item -Path '.\Deployment.msi' -Destination $path -Force
 
     $wmi = [string]::Format("\\{0}\ROOT\CIMV2:Win32_Product",$item.Hostname)
  
     #check that PSS App it's not already installed.
 
     echo 'Checking for an already installed version of PSS Application'
     
     $app = Get-WmiObject -Class "Win32_Product" -ComputerName "$name" | where-object {$_.Name -match "PSS Application"}
     
     if ($app)
     {    
      $version = $app.Version
      
      echo "PSS Application is already installed. Current Version is $version"     
      echo "Uninstalling $version"
               
      $app.uninstall() > $null
     
     }
     
     echo "Start Install of PSS Application"
 
     $product = [WMIClass]$wmi
     $var = $product.Install("c:\temp\Deployment.msi", "TARGETDIR=$targetdir DOMAINNAME=$domainname USER=$username PASSWORD=$password", $true)
    
 
     if ($var.ReturnValue -ne 0)
     { 
       echo "Error installing PSS Deployment.msi on $name"
       $exit = [string]::Format("exitcode: {0}", $var.ReturnValue)
       echo $exit
     }   
 
     echo "Installed  on $name"
  }  
  elseif ($item.Type -eq "DB" )
  {
     $name = $item.Hostname
     $path = [string]::Format("\\{0}\{1}", $item.HostName,$dest)
 
     New-Item -ItemType directory -Path $path -Force > $null
    
     echo "Copying PSS Database.msi to $name"
 
     Copy-Item -Path '.\Database.msi' -Destination $path -Force
 
     $wmi = [string]::Format("\\{0}\ROOT\CIMV2:Win32_Product",$item.Hostname)
  
     #check that  it's not already installed.
 
     echo 'Checking for an already installed version of PSS Database'
     
     $app = Get-WmiObject -Class "Win32_Product" -ComputerName "$name" | where-object {$_.Name -match "PSS Database"}
     
     if ($app)
     {    
      $version = $app.Version
      
      echo "PSS Database is already installed. Current Version is $version"     
      echo "Uninstalling $version"
               
      $app.uninstall() > $null
     
     }
     
     echo "Start Install PSS Database"
 
     $product = [WMIClass]$wmi 
     $var = $product.Install("c:\temp\Database.msi", "SQLSERVER=$db TARGETDIR=$targetdir DOMAINNAME=$domainname USER=$username PASSWORD=$password", $true)
  
 
     if ($var.ReturnValue -ne 0)
     { 
       echo "Error installing PSS Database.msi on $name"
       $exit = [string]::Format("exitcode: {0}", $var.ReturnValue)
       echo $exit
     }   
 
     echo "Installed  on $name"
 
     }
          
  else
  {
   echo 'Unknown or missing type'
  }
 }

Example serverfile:
HostName,Type 
uk701,App 
uk702,App 
uk703,App 
uk704,DB
There are a few things that could be improved:
  1. I really should create a function rather than having the same piece of code twice.
  2. [string]::format is probably overkill but I couldn't get it to work otherwise.
  3. I should really only run the uninstall if an exitcode of 1638 is received from the install but at the moment the builds are always there so it seemed pointless.
  4. Hard coding of paths is a bad idea even if to c:\temp

2 comments:

  1. Thank you for sharing this experience, I too just found myself baffled by the fact that, as a "scripting language", Powershell functions differently from one manner of execution to the next; a PS instance command versus execution from .ps1 source. This is just more of the same from Windows and its needlessly convoluted permissions paradigm. Thank Cronus that there's still Unix/Linux.

    ReplyDelete
  2. You could use the Join-Path Commandlet for the [string]::Format

    ReplyDelete