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,TypeThere are a few things that could be improved:
uk701,App
uk702,App
uk703,App
uk704,DB
- I really should create a function rather than having the same piece of code twice.
- [string]::format is probably overkill but I couldn't get it to work otherwise.
- 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.
- Hard coding of paths is a bad idea even if to c:\temp
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.
ReplyDeleteYou could use the Join-Path Commandlet for the [string]::Format
ReplyDelete