Part 2c: Creating a nupkg to distribute an installable package of an MSI


2013-09-25: Major updates in regards to the script. Awaiting integration into chocolatey.

 
Please refer to Building Packages to form the proper template before continuing.


 

Create the Package config:

cd %chocolateyinstall%\chocolateytemplates\_templates
warmup chocolatey desktoprestore
cd desktoprestore

Edit the package config:
You will edit the nuspec file, which is generally the same as the nuget nuspec specifications, except for the additional “source” parameter.
Beware of common mistakes.

cd %chocolateyinstall%\chocolateytemplates\_templates\desktoprestore
notepad desktoprestore.nuspec

A few things should already be specified, but we will modify a few other things.

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <id>desktoprestore</id>
    <title>Desktop Icon Save and Restore</title>
    <version>1.6.3</version>
    <authors>Jamie O'Connell</authors>
    <owners>Matt Brown</owners>
    <summary>This tiny application operates as a Shell extension.  It records the layout and positions of icons and programs on the Windows Desktop, and permits restoration of the layout.</summary>
    <description>Desktop Icon Save and Restore</description>
    <projectUrl>http://www.midiox.com/index.htm?http://midiox.com/desktoprestore.htm</projectUrl>
    <tags>midiox desktop restore extension icon position save</tags>
    <copyright>WTFPL</copyright>
    <licenseUrl>http://www.wtfpl.net/</licenseUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <!--<iconUrl>https://raw.github.com/test repo/master/desktoprestore/desktoprestore.png</iconUrl>-->
    <!--<dependencies>
      <dependency id="" version="" />
    </dependencies>-->
    <releaseNotes>This chocolatey package is configured to install Desktop Icon Save and Restore using the Windows Installer engine in silent mode.</releaseNotes>
  </metadata>
  <files>
    <file src="tools\**" target="tools" />
    <!--<file src="content\**" target="content" />-->
  </files>
</package>

Create the installation and uninstallation scripts:

Edit the powershell install script:
The chocolateyInstall.ps1 powershell script is what’s executed by chocolatey when an install occurs.
The script helper reference will assist you when dealing with the chocolateyInstall.ps1 powershell script.

cd %chocolateyinstall%\chocolateytemplates\_templates\desktoprestore
notepad .\tools\chocolateyInstall.ps1

Make note of some of the interesting things:
1) This is an installable application.
2) It is an MSI.
3) This MSI is a Windows Installer package and is installable.

$packageName = 'desktoprestore'
$installerType = 'MSI'
$url = 'http://midiox.com/zip/DeskInstU.msi'
$url64 = 'http://midiox.com/zip/DeskInst64.msi'
$silentArgs = '/q'
$validExitCodes = @(0)
Install-ChocolateyPackage "$packageName" "$installerType" "$silentArgs" "$url" "$url64"  -validExitCodes $validExitCodes

Edit the powershell uninstall script:
Although not contained in the template, in order to allow users to utilize the `chocolatey uninstall` command for a package, you must include a `chocolateyUninstall.ps1` script within the package.

Due to the way the 32-bit virtualization works on 64-bit Windows systems, there are, in essence, two registries, one for 32-bit processes, and one for 64-bit processes. In order to take care of this problem, I have modified a script I located on the Internet for querying of the registry and included it within the chocolateyUninstall.ps1.

You can utilize it within the chocolateyUninstall.ps1 script:

cd %chocolateyinstall%\chocolateytemplates\_templates\desktoprestore
notepad .\tools\chocolateyUninstall.ps1

There are two helper scripts that are included (as of August), but aren’t really rolled into the main parts of code yet: `Uninstall-ChocolateyPackage.ps1` and `UnInstall-ChocolateyZipPackage.ps1`. So, do not use them (yet).

$global:debug = $false

