Skip to content

Commit

Permalink
Merge pull request #390 from chocolatey/core_extension
Browse files Browse the repository at this point in the history
Core extension
  • Loading branch information
gep13 authored Nov 12, 2016
2 parents 84dde67 + 1cab643 commit 02d16b3
Show file tree
Hide file tree
Showing 12 changed files with 437 additions and 120 deletions.
13 changes: 13 additions & 0 deletions extensions/chocolatey-core.extension/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# CHANGELOG

## Version 1.0

- Merged `mm-choco.extension`.
- Merged `chocolatey-uninstall.extension`.
- Added `Get-PackageCacheLocation`
- Added `CHANGELOG.md` and `README.md`.
- Refactoring and more documentation.

## Version 0.1.3

- `Get-WebContent` - Download file with choco internals.
49 changes: 49 additions & 0 deletions extensions/chocolatey-core.extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# chocolatey-core.extension


This is the Powershell module that extends Chocolatey with new functions.


## Installation

Install via chocolatey: `choco install chocolatey-core.extension`.

The module is usually automatically installed as a dependency.


## Usage

To create a package that uses a extension function add the following to the `nuspec` specification:

<dependencies>
<dependency id="chocolatey-core.extension" version="1.0" />
</dependencies>

**NOTE**: Make sure you use adequate _minimum_ version.

To test the functions you can import the module directly or via the `chocolateyInstaller.psm1` module:

PS> import-module $Env:ChocolateyInstall\helpers\chocolateyInstaller.psm1
PS> import-module $Env:ChocolateyInstall\extensions\chocolatey-core\*.psm1

You can now test any of the functions:

PS> Get-AppInstallLocation choco -Verbose

VERBOSE: Trying local and machine (x32 & x64) Uninstall keys
VERBOSE: Trying Program Files with 2 levels depth
VERBOSE: Trying PATH
C:\ProgramData\chocolatey\bin

Keep in mind that function may work only in the context of the `chocolateyInstaller.ps1`.

To get the list of functions, load the module directly and invoke the following command:

Get-Command -Module chocolatey-core

To get the help for the specific function use `man`:

man Get-UninstallRegistryKey



Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,29 @@
<!-- Do not remove this test for UTF-8: if “Ω” doesn’t appear as greek uppercase omega letter enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<!-- == PACKAGE SPECIFIC SECTION == -->
<!-- This section is about this package, although id and version have ties back to the software -->
<id>chocolatey-core.extension</id>
<version>0.1.3</version>
<packageSourceUrl>https://github.com/chocolatey/chocolatey-coreteampackages</packageSourceUrl>
<!-- owners is a poor name for maintainers of the package. It sticks around by this name for compatibility reasons. It basically means you. -->
<owners>ebugusey,chocolatey</owners>
<!-- ============================== -->

<!-- == SOFTWARE SPECIFIC SECTION == -->
<!-- This section is about the software itself -->
<version>1.0</version>
<title>Chocolatey Core Extensions</title>
<authors>ebugusey</authors>
<!-- projectUrl is required for the community feed -->
<summary>Helper functions extending core choco functionality</summary>
<authors>chocolatey</authors>
<owners>chocolatey</owners>
<description>
This package provides helper functions installed as a Chocolatey extension.
These functions may be used in Chocolatey install/uninstall scripts by declaring this package a dependency in your package's nuspec.
</description>
<tags>chocolatey core extension admin</tags>
<projectUrl>https://github.com/chocolatey/chocolatey-coreteampackages</projectUrl>
<!--<iconUrl>http://cdn.rawgit.com/__REPLACE_YOUR_REPO__/master/icons/chocolatey-common.extension.png</iconUrl>-->
<!--<iconUrl>http://cdn.rawgit.com/chocolatey-coreteampackages/master/icons/chocolatey-common.extension.png</iconUrl>-->
<copyright>© 2016 Chocolatey Core Team Package Contributors</copyright>
<licenseUrl>https://github.com/chocolatey/chocolatey-coreteampackages/blob/master/LICENSE.md</licenseUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<projectSourceUrl>https://github.com/chocolatey/chocolatey-coreteampackages</projectSourceUrl>
<!--<docsUrl>At what url are the software docs located?</docsUrl>-->
<!--<mailingListUrl></mailingListUrl>-->
<packageSourceUrl>https://github.com/chocolatey/chocolatey-coreteampackages/tree/master/chocolatey-core.extension</packageSourceUrl>
<docsUrl>https://github.com/chocolatey/chocolatey-coreteampackages/tree/master/extensions/chocolatey-core.extension/README.md</docsUrl>
<bugTrackerUrl>https://github.com/chocolatey/chocolatey-coreteampackages/issues</bugTrackerUrl>
<tags>chocolatey core extension admin</tags>
<summary>Helper functions extending core choco functionality</summary>
<description>
This package provides helper functions installed as a Chocolatey extension.
These functions may be used in Chocolatey install/uninstall scripts by declaring this package a dependency in your package's nuspec.

&lt;`dependencies`&gt;
&amp;nbsp;&amp;nbsp;&lt;`dependency id="chocolatey-core.extension" /`&gt;
&lt;`/dependencies`&gt;

### Functions

`Get-WebContent` -- Download file with choco internals.

Example:
`$s = Get-WebContent "http://example.com"`

Example:
`$opts = @{ Headers = @{ Referer = 'http://google.com' } }`
`$s = Get-WebContent -url "http://example.com" -options $opts`
</description>
<!-- <releaseNotes>__REPLACE_OR_REMOVE__MarkDown_Okay</releaseNotes> -->
</metadata>
<files>
<!-- this section controls what actually gets packaged into the Chocolatey package -->
<releaseNotes>https://github.com/chocolatey/chocolatey-coreteampackages/tree/master/chocolatey-core.extension/CHANGELOG.md</releaseNotes>
</metadata>
<files>
<file src="extensions\**" target="extensions" />
<!--Building from Linux? You may need this instead: <file src="tools/**" target="tools" />-->
</files>
</package>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<#
.SYNOPSIS
Get application install location
.DESCRIPTION
Function tries to find install location in multiple places. It returns $null if all fail. The following
locations are tried:
- local and machine (x32 & x64) various Uninstall keys
- x32 & x64 Program Files up to the 2nd level of depth
- native commands available via PATH
- locale and machine registry key SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
Use Verbose parameter to see which of the above locations was used for the result, if any.
.EXAMPLE
PS> Get-AppInstallLocation choco
Returns the install location of the application 'choco'.
.OUTPUTS
[String] or $null
#>
function Get-AppInstallLocation {
[CmdletBinding()]
param(
# Regular expression pattern
[ValidateNotNullOrEmpty()]
[string] $AppNamePattern
)

function strip($path) { if ($path.EndsWith('\')) { return $path -replace '.$' } else { $path } }

$ErrorActionPreference = "SilentlyContinue"

Write-Verbose "Trying local and machine (x32 & x64) Uninstall keys"
[array] $key = Get-UninstallRegistryKey $AppNamePattern
if ($key.Count -eq 1) {
Write-Verbose "Trying Uninstall key property 'InstallLocation'"
$location = $key.InstallLocation
if ($location -and (Test-Path $location)) { return strip $location }

Write-Verbose "Trying Uninstall key property 'UninstallString'"
$location = $key.UninstallString.Replace('"', '')
if ($location) { $location = Split-Path $location }
if ($location -and (Test-Path $location)) { return strip $location }

Write-Verbose "Trying Uninstall key property 'DisplayIcon'"
$location = $key.DisplayIcon
if ($location) { $location = Split-Path $location }
if ($location -and (Test-Path $location)) { return strip $location }
} else { Write-Verbose "Found $($key.Count) keys, aborting this method" }

$dirs = $Env:ProgramFiles, "$Env:ProgramFiles\*\*"
if (Get-ProcessorBits 64) { $dirs += ${ENV:ProgramFiles(x86)}, "${ENV:ProgramFiles(x86)}\*\*" }
Write-Verbose "Trying Program Files with 2 levels depth: $dirs"
$location = (ls $dirs | ? {$_.PsIsContainer}) -match $AppNamePattern | select -First 1 | % {$_.FullName}
if ($location -and (Test-Path $location)) { return strip $location }

Write-Verbose "Trying native commands on PATH"
$location = (Get-Command -CommandType Application) -match $AppNamePattern | select -First 1 | % { Split-Path $_.Source }
if ($location -and (Test-Path $location)) { return strip $location }

$appPaths = "\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths"
Write-Verbose "Trying Registry: $appPaths"
$location = (ls "HKCU:\$appPaths", "HKLM:\$appPaths") -match $AppNamePattern | select -First 1
if ($location) { $location = Split-Path $location }
if ($location -and (Test-Path $location)) { return strip $location }

Write-Verbose "No location found"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<#
.SYNOPSIS
Get temporary location for the package based on its name and version.

.DESCRIPTION
The function returns package cache directory within $Env:TEMP. It will not create the directory
if it doesn't exist.

This function is useful when you have to obtain the file using `Get-ChocolateyWebFile` in order
to perform certain installation steps that other helpers can't do.

.EXAMPLE
Get-PackageCacheLocation

.OUTPUTS
[String]

.LINKS
Get-ChocolateyWebFile
#>
function Get-PackageCacheLocation {
[CmdletBinding()]
param (
# Name of the package, by default $Env:ChocolateyPackageName
[string] $Name = $Env:ChocolateyPackageName,
# Version of the package, by default $Env:ChocolateyPackageVersion
[string] $Version = $Env:ChocolateyPackageVersion
)

if (!$Name) { Write-Warning 'Environment variable $Env:ChocolateyPackageName is not set' }
$res = Join-Path $Env:TEMP $Name

if (!$Version) { Write-Warning 'Environment variable $Env:ChocolateyPackageVersion is not set' }
$res = Join-Path $packageTemp $Version

$res
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<#
.SYNOPSIS
Parses parameters of the package

.EXAMPLE
Get-PackageParameters "/Shortcut /InstallDir:'c:\program files\xyz' /NoStartup" | set r
if ($r.Shortcut) {... }
Write-Host $r.InstallDir

.OUTPUTS
[HashTable]
#>
function Get-PackageParameters([string] $Parameters = $Env:ChocolateyPackageParameters) {
$res = @{}
$re = "\/([a-zA-Z]+)(:([`"'])?([a-zA-Z0-9- _\\:\.]+)([`"'])?)?"
$results = $Parameters | sls $re -AllMatches | % Matches
foreach ($m in $results) {
$a = $m.Value -split ':'
$opt = $a[0].Substring(1); $val = $a[1..100]
if ($val -match '^(".+")|(''.+'')$') {$val = $val -replace '^.|.$'}
$res[ $opt ] = if ($val) { $val } else { $true }
}
$res
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<#
.SYNOPSIS
Retrieve registry key(s) for system-installed applications from an exact or wildcard search.

.DESCRIPTION
This function will attempt to retrieve a matching registry key for an already installed application,
usually to be used with a chocolateyUninstall.ps1 automation script.

The function also prevents `Get-ItemProperty` from failing when handling wrongly encoded registry keys.

.PARAMETER SoftwareName
Part or all of the Display Name as you see it in Programs and Features.
It should be enough to be unique.

If the display name contains a version number, such as "Launchy (2.5)", it is recommended you use a
fuzzy search `"Launchy (*)"` (the wildcard `*`) so if Launchy auto-updates or is updated outside
of chocolatey, the uninstall script will not fail.

Take care not to abuse fuzzy/glob pattern searches. Be conscious of programs that may have shared
or common root words to prevent overmatching. For example, "SketchUp*" would match two keys with software
names "SketchUp 2016" and "SketchUp Viewer" that are different programs released by the same company.

.INPUTS
System.String

.OUTPUTS
PSCustomObject

.EXAMPLE
[array]$key = Get-UninstallRegistryKey -SoftwareName "VLC media player"
$key.UninstallString

Exact match: software name in Programs and Features is "VLC media player"

.EXAMPLE
[array]$key = Get-UninstallRegistryKey -SoftwareName "Gpg4win (*)"
$key.UninstallString

Version match: software name is "Gpg4Win (2.3.0)"

.EXAMPLE
[array]$key = Get-UninstallRegistryKey -SoftwareName "SketchUp [0-9]*"
$key.UninstallString

Version match: software name is "SketchUp 2016"
Note that the similar software name "SketchUp Viewer" would not be matched.

.LINK
Uninstall-ChocolateyPackage
#>
function Get-UninstallRegistryKey {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[ValidateNotNullOrEmpty()]
[string] $SoftwareName
)
Write-Debug "Running 'Get-UninstallRegistryKey' for `'$env:ChocolateyPackageName`' with SoftwareName:`'$SoftwareName`'";

$ErrorActionPreference = 'Stop'
$local_key = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
$machine_key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
$machine_key6432 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'

Write-Verbose "Retrieving all uninstall registry keys"
[array]$keys = Get-ChildItem -Path @($machine_key6432, $machine_key, $local_key) -ea 0
Write-Debug "Registry uninstall keys on system: $($keys.Count)"

Write-Debug "Error handling check: `'Get-ItemProperty`' fails if a registry key is encoded incorrectly."
[int]$maxAttempts = $keys.Count
for ([int]$attempt = 1; $attempt -le $maxAttempts; $attempt++)
{
$success = $false

try {
[array]$foundKey = Get-ItemProperty -Path $keys.PsPath -ea 0 | ? { $_.DisplayName -like $SoftwareName }
$success = $true
} catch {
Write-Debug "Found bad key."
foreach ($key in $keys){ try{ Get-ItemProperty $key.PsPath > $null } catch { $badKey = $key.PsPath }}
Write-Verbose "Skipping bad key: $badKey"
[array]$keys = $keys | ? { $badKey -NotContains $_.PsPath }
}

if ($success) { break; }
if ($attempt -eq 10) {
Write-Warning "Found more than 10 bad registry keys. Run command again with `'--verbose --debug`' for more info."
Write-Debug "Each key searched should correspond to an installed program. It is very unlikely to have more than a few programs with incorrectly encoded keys, if any at all. This may be indicative of one or more corrupted registry branches."
}
}

Write-Debug "Found $($foundKey.Count) uninstall registry key(s) with SoftwareName:`'$SoftwareName`'";
return $foundKey
}
Loading

0 comments on commit 02d16b3

Please sign in to comment.