Function GetUninstallString([string]$computername, $displayname)
{
	try {
		#first, we'll grab a collection of key names below HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall for 64 bit
		#we'll search for the DisplayName...
		#if found, we'll query for the UninstallString and we'll stop looking
		write-host "Getting UninstallString.  This may take a moment..." -backgroundcolor "DarkYellow"

		write-host "Enumerating 64-bit HKLM keys (or 32-bit keys if the host process is 32-bit)..." -backgroundcolor "DarkYellow"
		$keynames = GetValueFromRegistryThruWMI "enumkey" "64" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" "" 
		foreach ( $key in $keynames) {
			$dname = GetValueFromRegistryThruWMI "GetStringValue" "64" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "DisplayName"
			if ( $dname -ne $false -and $dname.contains($displayname) ) {
				$uninstallstring = GetValueFromRegistryThruWMI "GetStringValue" "64" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "UninstallString"
				write-host "Got it... $uninstallstring" -backgroundcolor "darkgreen"
				write-host "Passing it to the chocolatey gods for sacrifice..." -backgroundcolor "darkgreen"
				return $uninstallstring 
			}
		}
		#then, we'll grab a collection of key names below HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall for 32 bit
		#we'll search for the DisplayName... if found, we'll query for the UninstallString and we'll stop looking
		write-host "Enumerating 32-bit HKLM keys..." -backgroundcolor "DarkYellow"
		write-host "Don't you just wish you had installed that 64-bit version now? C'mon! Quantum computing is almost here!" -backgroundcolor "DarkYellow"
		$keynames = GetValueFromRegistryThruWMI "enumkey" "32" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" "" 
		foreach ( $key in $keynames) {
			$dname = GetValueFromRegistryThruWMI "GetStringValue" "32" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "DisplayName"
			if ( $dname -ne $false -and $dname.contains($displayname) ) {
				$uninstallstring = GetValueFromRegistryThruWMI "GetStringValue" "32" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "UninstallString"
				write-host "Got it... $uninstallstring" -backgroundcolor "darkgreen"
				write-host "Passing it to the chocolatey gods for sacrifice..." -backgroundcolor "darkgreen"
				return $uninstallstring 
			}
		}
		
		#then, we'll grab a collection of key names below HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall for 64 bit
		#we'll search for the DisplayName... if found, we'll query for the UninstallString and we'll stop looking
		write-host "Enumerating 64-bit HKCU keys (or 32-bit keys if the host process is 32-bit)" -backgroundcolor "DarkYellow"
		write-host "What great times we're having, huh?" -backgroundcolor "DarkYellow"
		$keynames = GetValueFromRegistryThruWMI "enumkey" "64" "localhost" "HKCU" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" "" 
		foreach ( $key in $keynames) {
			$dname = GetValueFromRegistryThruWMI "GetStringValue" "64" "localhost" "HKCU" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "DisplayName"
			if ( $dname -ne $false -and $dname.contains($displayname) ) {
				$uninstallstring = GetValueFromRegistryThruWMI "GetStringValue" "64" "localhost" "HKcU" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "UninstallString"
				write-host "Got it... $uninstallstring" -backgroundcolor "darkgreen"
				write-host "Passing it to the chocolatey gods for sacrifice..." -backgroundcolor "darkgreen"
				return $uninstallstring 
			}
		}
		
		#then, we'll grab a collection of key names below HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall for 32 bit
		#we'll search for the DisplayName... if found, we'll query for the UninstallString and we'll stop looking
		write-host "Enumerating 32-bit HKCU keys " -backgroundcolor "DarkYellow"
		write-host "The pain is almost over." -backgroundcolor "DarkYellow"
		$keynames = GetValueFromRegistryThruWMI "enumkey" "32" "localhost" "HKCU" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" "" 
		foreach ( $key in $keynames) {
			$dname = GetValueFromRegistryThruWMI "GetStringValue" "32" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "DisplayName"
			if ( $dname -ne $false -and $dname.contains($displayname) ) {
				$uninstallstring = GetValueFromRegistryThruWMI "GetStringValue" "32" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "UninstallString"
				write-host "Got it... $uninstallstring" -backgroundcolor "darkgreen"
				write-host "Passing it to the chocolatey gods for sacrifice..." -backgroundcolor "darkgreen"
				return $uninstallstring 
			}
		}
		throw "Couldn't find $displayname as a DIsplayName or couldn't find the DisplayName's UninstallString below Uninstall."
	}
	catch {
		return $error[0]
	}
}


Function GetValueFromRegistryThruWMI([string]$stdregprovmethod, $providerarchtoget, [string]$computername, $regtree, $regkey, $value)
{
	try{
		$stdregprovmethod = $stdregprovmethod.tostring().tolower()

		#these are all the available methods for stdregprov: http://msdn.microsoft.com/en-us/library/aa393664%28v=vs.85%29.aspx
		$supportedstdregprovmethods = 'enumkey','enumvalues','getstringvalue'
		#$allstdregprovmethods  = 'checkaccess','createkey','deletekey','deletevalue','enumkey','enumvalues','getbinaryvalue','getdwordvalue','getexpandedstringvalue','getmultistringvalue','getstringvalue','setbinaryvalue','setdwordvalue','setexpandedstringvalue','setmultistringvalue','setstringvalue','#these are not supported in windows version 5.x.x (<=2003/xp)','getqwordvalue','getsecuritydescriptor','setqwordvalue','setsecuritydescriptor'
		if ($debug) {write-host checking for $stdregprovmethod within $supportedstdregprovmethods}
		if ( $supportedstdregprovmethods -notcontains $stdregprovmethod ) {
			write-host You have provided a StrRegProv Method that is not supported yet.
			write-host enumkey will be used instead.
			$stdregprovmethod = 'enumkey'
		}
		else {
			
		}
		switch ($regtree)
		{
			#constant uints for registry trees
			HKCR { $regtree = "&h80000000" }
			HKCU { $regtree = "&h80000001" }
			HKLM { $regtree = "&h80000002" }
			HKU { $regtree = "&h80000003" }
			#more obscure trees: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724836%28v=vs.85%29.aspx
			HKPerformanceData { $regtree = "&h80000004" } # http://msdn.microsoft.com/en-us/library/windows/desktop/aa373219%28v=vs.85%29.aspx
			HKCurrentConfig { $regtree = "&h80000005" }
			HKCULocalSettings { $regtree = "&h80000007" }
			default { $regtree = "&h80000002" }
		}

		$objNamedValueSet = New-Object -COM "WbemScripting.SWbemNamedValueSet"
		
		if ( $providerarchtoget -eq 32 ) {
			if ( [IntPtr]::size -eq 8 -and (test-path env:\PROCESSOR_ARCHITEW6432) ) {
				if ($debug) { write-host process arch is 64-bit, querying 32-bit provider as requested. }
				$objNamedValueSet.Add("__ProviderArchitecture", 32) | Out-Null
			}
			elseif ( [IntPtr]::size -eq 4 ) {
				if ($debug) { write-host process arch is 32-bit, querying 32-bit provider. }
				$objNamedValueSet.Add("__ProviderArchitecture", 32) | Out-Null
			}
		}
		elseif ( $providerarchtoget -eq 64 ) {
			if ( [IntPtr]::size -eq 8 -and (test-path env:\PROCESSOR_ARCHITEW6432) ) {
				if ($debug) { write-host process arch is 64-bit, querying 64-bit provider as requested. }
				$objNamedValueSet.Add("__ProviderArchitecture", 64) | Out-Null
			}
			elseif ( [IntPtr]::size -eq 4 ) {
				if ($debug) { write-host process arch is 32-bit, querying 64-bit provider. }
				$objNamedValueSet.Add("__ProviderArchitecture", 64) | Out-Null
			}
		}
		else {
			if ($debug) { write-host You have requested an architecture that is not handled.  Please provide "32" or "64" as your first parameter. }
			return
		}
		
		$objLocator = New-Object -COM "Wbemscripting.SWbemLocator"
		$objServices = $objLocator.ConnectServer($computername,"root\default","","","","","",$objNamedValueSet)
		$objStdRegProv = $objServices.Get("StdRegProv")
		
		$Inparams = ($objStdRegProv.Methods_ | where {$_.name -eq $stdregprovmethod}).InParameters.SpawnInstance_()
		
	
		if ($stdregprovmethod -eq "getstringvalue") {
			if ($debug) { write-host getting string value $regtree $regkey : $value }
			($Inparams.Properties_ | where {$_.name -eq "Hdefkey"}).Value = $regtree
			($Inparams.Properties_ | where {$_.name -eq "Ssubkeyname"}).Value = $regkey
			($Inparams.Properties_ | where {$_.name -eq "Svaluename"}).Value = $value
			
			$Outparams = $objStdRegProv.ExecMethod_("GetStringValue", $Inparams, "", $objNamedValueSet)
		
			if (($Outparams.Properties_ | where {$_.name -eq "ReturnValue"}).Value -eq 0) {
				$result = ($Outparams.Properties_ | where {$_.name -eq "sValue"}).Value
				if ($debug) { $result }
				return $result
			}
			else{
				return $false
			}
		}
		elseif ($stdregprovmethod -eq "enumkey") {
			if ($debug) { write-host enuming keys below $regtree : $regkey }

			($Inparams.Properties_ | where {$_.name -eq "Hdefkey"}).Value = $regtree
			($Inparams.Properties_ | where {$_.name -eq "Ssubkeyname"}).Value = $regkey
			
			$Outparams = $objStdRegProv.ExecMethod_("EnumKey", $Inparams, "", $objNamedValueSet)
			
			if (($Outparams.Properties_ | where {$_.name -eq "ReturnValue"}).Value -eq 0) {
				$result = ($Outparams.Properties_ | where {$_.name -eq "sNames"}).Value
				return $result
			}
			else{
				return $false
			}
		}
	}
	catch {
		$error[0]
	}
}

$packageName = 'desktoprestore'
$fileType = 'MSI'
$silentArgs = '/quiet /promptrestart' # a quick google turned this up
$filePath = ''
 
#not ready as of September 9th, 2013:
#Uninstall-ChocolateyPackage $packageName $fileType $silentArgs $filePath

$filePath = GetUninstallString localhost "Desktop Restore"
& "$filePath" $silentArgs

Build the package:
All packages are a nuget package (nupkg). Chocolatey includes a batch to build nuget packages:

cd %chocolateyinstall%\chocolateytemplates\_templates\desktoprestore
cpack

Test the package install:
Without getting too complex with nuget.server asp.net instances, you can simply use any directory that is accessible (including anything accessible via a device redirector, like CIFS, NFS, etc):

mkdir %systemdrive%\nupkgs
cd %chocolateyinstall%\chocolateytemplates\_templates\desktoprestore
copy desktoprestore.*.nupkg %systemdrive%\nupkgs\
cd %userprofile%
chocolatey list -source '%systemdrive%\nupkgs'
chocolatey search icon -source '%systemdrive%\nupkgs'
chocolatey search icon desktop -source '%systemdrive%\nupkgs'
pause
chocolatey -debug install desktoprestore -source '%systemdrive%\nupkgs'
pause
chocolatey version all -lo

Test the package uninstall:

chocolatey version all -lo
chocolatey -debug uninstall desktoprestore

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: