Downloads:

10,555

Downloads of v 2019.5.22:

2,404

Last Update:

5/22/2019

Package Maintainer(s):

Software Author(s):

  • Miodrag Milić

Tags:

admin powershell module package chocolatey

Chocolatey Automatic Package Updater Module

2019.5.22 | Updated: 5/22/2019

Downloads:

10,555

Downloads of v 2019.5.22:

2,404

Maintainer(s):

Software Author(s):

  • Miodrag Milić

Chocolatey Automatic Package Updater Module 2019.5.22

This Package Contains an Exempted Check

1 Test Passing and 1 Exempted Test


Validation Testing Passed


Verification Testing Exempt:

This is exempted due to PowerShell 5 requirements.

To install Chocolatey Automatic Package Updater Module, run the following command from the command line or from PowerShell:

>

To upgrade Chocolatey Automatic Package Updater Module, run the following command from the command line or from PowerShell:

>

To uninstall Chocolatey Automatic Package Updater Module, run the following command from the command line or from PowerShell:

>

NOTE: This applies to both open source and commercial editions of Chocolatey.

1. Ensure you are set for organizational deployment

Please see the organizational deployment guide

  • Open Source or Commercial:
    • Proxy Repository - Create a proxy nuget repository on Nexus, Artifactory Pro, or a proxy Chocolatey repository on ProGet. Point your upstream to https://chocolatey.org/api/v2. Packages cache on first access automatically. Make sure your choco clients are using your proxy repository as a source and NOT the default community repository. See source command for more information.
    • You can also just download the package and push it to a repository Download

3. Enter your internal repository url

(this should look similar to https://chocolatey.org/api/v2)

4. Choose your deployment method:


choco upgrade au -y --source="'STEP 3 URL'" [other options]

See options you can pass to upgrade.

See best practices for scripting.

Add this to a PowerShell script or use a Batch script with tools and in places where you are calling directly to Chocolatey. If you are integrating, keep in mind enhanced exit codes.

If you do use a PowerShell script, use the following to ensure bad exit codes are shown as failures:


choco upgrade au -y --source="'STEP 3 URL'" 
$exitCode = $LASTEXITCODE

Write-Verbose "Exit code was $exitCode"
$validExitCodes = @(0, 1605, 1614, 1641, 3010)
if ($validExitCodes -contains $exitCode) {
  Exit 0
}

Exit $exitCode

- name: Ensure au installed
  win_chocolatey:
    name: au
    state: present
    version: 2019.5.22
    source: STEP 3 URL

See docs at https://docs.ansible.com/ansible/latest/modules/win_chocolatey_module.html.

Coming early 2020! Central Managment Reporting available now! More information...


chocolatey_package 'au' do
  action    :install
  version  '2019.5.22'
  source   'STEP 3 URL'
end

See docs at https://docs.chef.io/resource_chocolatey_package.html.


Chocolatey::Ensure-Package
(
    Name: au,
    Version: 2019.5.22,
    Source: STEP 3 URL
);

Requires Otter Chocolatey Extension. See docs at https://inedo.com/den/otter/chocolatey.


cChocoPackageInstaller au
{
   Name     = 'au'
   Ensure   = 'Present'
   Version  = '2019.5.22'
   Source   = 'STEP 3 URL'
}

Requires cChoco DSC Resource. See docs at https://github.com/chocolatey/cChoco.


package { 'au':
  provider => 'chocolatey',
  ensure   => '2019.5.22',
  source   => 'STEP 3 URL',
}

Requires Puppet Chocolatey Provider module. See docs at https://forge.puppet.com/puppetlabs/chocolatey.


salt '*' chocolatey.install au version="2019.5.22" source="STEP 3 URL"

See docs at https://docs.saltstack.com/en/latest/ref/modules/all/salt.modules.chocolatey.html.

5. If applicable - Chocolatey configuration/installation

See infrastructure management matrix for Chocolatey configuration elements and examples.

This package was approved as a trusted package on 5/22/2019.

Description

AU is Powershell module that helps you to automate Chocolatey package updates.

Features

  • Use only PowerShell to create automatic update script for given package.
  • Handles multiple streams with a single update script.
  • Automatically downloads installers and provides/verifies checksums for x32 and x64 versions.
  • Verifies URLs, nuspec versions, remote repository existence etc.
  • Automatically sets the nuspec descriptions from a README.md files.
  • Update single package or any subset of previously created AU packages with a single command.
  • Multithread support when updating multiple packages.
  • Repeat or ignore specific failures when updating multiple packages.
  • Plugin system when updating everything, with few integrated plugins to send email notifications, save results to gist and push updated packages to git repository.
  • Use of global variables to change functionality.
  • Sugar functions for Chocolatey package maintainers.
  • Great performance - hundreds of packages can be checked and updated in several minutes.

tools\AU\AU.psd1
#
# Module manifest for module 'AU'
#
# Generated by: Miodrag Milic
#
# Generated on: 22.5.2019.
#

@{

# Script module or binary module file associated with this manifest.
RootModule = 'AU.psm1'

# Version number of this module.
ModuleVersion = '2019.5.22'

# Supported PSEditions
# CompatiblePSEditions = @()

# ID used to uniquely identify this module
GUID = 'b2cb6770-ecc4-4a51-a57a-3a34654a0938'

# Author of this module
Author = 'Miodrag Milic'

# Company or vendor of this module
CompanyName = 'Unknown'

# Copyright statement for this module
Copyright = '(c) 2019 Miodrag Milic. All rights reserved.'

# Description of the functionality provided by this module
Description = 'Chocolatey Automatic Package Updater Module'

# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '5.0'

# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''

# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''

# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''

# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# CLRVersion = ''

# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''

# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()

# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()

# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()

# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()

# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()

# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = 'Get-AUPackages', 'Get-RemoteChecksum', 'Get-RemoteFiles', 
               'Get-Version', 'Push-Package', 'Set-DescriptionFromReadme', 
               'Test-Package', 'Update-AUPackages', 'Update-Package'

# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = '*'

# Variables to export from this module
VariablesToExport = '*'

# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = 'gau', 'lsau', 'update', 'updateall'

# DSC resources to export from this module
# DscResourcesToExport = @()

# List of all modules packaged with this module
# ModuleList = @()

# List of all files packaged with this module
# FileList = @()

# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{

    PSData = @{

        # Tags applied to this module. These help with module discovery in online galleries.
        Tags = 'chocolatey', 'update'

        # A URL to the license for this module.
        LicenseUri = 'https://www.gnu.org/licenses/gpl-2.0.txt'

        # A URL to the main website for this project.
        ProjectUri = 'https://github.com/majkinetor/au'

        # A URL to an icon representing this module.
        # IconUri = ''

        # ReleaseNotes of this module
        ReleaseNotes = 'https://github.com/majkinetor/au/blob/master/CHANGELOG.md'

    } # End of PSData hashtable

} # End of PrivateData hashtable

# HelpInfo URI of this module
HelpInfoURI = 'https://github.com/majkinetor/au/blob/master/README.md'

# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''

}

tools\AU\AU.psm1
#requires -version 3

$paths = "Private", "Public"
foreach ($path in $paths) {
    ls $PSScriptRoot\$path\*.ps1 | % { . $_ }
}
tools\AU\en-US\about_AU.help.txt
[![](http://transparent-favicon.info/favicon.ico)](#) 
[![](http://transparent-favicon.info/favicon.ico)](#) 
[![](http://transparent-favicon.info/favicon.ico)](#) 
[![](https://img.shields.io/badge/donate-patreon-blue.svg?longCache=true&style=for-the-badge)](https://www.patreon.com/majkinetor)

---

# Chocolatey Automatic Package Updater Module

This PowerShell module implements functions that can be used to automate [Chocolatey](https://chocolatey.org) package updates.

To learn more about Chocolatey automatic packages, please refer to the relevant [documentation](https://github.com/chocolatey/choco/wiki/AutomaticPackages).  
To see AU in action see [video tutorial](https://www.youtube.com/watch?v=m2XpV2LxyEI&feature=youtu.be).

## Features

- Use only PowerShell to create automatic update script for given package.
- Handles multiple streams with a single update script.
- Automatically downloads installers and provides/verifies checksums for x32 and x64 versions.
- Verifies URLs, nuspec versions, remote repository existence etc.
- Automatically sets the nuspec descriptions from a README.md files.
- Update single package or any subset of previously created AU packages with a single command.
- Multithread support when updating multiple packages.
- Repeat or ignore specific failures when updating multiple packages. 
- Plugin system when updating everything, with few integrated plugins to send email notifications, save results to gist and push updated packages to git repository.
- Use of global variables to change functionality.
- Sugar functions for Chocolatey package maintainers.
- Great performance - hundreds of packages can be checked and updated in several minutes.


## Installation

AU module requires minimally PowerShell version 5: `$host.Version -ge '5.0'`

To install it, use one of the following methods:
- PowerShell Gallery: [`Install-Module au`](https://www.powershellgallery.com/packages/AU).  
- Chocolatey:  [`cinst au`](https://chocolatey.org/packages/au). 
- [Download](https://github.com/majkinetor/au/releases/latest) latest 7z package or latest build [artifact](https://ci.appveyor.com/project/majkinetor/au/build/artifacts).


To quickly start using AU, fork [au-packages-template](https://github.com/majkinetor/au-packages-template) repository and rename it to `au-packages`.

**NOTE**: All module functions work from within specific root folder. The folder contains all of your Chocolatey packages.

## Creating the package updater script

The AU uses `update.ps1` script that package maintainers should create in the package directory. No templates are used, just plain PowerShell.

To write the package update script, it is generally required to implement 2 functions: `au_GetLatest` and `au_SearchReplace`.

### `au_GetLatest`  

This function is used to get the latest package information.

As an example, the following function uses [Invoke-WebRequest](https://technet.microsoft.com/en-us/library/hh849901.aspx?f=255&MSPPError=-2147217396) to download a page (#1). After that it takes a `href` attribute from the first page link that ends with `.exe` word as a latest URL for the package (#2). Then it conveniently splits the URL to get the latest version for the package (#3), a step that is highly specific to the URL but very easy to determine.

```powershell
function global:au_GetLatest {
     $download_page = Invoke-WebRequest -Uri $releases -UseBasicParsing #1
     $regex   = '.exe$'
     $url     = $download_page.links | ? href -match $regex | select -First 1 -expand href #2
     $version = $url -split '-|.exe' | select -Last 1 -Skip 2 #3
     return @{ Version = $version; URL32 = $url }
}
```

The returned version is later compared to the one in the nuspec file and if remote version is higher, the files will be updated. The returned keys of this HashTable are available via global variable `$global:Latest` (along with some keys that AU generates). You can put whatever data you need in the returned HashTable - this data can be used later in `au_SearchReplace`.

### `au_SearchReplace`  

Function returns HashTable containing search and replace data for any package file in the form:  

```powershell
    @{
        file_path1 = @{
            search1 = replace1
            ...
            searchN = replaceN
        }
        file_path2 = @{ ... }
        ...
    }
```

Search and replace strings are operands for PowerShell [replace](http://www.regular-expressions.info/powershell.html) operator. You do not have to write them most of the time however, they are rarely changed.

File paths are relative to the package directory. The function can use `$global:Latest` variable to get any type of information obtained when `au_GetLatest` was executed along with some AU generated data such as `PackageName`, `NuspecVersion` etc. 

The following example illustrates the usage:

```powershell
function global:au_SearchReplace {
    @{
        "tools\chocolateyInstall.ps1" = @{
            "(^[$]url32\s*=\s*)('.*')"      = "`$1'$($Latest.URL32)'"           #1
            "(^[$]checksum32\s*=\s*)('.*')" = "`$1'$($Latest.Checksum32)'"      #2
        }
    }
}
```

Here, line of the format `$url32 = '<package_url>'` in the file `tools\chocolateyInstall.ps1` will have its quoted string replaced with latest URL (#1). The next line replaces value of the variable `$checksum32` on the start of the line with the latest checksum that is automatically injected in the `$Latest` variable by the AU framework (#2). Replacement of the latest version in the nuspec file is handled automatically. 

**NOTE**: The search and replace works on lines, multiple lines can not be matched with single regular expression.

### Update 

With above functions implemented calling the `Update-Package` (alias `update`) function from the AU module will update the package when needed.

You can then update the individual package by running the appropriate `update.ps1` script from within the package directory:

```
PS C:\chocolatey\dngrep> .\update.ps1
dngrep - checking updates using au version 2016.9.14
nuspec version: 2.8.15.0
remote version: 2.8.16.0
New version found
Automatic checksum started
Downloading dngrep 32 bit
  from 'https://github.com/dnGrep/dnGrep/releases/download/v2.8.16.0/dnGREP.2.8.16.x86.msi'

Download of dnGREP.2.8.16.x86.msi (3.36 MB) completed.
Package downloaded and hash calculated for 32 bit version
Downloading dngrep 64 bit
  from 'https://github.com/dnGrep/dnGrep/releases/download/v2.8.16.0/dnGREP.2.8.16.x64.msi'

Download of dnGREP.2.8.16.x64.msi (3.39 MB) completed.
Package downloaded and hash calculated for 64 bit version
Updating files
  dngrep.nuspec
    updating version:  2.8.15.0 -> 2.8.16.0
  tools\chocolateyInstall.ps1
    (^[$]url32\s*=\s*)('.*') = $1'https://github.com/dnGrep/dnGrep/releases/download/v2.8.16.0/dnGREP.2.8.16.x86.msi'
    (^[$]url64\s*=\s*)('.*') = $1'https://github.com/dnGrep/dnGrep/releases/download/v2.8.16.0/dnGREP.2.8.16.x64.msi'
    (^[$]checksum32\s*=\s*)('.*') = $1'CE4753735148E1F48FE0E1CD9AA4DFD019082F4F43C38C4FF4157F08D346700C'
    (^[$]checksumType32\s*=\s*)('.*') = $1'sha256'
    (^[$]checksum64\s*=\s*)('.*') = $1'025BD4101826932E954AACD3FE6AEE9927A7198FEEFFB24F82FBE5D578502D18'
    (^[$]checksumType64\s*=\s*)('.*') = $1'sha256'
Attempting to build package from 'dngrep.nuspec'.
Successfully created package 'dngrep.2.8.16.0.nupkg'
Package updated
```

This is best understood via the example - take a look at the real life package [installer script](https://github.com/majkinetor/au-packages/blob/master/dngrep/tools/chocolateyInstall.ps1) and its [AU updater](https://github.com/majkinetor/au-packages/blob/master/dngrep/update.ps1).

### Automatic package description from README.md

If a package directory contains the `README.md` file, its content will be automatically set as description of the package with first 2 lines omitted - you can put on those lines custom content that will not be visible in the package description. 

To disable this option use `-NoReadme` switch with the `Update-Package` function. You can still call it manually from within `au_AfterUpdate`, which you may want to do in order to pass custom parameters to it:

```powershell
function global:au_AfterUpdate ($Package)  {
     Set-DescriptionFromReadme $Package -SkipLast 2 -SkipFirst 5
}
```

To extract descriptions from existing packages into README.md files the following script can be used:

```powershell
ls | ? PSIsContainer | ? { !(Test-Path $_\README.md) } | % {
  [xml] $package = gc $_\*.nuspec -ea 0 -Encoding UTF8
  if (!$package) { return }

  $meta = $package.package.metadata
  $readme = ('# <img src="{1}" width="48" height="48"/> [{0}](https://chocolatey.org/packages/{0})' -f $meta.id, $meta.iconUrl), ''
  $readme += $meta.description -split "`n" | % { $_.Trim() }
  $readme -join "`n" | Out-File -Encoding UTF8 $_\README.md
  $meta.id
}
```

### Checks

The `update` function does the following checks:

- The `$Latest.Version` will be checked to match a valid nuspec pattern.
- Any hash key that starts with the word `Url`, will be checked for existence and MIME textual type, since binary is expected here.
- If the remote version is higher then the nuspec version, the Chocolatey site will be checked for existence of this package version (this works for unpublished packages too). This allows multiple users to update same set of packages without a conflict. Besides, this feature makes it possible not to persist state between the updates as once the package is updated and pushed, the next update will not push the package again. To persist the state of updated packages you can use for instance [Git](https://github.com/majkinetor/au/blob/master/AU/Plugins/Git.ps1) plugin which saves the updated and published packages to the git repository. 
- The regex patterns in `au_SearchReplace` will be checked for existence.

If any of the checks fails, package will not get updated. This feature releases you from the worries about how precise is your pattern match in both `au_` functions - if for example, a vendor site changes, the package update will fail because of the wrongly parsed data. 

For some packages, you may want to disable some of the checks by specifying additional parameters of the `update` function (not all can be disabled):

| Parameter             | Description                       |
| ---------             | ------------                      |
| `NoCheckUrl`          | Disable URL checks                |
| `NoCheckChocoVersion` | Disable the Chocolatey site check |
| `ChecksumFor none`    | Disable automatic checksum        |

### Automatic checksums

**NOTE**: This feature works by invoking `chocolateyInstall.ps1` of the respective package with a [monkey-patched version of the `Get-ChocolateyWebFile` helper function](https://github.com/majkinetor/au/blob/a8d31244997f08685cc894da4faa1012c60b34f1/AU/Public/Update-Package.ps1#L172). The install script is supposed to either call this function explicitly or indirectly (e.g. `Install-ChocolateyInstallPackage $url`, which calls the former one).
In any case, upon execution of `Get-ChocolateyWebFile`, the install script will be **terminated**. Any actions in your script occurring after the call to `Get-ChocolateyWebFile` will **not** be run. This is due to the nature of [how the function gets monkey-patched](https://github.com/majkinetor/au/blob/a8d31244997f08685cc894da4faa1012c60b34f1/AU/Public/Update-Package.ps1#L172), which might be improved in the future.

When new version is available, the `update` function will by default download both x32 and x64 versions of the installer and calculate the desired checksum. It will inject this info in the `$global:Latest` HashTable variable so you can use it via `au_SearchReplace` function to update hashes. The parameter `ChecksumFor` can contain words `all`, `none`, `32` or `64` to further control the behavior.

You can disable this feature by calling update like this:

    update -ChecksumFor none

You can define the hash algorithm by returning corresponding `ChecksumTypeXX` hash keys in the `au_GetLatest` function:

    return @{ ... ChecksumType32 = 'sha512'; ... }

If the checksum is actually obtained from the vendor's site, you can provide it along with its type (SHA256 by default) by returning corresponding `ChecksumXX` hash keys in the `au_GetLatest` function:

    return @{ ... Checksum32 = 'xxxxxxxx'; ChecksumType32 = 'md5'; ... }

If the `ChecksumXX` hash key is present, the AU will change to checksum verification mode - it will download the installer and verify that its checksum matches the one provided. If the key is not present, the AU will calculate hash with the given `ChecksumTypeXX` algorithm.

### Manual checksums

Sometimes invoking `chocolateyInstall.ps1` during the automatic checksum could be problematic so you need to disable it using update option `ChecksumFor none` and get the checksum some other way. Function `Get-RemoteChecksum` can be used to simplify that:

```powershell
  function global:au_BeforeUpdate() {
     $Latest.Checksum32 = Get-RemoteChecksum $Latest.Url32
  }

  function global:au_GetLatest() {
    $download_page = Invoke-WebRequest $releases -UseBasicParsing
    $url     = $download_page.links | ? href -match '\.exe$' | select -First 1 -expand href
    $version = $url -split '/' | select -Last 1 -Skip 1
    @{
        URL32     = $url
        Version   = $version
    }
  }
```

### Force update

You can force the update even if no new version is found by using the parameter `Force` (or global variable `$au_Force`). This can be useful for testing the update and bug fixing, recalculating the checksum after the package was created and already pushed to Chocolatey or if URLs to installer changed without change in version.

**Example**:

```
PS C:\chocolatey\cpu-z.install> $au_Force = $true; .\update.ps1
cpu-z.install - checking updates
nuspec version: 1.77
remote version: 1.77
No new version found, but update is forced
Automatic checksum started
...
Updating files
  cpu-z.install.nuspec
    updating version using Chocolatey fix notation: 1.77 -> 1.77.0.20160814
...
```

Force option changes how package version is used. Without force, the `NuspecVersion` determines what is going on. Normally, if `NuspecVersion` is lower then the `RemoteVersion` update happens. With `Force` this changes:

1. If `NuspecVersion` is lower then `RemoteVersion`, Force is ignored and update happens as it would normally
2. If `NuspecVersion` is the same as the `RemoteVersion`, the version will change to chocolatey fix notation.
3. If the `NuspecVersion` is already using chocolatey fix notation, the version will be updated to fix notation for the current date.
4. If the `NuspecVersion` is higher then the `RemoteVersion` update will happen but `RemoteVersion` will be used.

Points 2-4 do not apply if you set the explicit version using the variable `au_Version`.

[Chocolatey fix notation](https://github.com/chocolatey/choco/wiki/CreatePackages#package-fix-version-notation) changes a version so that current date is added in the _revision_ component of the package version in the format `yyyyMMdd`. More precisely: 

- chocolatey _fix version_ always ends up in to the _Revision_ part of the package version;
- existing _fix versions_ are changed to contain the current date;
- if _revision_ part is present in the package version and it is not in the _chocolatey fix notation_ form, AU will keep the existing version but notify about it;

Force can be triggered also from the `au_GetLatest` function. This may be needed if remote version doesn't change but there was nevertheless change on the vendor site. See the [example](https://github.com/majkinetor/au-packages/blob/master/cpu-z.install/update.ps1#L18-L39) on how to update the package when remote version is unchanged but hashsum of the installer changes.

### Global variables

To avoid changing the `./update.ps1` when troubleshooting or experimenting you can set up any **already unset** `update` parameter via global variable. The names of global variables are the same as the names of parameters with the prefix `au_`. As an example, the following code will change the update behavior so that URL is not checked for existence and MIME type and update is forced: 

    $au_NoCheckUrl = $au_Force = $true
    ./update.ps1

This is the same as if you added the parameters to `update` function inside the `./update.ps1` script:

    update -NoCheckUrl -Force

however, its way easier to setup global variable with manual intervention on multiple packages.

There is also a special variable `$au_GalleryUrl` using which you can change the URL that is used to check if package is already pushed. It defaults to https://chocolatey.org and you can change it if you need to this option for 3rd party or internal package repositories. 

### Reusing the AU updater with metapackages

Metapackages can reuse an AU updater of its dependency by the following way:

- In the dependent updater, instead of calling the `update` directly, use construct:

  ```
    if ($MyInvocation.InvocationName -ne '.') { update ... }
  ```

- In the metapackage updater dot source the dependent updater and override `au_SearchReplace`.

This is best understood via example - take a look at the [cpu-z](https://github.com/majkinetor/au-packages/blob/master/cpu-z/update.ps1) AU updater which uses the updater from the [cpu-z.install](https://github.com/majkinetor/au-packages/blob/master/cpu-z.install/update.ps1) package on which it depends. It overrides the `au_SearchReplace` function and the `update` call but keeps the `au_GetLatest`.

### Embedding binaries

Embedded packages do not download software from the Internet but contain binaries inside the package. This makes package way more stable as it doesn't depend on the network for installation - all versions of the package will **always work**, either on Chocolatey gallery or in your private cache.

AU function `Get-RemoteFiles` can download files and save them in the package's `tools` directory. It does that by using the `$Latest.URL32` and/or `$Latest.URL64`.

The following example downloads files inside `au_BeforeUpdate` function which is called before the package files are updated with the latest data (function is not called if no update is available): 

```powershell
function global:au_BeforeUpdate() {
    #Download $Latest.URL32 / $Latest.URL64 in tools directory and remove any older installers.
    Get-RemoteFiles -Purge
}
```

This function will also set the appropriate `$Latest.ChecksumXX`. 

**NOTE**: There is no need to use automatic checksum when embedding because `Get-RemoteFiles` will do it, so always use parameter `-ChecksumFor none`. 

### Streams

The software vendor may maintain _multiple latest versions_, of specific releases because of the need for long time support. `au_GetLatest` provides an option to return multiple HashTables in order for its user to monitor each supported software _stream_. Prior to AU streams, each software stream was typically treated as a separate package and maintained independently. Using AU streams allows a single package updater to update multiple version streams in a single run:

```powershell
function global:au_GetLatest {
    # ...
    @{
        Streams = [ordered] @{
            '1.3' = @{ Version = $version13; URL32 = $url13 }  # $version13 = '1.3.9'
            '1.2' = @{ Version = $version12; URL32 = $url12 }  # $version12 = '1.2.3.1'
        }
    }
}
```

Though a `Hashtable` can be returned for streams, it is recommended to return an `OrderedDictionary` (see above example) that contains streams from the most recent to the oldest one. This ensures that when forcing an update, the most recent stream available will be considered by default (i.e. when no `-IncludeStream` is specified).

Latest stream versions are kept in the `<package_name>.json` file in the package directory. For real life example take a look at the [Python3](https://github.com/chocolatey/chocolatey-coreteampackages/blob/master/automatic/python3/update.ps1) package updater which automatically finds available python 3 streams and keeps them [up to date](https://gist.github.com/a14b1e5bfaf70839b338eb1ab7f8226f/78cdc99c2d7433d26c65bc721c26c1cc60ccca3d#python3).

Streams can be also used to manage multiple related packages as a single package. [LibreOffice](https://github.com/chocolatey/chocolatey-coreteampackages/blob/master/automatic/libreoffice/update.ps1) package updater uses streams to manage [two different](https://gist.github.com/choco-bot/a14b1e5bfaf70839b338eb1ab7f8226f/78cdc99c2d7433d26c65bc721c26c1cc60ccca3d#libreoffice) variants of the software (prior to streams this was handled via 2 packages.)

In order to help working with versions, function `Get-Version` can be called in order to parse [semver](http://semver.org/) versions in a flexible manner. It returns an `AUVersion` object with all the details about the version. Furthermore, this object can be compared and sorted.

**NOTES**: 
- By default only the first updated stream is pushed per run of `updateall`. In order to push all of them add among its options `PushAll = $true`.
- To force the update of the single stream using `IncludeStream` parameter. To do so on via commit message use `[AU package\stream]` syntax.

```powershell
PS> Get-Version 'v1.3.2.7rc1'

Version Prerelease BuildMetadata
------- ---------- -------------
1.3.2.7 rc1

PS> $version = Get-Version '1.3.2-beta2+5'
PS> $version.ToString(2) + ' => ' + $version.ToString()
1.3 => 1.3.2-beta2+5
```

### WhatIf

If you don't like the fact that AU changes the package inline, or just want to preview changes you can use `$WhatIf` parameter or `$au_WhatIf` global variable:

```powershell
PS C:\au-packages\copyq> $au_Force = $au_WhatIf = $true; .\update.ps1

WARNING: WhatIf passed - package files will not be changed
copyq - checking updates using au version 2017.5.21.172014
...
Successfully created package 'C:\au-packages\copyq\copyq.3.0.1.20170523.nupkg'
WARNING: Package restored and updates saved to: C:\Users\majkinetor\AppData\Local\Temp\au\copyq\_output
```

**NOTES**: 
- The inline editing is intentional design choice so that AU, its plugins and user scripts can use latest package data, such as latest version, checksum etc.
- WhatIf can be used when updating all packages.
- Since WhatIf saves the original package before the update and restores it after the update, interruption at specific time can cause package files to be left unrestored. In that case you can manually restore the package from `$Env:TEMP\au\<package_name>\_backup` directory. This is in general not very likely however, because restore happens very quickly after the update. 

## Updating all packages

You can update all packages and optionally push them to the Chocolatey repository with a single command. Function `Update-AUPackages` (alias `updateall`) will iterate over `update.ps1` scripts and execute each in a separate thread. If it detects that a package is updated it will optionally try to push it to the Chocolatey repository and may also run configured plugins.

For the push to work, specify your Chocolatey API key in the file `api_key` in the script's directory (or its parent directory) or set the environment variable `$Env:api_key`. If none provided cached NuGet key will be used.

The function will search for packages in the current directory. To override that, use global variable `$au_Root`:

    PS> $au_root = 'c:\chocolatey_packages`
    PS> $Options = @{
        Timeout = 10
        Threads = 15
        Push    = $true
    }
    PS> updateall -Options $Options

    Updating 6 automatic packages at 2016-09-16 22:03:33
    Push is enabled
       copyq is updated to 2.6.1 and pushed 
       dngrep had errors during update
           Can't validate URL 'https://github.com/dnGrep/dnGrep/releases/download/v2.8.16.0/dnGREP.2.8.16.x64.msi'
           Exception calling "GetResponse" with "0" argument(s): "The operation has timed out"
       eac has no updates
       pandoc has no updates
       plantuml has no updates
       yed had errors during update
           Can't validate URL 'https://www.yworks.com'
           Invalid content type: text/html

    Finished 6 packages after .32 minutes.
    1 updated and 1 pushed.
    2 errors - 2 update, 0 push.


Use `updateall` parameter `Name` to specify package names via glob, for instance `updateall [a-d]*` would update only packages which names start with the letter 'a' trough 'd'. Add `Push` among options to push successfully built packages to the chocolatey repository.

Take a look at the [real life example](http://tiny.cc/v1u1ey) of the update script. To see all available options for `updateall` type `man updateall -Parameter Options`.

### Plugins

It is possible to specify a custom user logic in `Options` parameter - every key that is of type `[HashTable]` will be considered plugin with the PowerShell script that is named the same as the key. The following code shows how to use 5 integrated plugins:

```powershell
    $Options = [ordered]@{
        Timeout = 100
        Threads = 15
        Push    = $true
          
        # Save text report in the local file report.txt
        Report = @{
            Type = 'text'
            Path = "$PSScriptRoot\report.txt"
        }
        
        # Then save this report as a gist using your api key and gist id
        Gist = @{
            ApiKey = $Env:github_api_key
            Id     = $Env:github_gist_id
            Path   = "$PSScriptRoot\report.txt"
        }

        # Persist pushed packages to your repository
        Git = @{
            User = ''
            Password = $Env:github_api_key
        }
        
        # Then save run info which can be loaded with Import-CliXML and inspected
        RunInfo = @{
            Path = "$PSScriptRoot\update_info.xml"
        }

        # Finally, send an email to the user if any error occurs and attach previously created run info
        Mail = if ($Env:mail_user) {
                @{
                   To          = $Env:mail_user
                   Server      = 'smtp.gmail.com'
                   UserName    = $Env:mail_user
                   Password    = $Env:mail_pass
                   Port        = 587
                   EnableSsl   = $true
                   Attachment  = "$PSScriptRoot\$update_info.xml"
                   UserMessage = 'Save attachment and load it for detailed inspection: <code>$info = Import-CliXCML update_info.xml</code>'
                }
        } else {}
    }
```

The plugins above - `Report`, `Gist`,`Git`,`RunInfo` and `Mail` -  are executed in the given order (hence the `[ordered]` flag) and AU passes them given options and saves the run results. 

To add custom plugins, specify additional plugin search path via `$Options.PluginPath`. Plugin is a normal PowerShell script and apart from parameters given in its HashTable the AU will send it one more parameter `$Info` that contains current run info. The name of the script file must be the same as that of the key which value is used to pass the parameters to the plugin. If a key with the value of type `[HashTable]` doesn't point to existing PowerShell script it is not considered to be an AU plugin.

To temporary disable plugins use `updateall` option `NoPlugins` or global variable `$au_NoPlugins`.
To temporary exclude the AU package from `updateall` procedure add `_` prefix to the package directory name.

You can also execute a custom script via ScriptBlock specified via `BeforeEach` and `AfterEach` options. They will receive 2 parameters - package name and Options HashTable which you can use to pass any other parameter.

For more information take a look at the [plugins documentation](https://github.com/majkinetor/au/tree/master/Plugins.md).

### Make a script

Its desirable to put everything in a single script `update_all.ps1` so it can be scheduled and called with the given options. Rename `update_all_default.ps1` and uncomment and set the options you need. 

To make a local scheduled task, use the following code in the directory where your `update_all.ps1` script is found to install it:

    $At = '03:00'
    schtasks /create /tn "Update-AUPackages" /tr "powershell -File '$pwd\update_all.ps1'" /sc daily /st $At

Its preferable to run the updater on [AppVeyor](https://github.com/majkinetor/au/wiki/AppVeyor).

### Handling update errors

When errors occur during the update, email will be sent to the owner and report will contain [errors](https://gist.github.com/gep13/bd2eaa76f2a9ab739ca0544c502dca6e/c71d4eb3f6de2848f41c1b92e221737d775f0b6f#errors) section. Some network errors are expectable and you may want to ignore them - package that failed will get updated in one of the subsequent runs anyway. To ignore an error, use try/catch block around update and return 'ignore' word from the `update.ps1` script:

    try {
        update
    } catch {
        $ignore = 'Unable to connect to the remote server'
        if ($_ -match $ignore) { Write-Host $ignore; 'ignore' }  else { throw $_ }
    }
    

The package will get shown in the report as [ignored](https://gist.github.com/gep13/bd2eaa76f2a9ab739ca0544c502dca6e/db5313020d882945d8fcc3a10f5176263bb837a6#quicktime) and no errors will be shown.

`au_GetLatest` can also return 'ignore' word instead of `$Latest` HashTable to force the package to be ignored in the `updateall` context.

If some errors occur in multiple packages, you can make `updateall` **repeat and/or ignore** such packages globally without any changes to `update.ps1` scripts. To do so, provide repeat/ignore options to its`$Options` HashTable parameter as in the following example:

```powershell
IgnoreOn = @(                                      #Error message parts to set the package ignore status
    'Timeout'
    'Access denied'
)                                  
RepeatOn = @(                                      #Error message parts on which to repeat package updater
    'Unable to create secure channel'
    'Could not establish trust relationship'
    'Unable to connect'
)
RepeatSleep   = 120                                #How much to sleep between repeats in seconds, by default 0
RepeatCount   = 2                                  #How many times to repeat on errors, by default 1
```

**Notes**
- The repeat wont work if the package has its own ignore routine for the same error, because the package wont return an error in that case.
- If the same error is both in `RepeatOn` and `IgnoreOn` list, the package will first be repeated and if the error persists, it will be ignored.
- The last line returned by the package prior to the word 'ignore' is used as `IgnoreMessage` for that package and shown in reports.

#### Can't validate URL error

If you encounter `Can't validate URL` error like

```bash
Can't validate URL
Exception calling "GetResponse" with "0" argument(s): "The remote server returned an error: (401) Unauthorized.":<url>
```

you need to pass HTTP/HTTPS headers used for retrieving `url`/`url64bit` to `$Latest.Options.Headers` as `Hashtable`, where key is header name, and value are header itself. This may be `Authorization` or `Referer` header or any others.

## Other functions

Apart from the functions used in the updating process, there are few sugars for regular maintenance of the package:

- Test-Package  
Quickly test install and/or uninstall of the package from the current directory with optional parameters. This function can be used to start testing in [chocolatey-test-environment](https://github.com/majkinetor/chocolatey-test-environment) via `Vagrant` parameter.

- Push-Package  
Push the latest package using your API key.

- Get-AuPackages (alias `gau` or `lsau`)  
Returns the list of the packages which have `update.ps1` script in its directory and which name doesn't start with '_'.

## Community

- [Wormies AU Helpers](https://github.com/WormieCorp/Wormies-AU-Helpers)  
Helper scripts to make maintaining packages using AU even easier
- [Chocolatey Core Community Maintainers Team Packages](https://github.com/chocolatey/chocolatey-coreteampackages)  
The [largest](https://gist.github.com/choco-bot/a14b1e5bfaf70839b338eb1ab7f8226f) repository of AU packages by far
tools\AU\Plugins\Gist.ps1
# Author: Miodrag Milic <[email protected]>
# Last Change: 10-Nov-2016.
<#
.SYNOPSIS
    Upload files to Github gist platform.

.DESCRIPTION
    Plugin uploads one or more local files to the gist with the given id
#>
param(
    $Info,

    # Gist id, leave empty to create a new gist
    [string] $Id,

    # Github ApiKey, create in Github profile -> Settings -> Personal access tokens -> Generate new token
    # Make sure token has 'gist' scope set.
    [string] $ApiKey,

    # File paths to attach to gist
    [string[]] $Path,

    # Gist description
    [string] $Description = "Update-AUPackages Report #powershell #chocolatey"
)

# Create gist
$gist = @{
    description = $Description
    public      = $true
    files       = @{}
}

ls $Path | % {
    $name      = Split-Path $_ -Leaf
    $content   = gc $_ -Raw
    $gist.files[$name] = @{content = "$content"}
}

# request

#https://github.com/majkinetor/au/issues/142
[System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor [System.Net.SecurityProtocolType]::Tls -bor [System.Net.SecurityProtocolType]::Ssl3

$uri  = 'https://api.github.com/gists'
$params = @{
    ContentType = 'application/json'
    Method      = if ($Id) { "PATCH" } else { "POST" }
    Uri         = if ($Id) { "$uri/$Id" } else { $uri }
    Body        = $gist | ConvertTo-Json
    UseBasicparsing = $true
}

#$params | ConvertTo-Json -Depth 100 | Write-Host

if ($ApiKey) {
    $params.Headers = @{
        Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($ApiKey))
    }
}

$res = iwr @params

#https://api.github.com/gists/a700c70b8847b29ebb1c918d47ee4eb1/211bac4dbb707c75445533361ad12b904c593491
$id = (($res.Content | ConvertFrom-Json).history[0].url -split '/')[-2,-1] -join '/'
"https://gist.github.com/$id"
tools\AU\Plugins\Git.ps1
# Author: Miodrag Milic <[email protected]>
# Last Change: 09-Nov-2016.

# https://www.appveyor.com/docs/how-to/git-push/

param(
    $Info,

    # Git username
    [string] $User,

    # Git password. You can use Github Token here if you omit username.
    [string] $Password,

    # Force git commit when package is updated but not pushed.
    [switch] $Force,

    # Commit strategy: 
    #  single    - 1 commit with all packages
    #  atomic    - 1 commit per package    
    #  atomictag - 1 commit and tag per package
    [ValidateSet('single', 'atomic', 'atomictag')]
    [string]$commitStrategy = 'single',

    # Branch name
    [string]$Branch = 'master'
)

[array]$packages = if ($Force) { $Info.result.updated } else { $Info.result.pushed }
if ($packages.Length -eq 0) { Write-Host "No package updated, skipping"; return }

$root = Split-Path $packages[0].Path
pushd $root
$origin  = git config --get remote.origin.url
$origin -match '(?<=:/+)[^/]+' | Out-Null
$machine = $Matches[0]

if ($User -and $Password) {
    Write-Host "Setting credentials for: $machine"

    if ( "machine $server" -notmatch (gc ~/_netrc)) {
        Write-Host "Credentials already found for machine: $machine"
    }
    "machine $machine", "login $User", "password $Password" | Out-File -Append ~/_netrc -Encoding ascii
} elseif ($Password) {
    Write-Host "Setting oauth token for: $machine"
    git config --global credential.helper store
    Add-Content "$env:USERPROFILE\.git-credentials" "https://${Password}:[email protected]$machine`n"
}

Write-Host "Executing git pull"
git checkout -q $Branch
git pull -q origin $Branch


if  ($commitStrategy -like 'atomic*') {
    $packages | % {
        Write-Host "Adding update package to git repository: $($_.Name)"
        git add -u $_.Path
        git status

        Write-Host "Commiting $($_.Name)"
        $message = "AU: $($_.Name) upgraded from $($_.NuspecVersion) to $($_.RemoteVersion)"
        $gist_url = $Info.plugin_results.Gist -split '\n' | select -Last 1
        $snippet_url = $Info.plugin_results.Snippet -split '\n' | select -Last 1
        git commit -m "$message`n[skip ci] $gist_url $snippet_url" --allow-empty

        if ($commitStrategy -eq 'atomictag') {
          $tagcmd = "git tag -a $($_.Name)-$($_.RemoteVersion) -m '$($_.Name)-$($_.RemoteVersion)'"
          Invoke-Expression $tagcmd
        }
    }
}
else {
    Write-Host "Adding updated packages to git repository: $( $packages | % Name)"
    $packages | % { git add -u $_.Path }
    git status

    Write-Host "Commiting"
    $message = "AU: $($packages.Length) updated - $($packages | % Name)"
    $gist_url = $Info.plugin_results.Gist -split '\n' | select -Last 1
    $snippet_url = $Info.plugin_results.Snippet -split '\n' | select -Last 1
    git commit -m "$message`n[skip ci] $gist_url $snippet_url" --allow-empty

}
Write-Host "Pushing changes"
git push -q 
if ($commitStrategy -eq 'atomictag') {
    write-host 'Atomic Tag Push'
    git push -q --tags
}
popd
tools\AU\Plugins\GitReleases.ps1
# Author: Kim Nordmo <[email protected]>
# Last Change: 29-Oct-2017.

<#
.SYNOPSIS
    Creates Github release for updated packages
#>
param(
    $Info,

    # Github API token to use when creating/checking releases and uploading artifacts
    [string]$ApiToken,

    # What kind of release should be created, either 1 release per date, or 1 release per package and version is supported.
    [ValidateSet('date', 'package')]
    [string]$releaseType,

    # The text that should be used in the header of the release.
    [string]$releaseHeader = $null,

    # The text that should be used in the description of the release.
    [string]$releaseDescription = $null,

    # The formatting to use when replacing <date> in release header/description and on date based releases.
    [string]$dateFormat = '{0:yyyy-MM-dd}',

    # Force creating a release when a package have been updated and not just been pushed.
    [switch]$Force
)

function GetOrCreateRelease() {
    param(
        [string]$tagName,
        [string]$releaseName,
        [string]$releaseDescription,
        [string]$repository,
        $headers)

    try {
        Write-Verbose "Checking for a release using the tag: $tagName..."
        $response = Invoke-RestMethod -UseBasicParsing -Uri "https://api.github.com/repos/$repository/releases/tags/$tagName" -Headers $headers | ? tag_name -eq $tagName
        if ($response) {
            return $response
        }
    }
    catch {
    }

    $json = @{
        "tag_name"         = $tagName
        "target_commitish" = "master"
        "name"             = $releaseName
        "body"             = $releaseDescription
        "draft"            = $false
        "prerelease"       = $false
    } | ConvertTo-Json -Compress

    Write-Host "Creating the new release $tagName..."
    return Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://api.github.com/repos/$repository/releases" -Body $json -Headers $headers
}

[array]$packages = if ($Force) { $Info.result.updated } else { $Info.result.pushed }

if ($packages.Length -eq 0) { Write-Host "No package updated, skipping"; return }

$packagesToRelease = New-Object 'System.Collections.Generic.List[hashtable]'

$packages | % {
    if ($_.Streams) {
        $_.Streams.Values | ? { $_.Updated } | % {
            $packagesToRelease.Add(@{
                    Name          = $_.Name
                    NuspecVersion = $_.NuspecVersion
                    RemoteVersion = $_.RemoteVersion
                    NuFile        = Resolve-Path ("$($_.Path)/$($_.Name).$($_.RemoteVersion).nupkg")
                })
        }
    }
    else {
        $packagesToRelease.Add(@{
                Name          = $_.Name
                NuspecVersion = $_.NuspecVersion
                RemoteVersion = $_.RemoteVersion
                NuFile        = Resolve-Path ("$($_.Path)/$($_.Name).$($_.RemoteVersion).nupkg")
            })
    }
}

$origin = git config --get remote.origin.url

if (!($origin -match "github.com\/([^\/]+\/[^\/\.]+)")) {
    Write-Warning "Unable to parse the repository information, skipping..."
    return;
}
$repository = $Matches[1]

$headers = @{
    Authorization = "token $ApiToken"
}

if ($releaseType -eq 'date' -and !$releaseHeader) {
    $releaseHeader = 'Packages updated on <date>'
}
elseif (!$releaseHeader) {
    $releaseHeader = '<PackageName> <RemoteVersion>'
}

if ($releaseType -eq 'date' -and !$releaseDescription) {
    $releaseDescription = 'We had packages that was updated on <date>'
}
elseif (!$releaseDescription) {
    $releaseDescription = '<PackageName> was updated from version <NuspecVersion> to <RemoteVersion>'
}

$date = Get-Date -UFormat $dateFormat

if ($releaseType -eq 'date') {
    $release = GetOrCreateRelease `
        -tagName $date `
        -releaseName ($releaseHeader -replace '<date>', $date) `
        -releaseDescription ($releaseDescription -replace '<date>', $date) `
        -repository $repository `
        -headers $headers

    if (!$release) {
        Write-Error "Unable to create a new release, please check your permissions..."
        return
    }
}

$uploadHeaders = $headers.Clone()
$uploadHeaders['Content-Type'] = 'application/zip'

$packagesToRelease | % {
    # Because we grab all streams previously, we need to ignore
    # cases when a stream haven't been updated (no nupkg file created)
    if (!$_.NuFile) { return }

    if ($releaseType -eq 'package') {
        $releaseName = $releaseHeader -replace '<PackageName>', $_.Name -replace '<RemoteVersion>', $_.RemoteVersion -replace '<NuspecVersion>', $_.NuspecVersion -replace '<date>', $date
        $packageDesc = $releaseDescription -replace '<PackageName>', $_.Name -replace '<RemoteVersion>', $_.RemoteVersion -replace '<NuspecVersion>', $_.NuspecVersion -replace '<date>', $date

        $release = GetOrCreateRelease `
            -tagName "$($_.Name)-$($_.RemoteVersion)" `
            -releaseName $releaseName `
            -releaseDescription $packageDesc `
            -repository $repository `
            -headers $headers
    }

    $fileName = [System.IO.Path]::GetFileName($_.NuFile)

    $existing = $release.assets | ? name -eq $fileName
    if ($existing) {
        Write-Verbose "Removing existing $fileName asset..."
        Invoke-RestMethod -UseBasicParsing -Uri $existing.url -method Delete -Headers $headers | Out-Null
    }

    $uploadUrl = $release.upload_url -replace '\{.*\}$', ''
    $rawContent = [System.IO.File]::ReadAllBytes($_.NuFile)
    Write-Host "Uploading $fileName asset..."
    Invoke-RestMethod -UseBasicParsing -Uri "${uploadUrl}?name=${fileName}&label=$($_.Name) v$($_.RemoteVersion)" -Body $rawContent -Headers $uploadHeaders -Method Post | Out-Null
}
tools\AU\Plugins\Gitter.ps1
# Author: Kim Nordmo <[email protected]>
# Last Change: 2018-06-13
<#
.SYNOPSIS
  Publishes the package update status to gitter.

.PARAMETER WebHookUrl
  This is the cusotm webhook url created through gitter integrations.

.PARAMETER MessageFormat
  The format of the message that is meant to be published on gitter.
  {0} = The total number of automated packages.
  {1} = The number of updated packages,
  {2} = The number of published packages.
  {3} = The number of failed packages.
  {4} = The url to the github gist.
#>
param(
  $Info,
  [string]$WebHookUrl,
  [string]$MessageFormat = "[Update Status:{0} packages.`n  {1} updated, {2} Published, {3} Failed]({4})"
)

if (!$WebHookUrl) { return } # If we don't have a webhookurl we can't push status messages, so ignore.

$updatedPackages   = @($Info.result.updated).Count
$publishedPackages = @($Info.result.pushed).Count
$failedPackages    = $Info.error_count.total
$gistUrl           = $Info.plugin_results.Gist -split '\n' | select -Last 1
$packageCount      = $Info.result.all.Length

$gitterMessage     = ($MessageFormat -f $packageCount, $updatedPackages, $publishedPackages, $failedPackages, $gistUrl)

$arguments = @{
  Body             = if ($failedPackages -gt 0) { "message=$gitterMessage&level=error" } else { "message=$gitterMessage" }
  UseBasicParsing  = $true
  Uri              = $WebHookUrl
  ContentType      = 'application/x-www-form-urlencoded'
  Method           = 'Post'
}

"Submitting message to gitter"
Invoke-RestMethod @arguments
"Message submitted to gitter"
tools\AU\Plugins\History.ps1
# Author: Miodrag Milic <[email protected]>
# Last Change: 09-Dec-2016.

<#
.SYNOPSIS
    Create update history as markdown report

.DESCRIPTION
    Shows one date per line and all of the packages pushed to the Chocolatey community
    repository during that day. First letter of the package name links to report
    (produced by the Report plugin), the rest links to the actuall commit (produced by the Git plugin).
#>
param(
    $Info,

    #Number of dates to show in the report
    $Lines=30,

    #Github user repository, used to create commit links
    $Github_UserRepo = 'chocolatey/chocolatey-coreteampackages',

    #File path where to save the markdown report
    $Path = "Update-History.md"
)

Write-Host "Saving history to $Path"

$res=[System.Collections.Specialized.OrderedDictionary]@{}
$log = git --no-pager log -q --grep '^AU: ' --date iso | Out-String
$all_commits = $log | sls 'commit(.|\n)+?(?=\ncommit )' -AllMatches
foreach ($commit in $all_commits.Matches.Value) {
    $commit = $commit -split '\n'

    $id       = $commit[0].Replace('commit','').Trim().Substring(0,7)
    $date     = $commit[2].Replace('Date:','').Trim()
    $date     = ([datetime]$date).Date.ToString("yyyy-MM-dd")
    $report   = $commit[5].Replace('[skip ci]','').Trim()
    [array] $packages = ($commit[4] -replace '^\s+AU:.+?(-|:) |\[skip ci\]').Trim().ToLower()

    $packages_md = $packages -split ' ' | % {
        $first = $_.Substring(0,1).ToUpper(); $rest  = $_.Substring(1)
        if ($report) {
            "[$first]($report)[$rest](https://github.com/$Github_UserRepo/commit/$id)"
        } else {
            "[$_](https://github.com/$Github_UserRepo/commit/$id)"
        }
    }

    if (!$res.Contains($date)) { [email protected]() }
    $res.$date += $packages_md
}

$res = $res.Keys | select -First $Lines | % { $r=[System.Collections.Specialized.OrderedDictionary]@{} } { $r[$_] = $res[$_] } {$r}

$history = @"
# Update History

Showing maximum $Lines dates.  
Click on the first letter of the package name to see its report and on the remaining letters to see its git commit.

---

"@
foreach ($kv in $res.GetEnumerator()) { $history += "`n{0} ({2}) {1}`n" -f "**$($kv.Key)**", "$($kv.Value -join ' &ndash; ')", $kv.Value.Length }
$history | Out-File $Path
tools\AU\Plugins\Mail.ps1
# Author: Miodrag Milic <[email protected]>
# Last Change: 12-Nov-2016.

param(
    $Info,
    [string]   $To,
    [string]   $From,
    [string]   $Server,
    [string]   $UserName,
    [string]   $Password,
    [int]      $Port,
    [string[]] $Attachment,
    [switch]   $EnableSsl,
    [string]   $UserMessage,
    # Do not send only on errors
    [switch]   $SendAlways
)

if (($Info.error_count.total -eq 0) -and !$SendAlways) {
    Write-Host 'Mail not sent as there are no errors (override with SendAlways param)'
    return
}

$errors_word = if ($Info.error_count.total -eq 1) { 'error' } else { 'errors' }

# Create mail message

if (!$From) { $From = "[email protected]{0}.{1}" -f $Env:UserName, $Env:ComputerName }

$msg = New-Object System.Net.Mail.MailMessage $from, $To
$msg.IsBodyHTML = $true

if ($Info.error_count.total -eq 0) {
    $msg.Subject = "AU: run was OK"
    $msg.Body = $Info.stats | Out-String
}
else {
    $context = "with errors "
    $msg.Subject = "AU: $($info.error_count.total) $errors_word during update"
    $msg.Body = @"
<body><pre>
$($Info.error_count.total) $errors_word during update.
$UserMessage
$($info.error_info | Out-String)
</pre></body>
"@
}

$Attachment | % { if ($_) { $msg.Attachments.Add($_)} }

# Send mail message
$smtp = new-object Net.Mail.SmtpClient($Server)
if ($UserName) { $smtp.Credentials = new-object System.Net.NetworkCredential($UserName, $Password) }
if ($Port)     { $smtp.Port = $Port }
$smtp.EnableSsl = $EnableSsl
$smtp.Send($msg)

Write-Host "Mail ${context}sent to $To"
tools\AU\Plugins\Report.ps1
# Author: Miodrag Milic <[email protected]>
# Last Change: 10-Nov-2016.
<#
.SYNOPSIS
    Create different types of reports about the current run.

.DESCRIPTION
    The plugin saves state of all packages in a file that can be used locally or
    uploaded via other plugins to remote (such as Gist or Mail).
#>

param(
    $Info,

    # Type of the report, currently 'markdown' or 'text'
    [string] $Type = 'markdown',

    # Path where to save the report
    [string] $Path = 'Update-AUPackages.md',

    # Report parameters
    [HashTable] $Params
)

Write-Host "Saving $Type report: $Path"

$Type = "$PSScriptRoot\Report\$Type.ps1"
if (!(Test-Path $Type )) { throw "Report type not found: '$Type" }

$result = & $Type
$result | Out-File $Path
tools\AU\Plugins\Report\markdown.ps1
. $PSScriptRoot\markdown_funcs.ps1

$Github_UserRepo = $Params.Github_UserRepo
$UserMessage     = $Params.UserMessage
$NoAppVeyor      = $Params.NoAppVeyor
$IconSize        = if ($Params.IconSize) { $Params.IconSize } else { 32 }
$NoIcons         = $Params.NoIcons
$Title           = if ($Params.Title) { $Params.Title } else {  'Update-AUPackages' }

#=======================================================================================

$now             = $Info.startTime.ToUniversalTime().ToString('yyyy-MM-dd HH:mm')
$au_version      = gmo au -ListAvailable | % Version | select -First 1 | % { "$_" }
$package_no      = $Info.result.all.Length

$update_all_url  = if ($Github_UserRepo) {"https://github.com/$Github_UserRepo/blob/master/update_all.ps1" } else { "https://github.com/majkinetor/au-packages-template/blob/master/update_all.ps1" }

$icon_ok = 'https://cdn.rawgit.com/majkinetor/au/master/AU/Plugins/Report/r_ok.png'
$icon_er = 'https://cdn.rawgit.com/majkinetor/au/master/AU/Plugins/Report/r_er.png'

"# $Title"

#=== Header ===============================
if (!$NoAppVeyor -and $Github_UserRepo) { "[![](https://ci.appveyor.com/api/projects/status/github/${Github_UserRepo}?svg=true)](https://ci.appveyor.com/project/$Github_UserRepo/build/$Env:APPVEYOR_BUILD_NUMBER)" }

@"
[![$package_no](https://img.shields.io/badge/AU%20packages-$($package_no)-red.svg)](#ok)
[![$au_version](https://img.shields.io/badge/AU-$($au_version)-blue.svg)](https://www.powershellgallery.com/packages/AU)
[![](http://transparent-favicon.info/favicon.ico)](#)[![](http://transparent-favicon.info/favicon.ico)](#)
**UTC**: $now [![](http://transparent-favicon.info/favicon.ico)](#) [$Github_UserRepo](https://github.com/$Github_UserRepo)

_This file is automatically generated by the [update_all.ps1]($update_all_url) script using the [AU module](https://github.com/majkinetor/au)._
"@

"`n$UserMessage`n"

#=== Body ===============================

$errors_word = if ($Info.error_count.total -eq 1) {'error'} else {'errors' }
if ($Info.error_count.total) {
    "<img src='$icon_er' width='24'> **LAST RUN HAD $($Info.error_count.total) [$($errors_word.ToUpper())](#errors) !!!**" }
else {
    "<img src='$icon_ok' width='24'> **Last run was OK**"
}

""
md_fix_newline $Info.stats

$columns = 'Icon', 'Name', 'Updated', 'Pushed', 'RemoteVersion', 'NuspecVersion'
if ($NoIcons) { $columns = $columns[1.10] }
if ($Info.pushed) {
    md_title Pushed
    md_table $Info.result.pushed -Columns $columns
}

if ($Info.error_count.total) {
    md_title Errors
    md_table $Info.result.errors -Columns ($columns + 'Error' | ? { ('Updated', 'Pushed') -notcontains $_ } )
    $Info.result.errors | % {
        md_title $_.Name -Level 3
        md_code "$($_.Error)"
    }
}

if ($Info.result.ignored) {
    md_title Ignored
    md_table $Info.result.ignored -Columns 'Icon', 'Name', 'NuspecVersion', 'IgnoreMessage'
}

if ($Info.result.ok) {
    md_title OK
    md_table $Info.result.ok -Columns $columns
    $Info.result.ok | % {
        md_title $_.Name -Level 3
        md_code $_.Result
    }
}
tools\AU\Plugins\Report\markdown_funcs.ps1
function md_fix_newline($Text) {
    $Text -replace "\.`n", "\.`n  " | Out-String
}

function md_title($Title, $Level=2 ) {
    ""
    "#"*$Level + ' ' + $Title | Out-String
    ""
}

function md_code($Text) {
    "`n" + '```'
    ($Text -join "`n").Trim()
    '```' + "`n"
}

function md_table($result, $Columns, $MaxErrorLength=150) {
    if (!$Columns) { $Columns = 'Name', 'Updated', 'Pushed', 'RemoteVersion', 'NuspecVersion', 'Error' }
    $res = '|' + ($Columns -join '|') + "|`r`n"
    $res += ((1..$Columns.Length | % { '|---' }) -join '') + "|`r`n"

    $result | % {
        $o = $_ | select `
                @{ N='Icon'
                   E={'<img src="{0}" width="{1}" height="{1}"/>' -f $_.NuspecXml.package.metadata.iconUrl, $IconSize }
                },
                @{ N='Name'
                   E={'[{0}](https://chocolatey.org/packages/{0}/{1})' -f $_.Name, $(if ($_.Updated) { $_.RemoteVersion } else {$_.NuspecVersion }) }
                },
                @{ N='Updated'
                   E={
                        $r  = "[{0}](#{1})" -f $_.Updated, $_.Name.Replace('.','').ToLower()
                        $r += if ($_.Updated) { ' &#x1F538;' }
                        $r += if ($_.Streams) { ' &#x1F544;' }
                        $r += if (ls $_.Path -Recurse -Include VERIFICATION.txt) { ' &#x1F4E5;' }
                        $r
                    }
                },
                'Pushed',
                @{ N='RemoteVersion'
                   E={"[{0}]({1})" -f $_.RemoteVersion, $_.NuspecXml.package.metadata.projectUrl }
                },
                @{ N='NuspecVersion'
                   E={"[{0}]({1})" -f $_.NuspecVersion, $_.NuspecXml.package.metadata.packageSourceUrl }
                },
                @{ N='Error'
                   E={
                        $err = ("$($_.Error)" -replace "`r?`n", '; ').Trim()
                        if ($err) {
                            if ($err.Length -gt $MaxErrorLength) { $err = $err.Substring(0,$MaxErrorLength) + ' ...' }
                            "[{0}](#{1})" -f $err, $_.Name.ToLower()
                        }
                    }
                }, 
                'Ignored',
                'IgnoreMessage'

        $res += ((1..$Columns.Length | % { $col = $Columns[$_-1]; '|' + $o.$col }) -join '') + "|`r`n"
    }

    $res
}
tools\AU\Plugins\Report\text.ps1
$UserMessage = $Params.UserMessage
$Title       = if ($Params.Title) { $Params.Title } else {  'Update-AUPackages' }

#==============================================================================

function title($txt) { "`r`n{0}`r`n{1}`r`n" -f $txt,('-'*$txt.Length) }
function indent($txt, $level=4) { $txt -split "`n" | % { ' '*$level + $_ } }

$now         = $Info.startTime.ToUniversalTime().ToString('yyyy-MM-dd HH:mm')
$au_version  = gmo au -ListAvailable | % Version | select -First 1 | % { "$_" }
$package_no  = $Info.result.all.Length

"{0,-15}{1}" -f 'Title:', $Title
"{0,-15}{1}" -f 'Time:', $now
"{0,-15}{1}" -f 'AU version:', $au_version
"{0,-15}{1}" -f 'AU packages:', $package_no

$errors_word = if ($Info.error_count.total -eq 1) {'error'} else {'errors' }
if ($Info.error_count.total) {
    "LAST RUN HAD $($Info.error_count.total) $errors_word !!!" }
else {
    "Last run was OK"
}

""; $Info.stats

""; $UserMessage; ""

if ($Info.pushed) {
    title Pushed
    $Info.result.pushed | select 'Name', 'Updated', 'Pushed', 'RemoteVersion', 'NuspecVersion' | ft | Out-String | set r
    indent $r 2

    $Info.result.pushed | % { $_.Name; indent $_.Result; "" }
}

if ($Info.error_count.total) {
    title Errors
    $Info.result.errors | select 'Name', 'NuspecVersion', 'Error' | ft | Out-String | set r
    indent $r 2

    $Info.result.errors | % { $_.Name; indent $_.Error; "" }
}


if ($Info.result.ignored) {
    title Ignored
    $Info.result.ignored | ft | select 'Name', 'NuspecVersion', 'IgnoreMessage' | ft | Out-String | set r
    indent $r 2
}

$ok = $Info.result.ok | ? { !$_.Pushed }
if ($ok) {
    title OK
    $ok | select 'Name', 'Updated', 'RemoteVersion', 'NuspecVersion' | ft | Out-String | set r
    indent $r 2

    $ok | % { $_.Name; indent $_.Result; "" }
}

tools\AU\Plugins\RunInfo.ps1
# Author: Miodrag Milic <[email protected]>
# Last Change: 21-Sep-2016.

<#
.SYNOPSIS
    Save run info to the file and exclude sensitive information.

.DESCRIPTION
    Run this plugin as the last one to save all other info produced during the run.
    To load it for inspection use `$info = Import-CliXml update_info.xml`.
#>
param(
    $Info,

    #Path to XML file to save
    [string] $Path = 'update_info.xml',

    #Match options with those words to erase
    [string[]] $Exclude = @('password', 'apikey')
)

function deep_clone {
    param($DeepCopyObject)

    $memStream = new-object IO.MemoryStream
    $formatter = new-object Runtime.Serialization.Formatters.Binary.BinaryFormatter
    $formatter.Serialize($memStream,$DeepCopyObject)
    $memStream.Position=0
    $formatter.Deserialize($memStream)
}

# Runinfo must save its own run results directly in Info
function result($msg) { $Info.plugin_results.RunInfo += $msg; Write-Host $msg }

$Info.plugin_results.RunInfo = @()
$format = '{0,-15}{1}'

$orig_opts = $Info.Options
$opts      = deep_clone $orig_opts
$excluded  = ''
foreach ($w in $Exclude) {
    foreach ($key in $Info.Options.Keys) {
        if ($Info.Options.$key -is [HashTable]) {
            foreach ($subkey in $Info.Options.$key.Keys) {
                if ($subkey -like "*$w*") {
                    $excluded += "$key.$subkey "
                    $opts.$key.$subkey = '*****'
                }
            }
        }
    }
}

if ($excluded) { result ($format -f 'Excluded:', $excluded) }
result ($format -f 'File:', $Path)
$Info.Options = $opts
$Info | Export-CliXML $Path
$Info.Options = $orig_opts
tools\AU\Plugins\Snippet.ps1
# Author: dimqua <[email protected]>
# Last Change: 11-Oct-2018.
<#
.SYNOPSIS
    Upload update history report to Gitlab snippet.

.DESCRIPTION
    Plugin uploads update history report (created by Report plugin) to the snippet with the given id and filename. You can use gitlab.com instance (default) or self-hosted one.
#>
param(
    $Info,

    # Snippet id
    [string] $Id,

    # Gitlab API Token, create in User Settings -> Access Tokens -> Create personal access token
    # Make sure token has 'api' scope.
    [string] $ApiToken,

    # File paths to attach to snippet
    [string[]] $Path,

    # Snippet file name
    [string] $FileName = 'Update-AUPackages.md',

    # GitLab instance's (sub)domain name
    [string] $Domain = 'gitlab.com'

)

# Create snippet
ls $Path | % {
    $file_name = Split-Path $_ -Leaf
    $content = gc $_ -Raw
    $snippet = '{"content": "' + $content + '"}'
    }

$params = @{
    ContentType = 'application/json'
    Method      = "PUT"
    Uri         = "https://$Domain/api/v4/snippets/$Id"
    Body        = ($snippet | ConvertTo-Json).replace('"{\"content\": \"','{"content": "').replace('\"}"','"') + ', "file_name": "' + $FileName + '"}'
    Headers = @{ 'PRIVATE-TOKEN'=$ApiToken }
}

# Request
$res = Invoke-WebRequest @params
"https://$Domain/snippets/$Id"
tools\AU\Private\AUPackage.ps1
class AUPackage {
    [string]   $Path
    [string]   $Name
    [bool]     $Updated
    [bool]     $Pushed
    [string]   $RemoteVersion
    [string]   $NuspecVersion
    [string[]] $Result
    [string]   $Error
    [string]   $NuspecPath
    [xml]      $NuspecXml
    [bool]     $Ignored
    [string]   $IgnoreMessage
    [string]   $StreamsPath
    [System.Collections.Specialized.OrderedDictionary] $Streams

    AUPackage([string] $Path ){
        if ([String]::IsNullOrWhiteSpace( $Path )) { throw 'Package path can not be empty' }

        $this.Path = $Path
        $this.Name = Split-Path -Leaf $Path

        $this.NuspecPath = '{0}\{1}.nuspec' -f $this.Path, $this.Name
        if (!(gi $this.NuspecPath -ea ignore)) { throw 'No nuspec file found in the package directory' }

        $this.NuspecXml     = [AUPackage]::LoadNuspecFile( $this.NuspecPath )
        $this.NuspecVersion = $this.NuspecXml.package.metadata.version

        $this.StreamsPath = '{0}\{1}.json' -f $this.Path, $this.Name
        $this.Streams     = [AUPackage]::LoadStreams( $this.StreamsPath )
    }

    [hashtable] GetStreamDetails() {
        return @{
            Path          = $this.Path
            Name          = $this.Name
            Updated       = $this.Updated
            RemoteVersion = $this.RemoteVersion
        }
    }

    static [xml] LoadNuspecFile( $NuspecPath ) {
        $nu = New-Object xml
        $nu.PSBase.PreserveWhitespace = $true
        $nu.Load($NuspecPath)
        return $nu
    }

    SaveNuspec(){
        $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
        [System.IO.File]::WriteAllText($this.NuspecPath, $this.NuspecXml.InnerXml, $Utf8NoBomEncoding)
    }

    static [System.Collections.Specialized.OrderedDictionary] LoadStreams( $streamsPath ) {
        if (!(Test-Path $streamsPath)) { return $null }
        $res = [System.Collections.Specialized.OrderedDictionary] @{}
        $versions = Get-Content $streamsPath | ConvertFrom-Json
        $versions.psobject.Properties | % {
            $stream = $_.Name
            $res.Add($stream, @{ NuspecVersion = $versions.$stream })
        }
        return $res
    }

    UpdateStream( $stream, $version ){
        $s = $stream.ToString()
        $v = $version.ToString()
        if (!$this.Streams) { $this.Streams = [System.Collections.Specialized.OrderedDictionary] @{} }
        if (!$this.Streams.Contains($s)) { $this.Streams.$s = @{} }
        if ($this.Streams.$s -ne 'ignore') { $this.Streams.$s.NuspecVersion = $v }
        $versions = [System.Collections.Specialized.OrderedDictionary] @{}
        $this.Streams.Keys | % {
            $versions.Add($_, $this.Streams.$_.NuspecVersion)
        }
        $versions | ConvertTo-Json | Set-Content $this.StreamsPath -Encoding UTF8
    }

    Backup()  { 
        $d = "$Env:TEMP\au\" + $this.Name

        rm $d\* -Recurse -ea 0
        cp . $d\_backup -Recurse 
    }

    [string] SaveAndRestore() { 
        $d = "$Env:TEMP\au\" + $this.Name

        cp . $d\_output -Recurse 
        rm .\* -Recurse
        cp $d\_backup\* . -Recurse 
        
        return "$d\_output"
    }

    AUPackage( [hashtable] $obj ) {
        if (!$obj) { throw 'Obj can not be empty' }
        $obj.Keys | ? { $_ -ne 'Streams' } | % {
            $this.$_ = $obj.$_
        }
        if ($obj.Streams) {
            $this.Streams = [System.Collections.Specialized.OrderedDictionary] @{}
            $obj.Streams.psobject.Properties | % {
                $this.Streams.Add($_.Name, $_.Value)
            }
        }
    }

    [hashtable] Serialize() {
        $res = @{}
        $this | Get-Member -Type Properties | ? { $_.Name -ne 'Streams' } | % {
            $property = $_.Name
            $res.Add($property, $this.$property)
        }
        if ($this.Streams) {
            $res.Add('Streams', [PSCustomObject] $this.Streams)
        }
        return $res
    }
}
tools\AU\Private\AUVersion.ps1
class AUVersion : System.IComparable {
    [version] $Version
    [string] $Prerelease
    [string] $BuildMetadata

    AUVersion([version] $version, [string] $prerelease, [string] $buildMetadata) {
        if (!$version) { throw 'Version cannot be null.' }
        $this.Version = $version
        $this.Prerelease = $prerelease
        $this.BuildMetadata = $buildMetadata
    }

    AUVersion($input) {
        if (!$input) { throw 'Input cannot be null.' }
        $v = [AUVersion]::Parse($input -as [string])
        $this.Version = $v.Version
        $this.Prerelease = $v.Prerelease
        $this.BuildMetadata = $v.BuildMetadata
    }

    static [AUVersion] Parse([string] $input) { return [AUVersion]::Parse($input, $true) }

    static [AUVersion] Parse([string] $input, [bool] $strict) {
        if (!$input) { throw 'Version cannot be null.' }
        $reference = [ref] $null
        if (![AUVersion]::TryParse($input, $reference, $strict)) { throw "Invalid version: $input." }
        return $reference.Value
    }

    static [bool] TryParse([string] $input, [ref] $result) { return [AUVersion]::TryParse($input, $result, $true) }

    static [bool] TryParse([string] $input, [ref] $result, [bool] $strict) {
        $result.Value = [AUVersion] $null
        if (!$input) { return $false }
        $pattern = [AUVersion]::GetPattern($strict)
        if ($input -notmatch $pattern) { return $false }
        $reference = [ref] $null
        if (![version]::TryParse($Matches['version'], $reference)) { return $false }
        $pr = $Matches['prerelease']
        $bm = $Matches['buildMetadata']
        if ($pr -and !$strict) { $pr = $pr.Replace(' ', '.') }
        if ($bm -and !$strict) { $bm = $bm.Replace(' ', '.') }
        # for now, chocolatey does only support SemVer v1 (no dot separated identifiers in pre-release):
        if ($pr -and $strict -and $pr -like '*.*') { return $false }
        if ($bm -and $strict -and $bm -like '*.*') { return $false }
        if ($pr) { $pr = $pr.Replace('.', '') }
        if ($bm) { $bm = $bm.Replace('.', '') }
        #
        $result.Value = [AUVersion]::new($reference.Value, $pr, $bm)
        return $true
    }

    hidden static [string] GetPattern([bool] $strict) {
        $versionPattern = '(?<version>\d+(?:\.\d+){1,3})'
        if ($strict) {
            $identifierPattern = "[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*"
            return "^$versionPattern(?:-(?<prerelease>$identifierPattern))?(?:\+(?<buildMetadata>$identifierPattern))?`$"
        } else {
            $identifierPattern = "[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+| \d+)*"
            return "$versionPattern(?:[- ]*(?<prerelease>$identifierPattern))?(?:[+ *](?<buildMetadata>$identifierPattern))?"
        }
    }

    [AUVersion] WithVersion([version] $version) { return [AUVersion]::new($version, $this.Prerelease, $this.BuildMetadata) }

    [int] CompareTo($obj) {
        if ($obj -eq $null) { return 1 }
        if ($obj -isnot [AUVersion]) { throw "AUVersion expected: $($obj.GetType())" }
        $t = $this.GetParts()
        $o = $obj.GetParts()
        for ($i = 0; $i -lt $t.Length -and $i -lt $o.Length; $i++) {
            if ($t[$i].GetType() -ne $o[$i].GetType()) {
                $t[$i] = [string] $t[$i]
                $o[$i] = [string] $o[$i]
            }
            if ($t[$i] -gt $o[$i]) { return 1 }
            if ($t[$i] -lt $o[$i]) { return -1 }
        }
        if ($t.Length -eq 1 -and $o.Length -gt 1) { return 1 }
        if ($o.Length -eq 1 -and $t.Length -gt 1) { return -1 }
        if ($t.Length -gt $o.Length) { return 1 }
        if ($t.Length -lt $o.Length) { return -1 }
        return 0
    }

    [bool] Equals($obj) { return $this.CompareTo($obj) -eq 0 }

    [int] GetHashCode() { return $this.GetParts().GetHashCode() }

    [string] ToString() {
        $result = $this.Version.ToString()
        if ($this.Prerelease) { $result += "-$($this.Prerelease)" }
        if ($this.BuildMetadata) { $result += "+$($this.BuildMetadata)" }
        return $result
    }

    [string] ToString([int] $fieldCount) {
        if ($fieldCount -eq -1) { return $this.Version.ToString() }
        return $this.Version.ToString($fieldCount)
    }

    hidden [object[]] GetParts() {
        $result = @($this.Version)
        if ($this.Prerelease) {
            $this.Prerelease -split '\.' | % {
                # if identifier is exclusively numeric, cast it to an int
                if ($_ -match '^[0-9]+$') {
                    $result += [int] $_
                } else {
                    $result += $_
                }
            }
        }
        return $result
    }
}

function ConvertTo-AUVersion($Version) {
    return [AUVersion] $Version
}
tools\AU\Private\check_url.ps1
# Returns nothing if url is valid, error otherwise
function check_url( [string] $Url, [int]$Timeout, $ExcludeType='text/html', $Options ) {
    if (!(is_url $Url)) { return "URL syntax is invalid" }

    try
    {
        $response = request $url $Timeout -Options $Options
        if ($response.ContentType -like "*${ExcludeType}*") { return "Bad content type '$ExcludeType'" }
    }
    catch {
        return "Can't validate URL`n$_"
    }
}
tools\AU\Private\is_url.ps1
# Returns [bool]
function is_url([string] $Url ) {
    [Uri]::IsWellFormedUriString($URL, [UriKind]::Absolute)
}
tools\AU\Private\is_version.ps1
# Returns [bool]
function is_version( [string] $Version ) {
    return [AUVersion]::TryParse($Version, [ref]($__))
}
tools\AU\Private\request.ps1
function request( [string]$Url, [int]$Timeout, $Options ) {
    if ([string]::IsNullOrWhiteSpace($url)) {throw 'The URL is empty'}
    $request = [System.Net.WebRequest]::Create($Url)
    if ($Timeout)  { $request.Timeout = $Timeout*1000 }
 
    if ($Options.Headers) { 
        $Options.Headers.Keys | % { 
            if ([System.Net.WebHeaderCollection]::IsRestricted($_)) {
                $key = $_.Replace('-','')
                $request.$key = $Options.Headers[$_]
            }
            else { 
                $request.Headers.add($_, $Options.Headers[$_])
            }
        }
    }
    
    $response = $request.GetResponse()
    $response.Close()
    $response
}
tools\AU\Public\Get-AUPackages.ps1
# Author: Miodrag Milic <[email protected]>
# Last Change: 12-Nov-2016.

<#
.SYNOPSIS
    Get AU packages

.DESCRIPTION

    Returns list of directories that have update.ps1 script in them and package name
    doesn't start with the '_' char (unpublished packages, not considered by Update-AUPackages
    function).

    Function looks in the directory pointed to by the global variable $au_root or, if not set, 
    the current directory.

.EXAMPLE
    gau p*

    Get all automatic packages that start with 'p' in the current directory.

.EXAMPLE
    $au_root = 'c:\packages'; lsau 'cpu-z*','p*','copyq'

    Get all automatic packages  in the directory 'c:\packages' that start with 'cpu-z' or 'p' and package which name is 'copyq'.
#>
function Get-AUPackages( [string[]] $Name ) {
    $root = $global:au_root
    if (!$root) { $root = $pwd }

    ls $root\*\update.ps1 | % {
        $packageDir = gi (Split-Path $_)

        if ($Name -and $Name.Length -gt 0) {
            $m = $Name | ? { $packageDir.Name -like $_ }
            if (!$m) { return }
        }

        if ($packageDir.Name -like '_*') { return }
        $packageDir
    }
}

Set-Alias gau  Get-AuPackages
Set-Alias lsau Get-AuPackages
tools\AU\Public\Get-RemoteChecksum.ps1
# Author: Miodrag Milic <[email protected]>
# Last Change: 26-Nov-2016.

<#
.SYNOPSIS
    Download file from internet and calculate its checksum

#>
function Get-RemoteChecksum( [string] $Url, $Algorithm='sha256', $Headers ) {
    $fn = [System.IO.Path]::GetTempFileName()
    Invoke-WebRequest $Url -OutFile $fn -UseBasicParsing -Headers $Headers
    $res = Get-FileHash $fn -Algorithm $Algorithm | % Hash
    rm $fn -ea ignore
    return $res.ToLower()
}

tools\AU\Public\Get-RemoteFiles.ps1
# Author: Miodrag Milic <[email protected]>
# Last Change: 22-Feb-2017.

<#
.SYNOPSIS
   Get Latest URL32 and/or URL64 into tools directory.

.DESCRIPTION
   This function will download the binaries pointed to by $Latest.URL32 and $Latest.URL64.
   The function is used to embed binaries into the Chocolatey package.

   The function will keep original remote file name but it will add suffix _x32 or _x64.
   This is intentional because you can use those to match particular installer via wildcards,
   e.g. `gi *_x32.exe`.

#>
function Get-RemoteFiles {
    param (
        # Delete existing file having $Latest.FileType extension.
        # Otherwise, when state of the package remains after the update, older installers
        # will pile up and may get included in the updated package.
        [switch] $Purge,

        # Do not add arch suffix (_x32 or _64) at the end of the filename
        [switch] $NoSuffix,

        # Override remote file name, use this one as a base. Suffixes _x32/_x64 are added.
        # Use this parameter if remote URL doesn't contain file name but generated hash.
        [string] $FileNameBase,

        # By default last URL part is used as a file name. Use this paramter to skip parts
        # if file name is specified earlier in the path.
        [int]    $FileNameSkip=0,

        # Sets the algorithm to use when calculating checksums
        # This defaults to sha256
        [ValidateSet('md5','sha1','sha256','sha384','sha512')]
        [string] $Algorithm = 'sha256'
    )

    function name4url($url) {
        if ($FileNameBase) { return $FileNameBase }
        $res = $url -split '/' | select -Last 1 -Skip $FileNameSkip
        $res -replace '\.[^.]+$'
    }

    function ext() {
        if ($Latest.FileType) { return $Latest.FileType }
        $url = $Latest.Url32; if (!$url) { $url = $Latest.Url64 }
        if ($url -match '(?<=\.)[^.]+$') { return $Matches[0] }
    }

    mkdir tools -ea 0 | Out-Null
    $toolsPath = Resolve-Path tools
    
    $ext = ext
    if (!$ext) { throw 'Unknown file type' }

    if ($Purge) {
        Write-Host 'Purging' $ext
        rm -Force "$toolsPath\*.$ext" -ea ignore
    }

    try {
        $client = New-Object System.Net.WebClient

        if ($Latest.Url32) {
            $base_name = name4url $Latest.Url32
            $file_name = "{0}{2}.{1}" -f $base_name, $ext, $(if ($NoSuffix) { '' } else {'_x32'})
            $file_path = "$toolsPath\$file_name"

            Write-Host "Downloading to $file_name -" $Latest.Url32
            $client.DownloadFile($Latest.URL32, $file_path)
            $global:Latest.Checksum32 = Get-FileHash $file_path -Algorithm $Algorithm | % Hash
            $global:Latest.ChecksumType32 = $Algorithm
            $global:Latest.FileName32 = $file_name
        }

        if ($Latest.Url64) {
            $base_name = name4url $Latest.Url64
            $file_name = "{0}{2}.{1}" -f $base_name, $ext, $(if ($NoSuffix) { '' } else {'_x64'})
            $file_path = "$toolsPath\$file_name"

            Write-Host "Downloading to $file_name -" $Latest.Url64
            $client.DownloadFile($Latest.URL64, $file_path)
            $global:Latest.Checksum64 = Get-FileHash $file_path -Algorithm $Algorithm | % Hash
            $global:Latest.ChecksumType64 = $Algorithm
            $global:Latest.FileName64 = $file_name
        }
    } catch{ throw $_ } finally { $client.Dispose() }
}
tools\AU\Public\Get-Version.ps1
# Author: Thomas Démoulins <[email protected]>

<#
.SYNOPSIS
    Parses a semver-like object from a string in a flexible manner.

.DESCRIPTION
    This function parses a string containing a semver-like version
    and returns an object that represents both the version (with up to 4 parts)
    and optionally a pre-release and a build metadata.

    The parsing is quite flexible:
    - the version can be in the middle of a url or sentence
    - first version found is returned
    - there can be no hyphen between the version and the pre-release
    - extra spaces are ignored
    - optional delimiters can be provided to help parsing the string

.EXAMPLE
    Get-Version 'Last version: 1.2.3 beta 3.'

    Returns 1.2.3-beta3

.EXAMPLE
    Get-Version 'https://github.com/atom/atom/releases/download/v1.24.0-beta2/AtomSetup.exe'

    Return 1.24.0-beta2

.EXAMPLE
    Get-Version 'http://mirrors.kodi.tv/releases/windows/win32/kodi-17.6-Krypton-x86.exe' -Delimiter '-'

    Return 17.6
#>
function Get-Version {
    [CmdletBinding()]
    param(
        # Version string to parse.
        [Parameter(Mandatory=$true)]
        [string] $Version,
        # Optional delimiter(s) to help locate the version in the string: the version must start and end with one of these chars.
        [char[]] $Delimiter
    )
    if ($Delimiter) {
        $delimiters = $Delimiter -join ''
        @('\', ']', '^', '-') | % { $delimiters = $delimiters.Replace($_, "\$_") }
        $regex = $Version | Select-String -Pattern "[$delimiters](\d+\.\d+[^$delimiters]*)[$delimiters]" -AllMatches
        foreach ($match in $regex.Matches) {
            $reference = [ref] $null
            if ([AUVersion]::TryParse($match.Groups[1], $reference, $false)) {
                return $reference.Value
            }
        }
    }
    return [AUVersion]::Parse($Version, $false)
}
tools\AU\Public\Push-Package.ps1
# Author: Miodrag Milic <[email protected]>
# Last Change: 22-Oct-2016.

<#
.SYNOPSIS
    Push latest (or all) created package(s) to the Chocolatey community repository.

.DESCRIPTION
    The function uses they API key from the file api_key in current or parent directory, environment variable
    or cached nuget API key.
#>
function Push-Package() {
    param(
        [switch] $All
    )
    $api_key =  if (Test-Path api_key) { gc api_key }
                elseif (Test-Path ..\api_key) { gc ..\api_key }
                elseif ($Env:api_key) { $Env:api_key }

    $push_url =  if ($Env:au_PushUrl) { $Env:au_PushUrl }
                 else { 'https://push.chocolatey.org' }

    $packages = ls *.nupkg | sort -Property CreationTime -Descending
    if (!$All) { $packages = $packages | select -First 1 }
    if (!$packages) { throw 'There is no nupkg file in the directory'}
    if ($api_key) {
        $packages | % { cpush $_.Name --api-key $api_key --source $push_url }
    } else {
        $packages | % { cpush $_.Name --source $push_url }
    }
}
tools\AU\Public\Set-DescriptionFromReadme.ps1
<#
.SYNOPSIS
  Updates nuspec file description from README.md

.DESCRIPTION
  This script should be called in au_AfterUpdate to put the text in the README.md
  into description tag of the Nuspec file. The current description will be replaced.
  
  You need to call this function manually only if you want to pass it custom parameters.
  In that case use NoReadme parameter of the Update-Package.

.EXAMPLE
  function global:au_AfterUpdate  { Set-DescriptionFromReadme -Package $args[0] -SkipLast 2 -SkipFirst 2 }
#>
function Set-DescriptionFromReadme{
    param(
      [AUPackage] $Package,
      # Number of start lines to skip from the README.md, by default 0.
      [int] $SkipFirst=0, 
      # Number of end lines to skip from the README.md, by default 0.
      [int] $SkipLast=0,
      # Readme file path
      [string] $ReadmePath = 'README.md'
    )

    "Setting package description from $ReadmePath"

    $description = gc $ReadmePath -Encoding UTF8
    $endIdx = $description.Length - $SkipLast
    $description = $description | select -Index ($SkipFirst..$endIdx) | Out-String

    $cdata = $Package.NuspecXml.CreateCDataSection($description)
    $xml_Description = $Package.NuspecXml.GetElementsByTagName('description')[0]
    $xml_Description.RemoveAll()
    $xml_Description.AppendChild($cdata) | Out-Null

    $Package.SaveNuspec()
}
tools\AU\Public\Test-Package.ps1
# Author: Miodrag Milic <[email protected]>
# Last Change: 15-Nov-2016.

<#
.SYNOPSIS
    Test Chocolatey package

.DESCRIPTION
    The function can test install, uninistall or both and provide package parameters during test.
    It will force install and then remove the Chocolatey package if called without arguments.

    It accepts either nupkg or nuspec path. If none specified, current directory will be searched
    for any of them.

.EXAMPLE
    Test-Package -Install

    Test the install of the package from the current directory.

.LINK
    https://github.com/chocolatey/choco/wiki/CreatePackages#testing-your-package
#>
function Test-Package {
    param(
        # If file, path to the .nupkg or .nuspec file for the package.
        # If directory, latest .nupkg or .nuspec file wil be looked in it.
        # If ommited current directory will be used.
        $Nu,

        # Test chocolateyInstall.ps1 only.
        [switch] $Install,

        # Test chocolateyUninstall.ps1 only.
        [switch] $Uninstall,

        # Package parameters
        [string] $Parameters,

        # Path to chocolatey-test-environment: https://github.com/majkinetor/chocolatey-test-environment
        [string] $Vagrant = $Env:au_Vagrant,

        # Open new shell window
        [switch] $VagrantOpen,

        # Do not remove existing packages from vagrant package directory
        [switch] $VagrantNoClear
    )

    if (!$Install -and !$Uninstall) { $Install = $true }

    if (!$Nu) { $dir = gi $pwd }
    else {
        if (!(Test-Path $Nu)) { throw "Path not found: $Nu" }
        $Nu = gi $Nu
        $dir = if ($Nu.PSIsContainer) { $Nu; $Nu = $null } else { $Nu.Directory }
    }

    if (!$Nu) {
        $Nu = gi $dir/*.nupkg | sort -Property CreationTime -Descending | select -First 1
        if (!$Nu) { $Nu = gi $dir/*.nuspec }
        if (!$Nu) { throw "Can't find nupkg or nuspec file in the directory" }
    }

    if ($Nu.Extension -eq '.nuspec') {
        Write-Host "Nuspec file given, running choco pack"
        choco pack -r $Nu.FullName --OutputDirectory $Nu.DirectoryName | Write-Host
        if ($LASTEXITCODE -ne 0) { throw "choco pack failed with $LastExitCode"}
        $Nu = gi "$($Nu.DirectoryName)\*.nupkg" | sort -Property CreationTime -Descending | select -First 1
    } elseif ($Nu.Extension -ne '.nupkg') { throw "File is not nupkg or nuspec file" }

    #At this point Nu is nupkg file

    $package_name    = $Nu.Name -replace '(\.\d+)+(-[^-]+)?\.nupkg$'
    $package_version = ($Nu.BaseName -replace $package_name).Substring(1)

    Write-Host "`nPackage info"
    Write-Host "  Path:".PadRight(15)      $Nu
    Write-Host "  Name:".PadRight(15)      $package_name
    Write-Host "  Version:".PadRight(15)   $package_version
    if ($Parameters) { Write-Host "  Parameters:".PadRight(15) $Parameters }
    if ($Vagrant)    { Write-Host "  Vagrant: ".PadRight(15) $Vagrant }

    if ($Vagrant) {
        Write-Host "`nTesting package using vagrant"

        if (!$VagrantNoClear)  {
            Write-Host 'Removing existing vagrant packages'
            rm $Vagrant\packages\*.nupkg -ea ignore
            rm $Vagrant\packages\*.xml   -ea ignore
        }

        cp $Nu $Vagrant\packages
        $options_file = "$package_name.$package_version.xml"
        @{ Install = $Install; Uninstall = $Uninstall; Parameters = $Parameters } | Export-CliXML "$Vagrant\packages\$options_file"
        if ($VagrantOpen) {
            start powershell -Verb Open -ArgumentList "-NoProfile -NoExit -Command `$Env:http_proxy=`$Env:https_proxy=`$Env:ftp_proxy=`$Env:no_proxy=''; cd $Vagrant; vagrant up"
        } else {
            powershell -NoProfile -Command "`$Env:http_proxy=`$Env:https_proxy=`$Env:ftp_proxy=`$Env:no_proxy=''; cd $Vagrant; vagrant up"
        }
        return
    }

    if ($Install) {
        Write-Host "`nTesting package install"
        choco install -y -r $package_name --version $package_version --source "'$($Nu.DirectoryName);https://chocolatey.org/api/v2/'" --force --packageParameters "'$Parameters'" | Write-Host
        if ($LASTEXITCODE -ne 0) { throw "choco install failed with $LastExitCode"}
    }

    if ($Uninstall) {
        Write-Host "`nTesting package uninstall"
        choco uninstall -y -r $package_name | Write-Host
        if ($LASTEXITCODE -ne 0) { throw "choco uninstall failed with $LastExitCode"}
    }
}
tools\AU\Public\Update-AUPackages.ps1

 # Author: Miodrag Milic <[email protected]>
 # Last Change: 08-May-2018

<#
.SYNOPSIS
    Update all automatic packages

.DESCRIPTION
    Function Update-AUPackages will iterate over update.ps1 scripts and execute each. If it detects
    that a package is updated it will push it to the Chocolatey community repository.

    The function will look for AU packages in the directory pointed to by the global variable au_root
    or in the current directory if mentioned variable is not set.

    For the push to work, specify your API key in the file 'api_key' in the script's directory or use
    cached nuget API key or set environment variable '$Env:api_key'.

    The function accepts many options via ordered HashTable parameter Options.

.EXAMPLE
    Update-AUPackages p* @{ Threads = 5; Timeout = 10 }

    Update all automatic packages in the current directory that start with letter 'p' using 5 threads
    and web timeout of 10 seconds.

.EXAMPLE
    $au_root = 'c:\chocolatey'; updateall @{ Force = $true }

    Force update of all automatic ackages in the given directory.

.LINK
    Update-Package

.OUTPUTS
    AUPackage[]
#>
function Update-AUPackages {
    [CmdletBinding()]
    param(
        # Filter package names. Supports globs.
        [string[]] $Name,

        <#
        Hashtable with options:
          Threads           - Number of background jobs to use, by default 10.
          Timeout           - WebRequest timeout in seconds, by default 100.
          UpdateTimeout     - Timeout for background job in seconds, by default 1200 (20 minutes).
          Force             - Force package update even if no new version is found.
          Push              - Set to true to push updated packages to Chocolatey community repository.
          PushAll           - Set to true to push all updated packages and not only the most recent one per folder.
          WhatIf            - Set to true to set WhatIf option for all packages.
          PluginPath        - Additional path to look for user plugins. If not set only module integrated plugins will work
          NoCheckChocoVersion  - Set to true to set NoCheckChocoVersion option for all packages.

          Plugin            - Any HashTable key will be treated as plugin with the same name as the option name.
                              A script with that name will be searched for in the AU module path and user specified path.
                              If script is found, it will be called with splatted HashTable passed as plugin parameters.

                              To list default AU plugins run:

                                    ls "$(Split-Path (gmo au -list).Path)\Plugins\*.ps1"
          IgnoreOn          - Array of strings, error messages that packages will get ignored on
          RepeatOn          - Array of strings, error messages that package updaters will run again on
          RepeatCount       - Number of repeated runs to do when given error occurs, by default 1
          RepeatSleep       - How long to sleep between repeast, by default 0

          BeforeEach        - User ScriptBlock that will be called before each package and accepts 2 arguments: Name & Options.
                              To pass additional arguments, specify them as Options key/values.
          AfterEach         - Similar as above.
          Script            - Script that will be called before and after everything.
        #>
        [System.Collections.Specialized.OrderedDictionary] [email protected]{},

        #Do not run plugins, defaults to global variable `au_NoPlugins`.
        [switch] $NoPlugins = $global:au_NoPlugins
    )

    $startTime = Get-Date

    if (!$Options.Threads)      { $Options.Threads       = 10 }
    if (!$Options.Timeout)      { $Options.Timeout       = 100 }
    if (!$Options.UpdateTimeout){ $Options.UpdateTimeout = 1200 }
    if (!$Options.Force)        { $Options.Force         = $false }
    if (!$Options.Push)         { $Options.Push          = $false }
    if (!$Options.PluginPath)   { $Options.PluginPath    = '' }
    if (!$Options.NoCheckChocoVersion){ $Options.NoCheckChocoVersion	= $false }

    Remove-Job * -force #remove any previously run jobs

    $tmp_dir = "$ENV:Temp\chocolatey\au"
    mkdir -ea 0 $tmp_dir | Out-Null
    ls $tmp_dir | ? PSIsContainer -eq $false | rm   #clear tmp dir files

    $aup = Get-AUPackages $Name
    Write-Host 'Updating' $aup.Length  'automatic packages at' $($startTime.ToString("s") -replace 'T',' ') $(if ($Options.Force) { "(forced)" } else {})
    Write-Host 'Push is' $( if ($Options.Push) { 'enabled' } else { 'disabled' } )
    Write-Host 'NoCheckChocoVersion is' $( if ($Options.NoCheckChocoVersion) { 'enabled' } else { 'disabled' } )
    if ($Options.Force) { Write-Host 'FORCE IS ENABLED. All packages will be updated' }

    $script_err = 0
    if ($Options.Script) { try { & $Options.Script 'START' $aup | Write-Host } catch { Write-Error $_; $script_err += 1 } }

    $threads = New-Object object[] $Options.Threads
    $result  = @()
    $j = $p  = 0
    while( $p -ne $aup.length ) {

        # Check for completed jobs
        foreach ($job in (Get-Job | ? state -ne 'Running')) {
            $p += 1

            if ( 'Stopped', 'Failed', 'Completed' -notcontains $job.State) { 
                Write-Host "Invalid job state for $($job.Name): " $job.State
            }
            else {
                Write-Verbose ($job.State + ' ' + $job.Name)

                if ($job.ChildJobs[0].JobStateInfo.Reason.Message) {
                    $pkg = [AUPackage]::new((Get-AuPackages $job.Name))
                    $pkg.Error = $job.ChildJobs[0].JobStateInfo.Reason.Message
                } else {
                    $pkg = $null
                    Receive-Job $job | set pkg

                    $ignored = $pkg -eq 'ignore'
                    if ( !$pkg -or $ignored ) {
                        $pkg = [AUPackage]::new( (Get-AuPackages $($job.Name)) )

                        if ($ignored) {
                            $pkg.Result = @('ignored', '') + (gc "$tmp_dir\$($pkg.Name)" -ea 0)
                            $pkg.Ignored = $true
                            $pkg.IgnoreMessage = $pkg.Result[-1]
                        } elseif ($job.State -eq 'Stopped') {
                            $pkg.Error = "Job terminated due to the $($Options.UpdateTimeout)s UpdateTimeout"
                        } else {
                            $pkg.Error = 'Job returned no object, Vector smash ?'
                        }
                    } else {
                        $pkg = [AUPackage]::new($pkg)
                    }
                }
                Remove-Job $job

                $jobseconds = ($job.PSEndTime.TimeOfDay - $job.PSBeginTime.TimeOfDay).TotalSeconds
                $message = "[$($p)/$($aup.length)] " + $pkg.Name + ' '
                $message += if ($pkg.Updated) { 'is updated to ' + $pkg.RemoteVersion } else { 'has no updates' }
                if ($pkg.Updated -and $Options.Push) {
                    $message += if (!$pkg.Pushed) { ' but push failed!' } else { ' and pushed'}
                }
                if ($pkg.Error) {
                    $message = "[$($p)/$($aup.length)] $($pkg.Name) ERROR: "
                    $message += $pkg.Error.ToString() -split "`n" | % { "`n" + ' '*5 + $_ }
                }
                $message+= " ({0:N2}s)" -f $jobseconds
                Write-Host '  ' $message

                $result += $pkg
            }
        }

        # Sleep a bit and check for running tasks update timeout
        $job_count = Get-Job | measure | % count
        if (($job_count -eq $Options.Threads) -or ($j -eq $aup.Length)) {
            sleep 1
            foreach ($job in $(Get-Job -State Running)) {
               $elapsed = ((get-date) - $job.PSBeginTime).TotalSeconds
               if ($elapsed -ge $Options.UpdateTimeout) { Stop-Job $job }
            }
            continue
        }

        # Start a new thread
        $package_path = $aup[$j++]
        $package_name = Split-Path $package_path -Leaf
        Write-Verbose "Starting $package_name"
        Start-Job -Name $package_name {         #TODO: fix laxxed variables in job for BE and AE
            function repeat_ignore([ScriptBlock] $Action) { # requires $Options
                $run_no = 0
                $run_max = if ($Options.RepeatOn) { if (!$Options.RepeatCount) { 2 } else { $Options.RepeatCount+1 } } else {1}

                :main while ($run_no -lt $run_max) {
                    $run_no++
                    try {
                        $res = & $Action 6> $out
                        break main
                    } catch {
                        if ($run_no -ne $run_max) {
                            foreach ($msg in $Options.RepeatOn) { 
                                if ($_.Exception -notlike "*${msg}*") { continue }
                                Write-Warning "Repeating $using:package_name ($run_no): $($_.Exception)"
                                if ($Options.RepeatSleep) { Write-Warning "Sleeping $($Options.RepeatSleep) seconds before repeating"; sleep $Options.RepeatSleep }
                                continue main
                            }
                        }
                        foreach ($msg in $Options.IgnoreOn) { 
                            if ($_.Exception -notlike "*${msg}*") { continue }
                            Write-Warning "Ignoring $using:package_name ($run_no): $($_.Exception)"
                            "AU ignored on: $($_.Exception)" | Out-File -Append $out
                            $res = 'ignore'
                            break main
                        }
                        $type = if ($res) { $res.GetType() }
                        if ( "$type" -eq 'AUPackage') { $res.Error = $_ } else { throw }
                    }
                }
                $res
            }

            $Options = $using:Options

            cd $using:package_path
            $out = "$using:tmp_dir\$using:package_name"

            $global:au_Timeout = $Options.Timeout
            $global:au_Force   = $Options.Force
            $global:au_WhatIf  = $Options.WhatIf
            $global:au_Result  = 'pkg'
            $global:au_NoCheckChocoVersion = $Options.NoCheckChocoVersion

            if ($Options.BeforeEach) {
                $s = [Scriptblock]::Create( $Options.BeforeEach )
                . $s $using:package_name $Options
            }
            
            $pkg = repeat_ignore { ./update.ps1 }
            if (!$pkg) { throw "'$using:package_name' update script returned nothing" }
            if (($pkg -eq 'ignore') -or ($pkg[-1] -eq 'ignore')) { return 'ignore' }

            $pkg  = $pkg[-1]
            $type = $pkg.GetType()
            if ( "$type" -ne 'AUPackage') { throw "'$using:package_name' update script didn't return AUPackage but: $type" }

            if ($pkg.Updated -and $Options.Push) {
                $res = repeat_ignore { 
                    $r = Push-Package -All:$Options.PushAll
                    if ($LastExitCode -eq 0) { return $r } else { throw $r }
                }
                if (($res -eq 'ignore') -or ($res[-1] -eq 'ignore')) { return 'ignore' }

                if ($res -is [System.Management.Automation.ErrorRecord]) {
                    $pkg.Error = "Push ERROR`n" + $res
                } else {
                    $pkg.Pushed = $true 
                    $pkg.Result += $res 
                } 
            }
            
            if ($Options.AfterEach) {
                $s = [Scriptblock]::Create( $Options.AfterEach )
                . $s $using:package_name $Options
            }

            $pkg.Serialize()
        } | Out-Null
    }
    $result = $result | sort Name

    $info = get_info
    run_plugins

    if ($Options.Script) { try { & $Options.Script 'END' $info | Write-Host } catch { Write-Error $_; $script_err += 1 } }

    @('') + $info.stats + '' | Write-Host

    $result
}

function run_plugins() {
    if ($NoPlugins) { return }

    rm -Force -Recurse $tmp_dir\plugins -ea ig
    mkdir -Force $tmp_dir\plugins | Out-Null
    foreach ($key in $Options.Keys) {
        $params = $Options.$key
        if ($params -isnot [HashTable]) { continue }

        $plugin_path = "$PSScriptRoot/../Plugins/$key.ps1"
        if (!(Test-Path $plugin_path)) {
            if([string]::IsNullOrWhiteSpace($Options.PluginPath)) { continue }

            $plugin_path = $Options.PluginPath + "/$key.ps1"
            if(!(Test-Path $plugin_path)) { continue }
        }

        try {
            Write-Host "`nRunning $key"
            & $plugin_path $Info @params *>&1 | tee $tmp_dir\plugins\$key | Write-Host
            $info.plugin_results.$key += gc $tmp_dir\plugins\$key -ea ig
        } catch {
            $err_lines = $_.ToString() -split "`n"
            Write-Host "  ERROR: " $(foreach ($line in $err_lines) { "`n" + ' '*4 + $line })
            $info.plugin_errors.$key = $_.ToString()
        }
    }
}


function get_info {
    $errors = $result | ? { $_.Error }
    $info = [PSCustomObject]@{
        result = [PSCustomObject]@{
            all     = $result
            ignored = $result | ? Ignored
            errors  = $errors
            ok      = $result | ? { !$_.Error }
            pushed  = $result | ? Pushed
            updated = $result | ? Updated
        }

        error_count = [PSCustomObject]@{
            update  = $errors | ? {!$_.Updated} | measure | % count
            push    = $errors | ? {$_.Updated -and !$_.Pushed} | measure | % count
            total   = $errors | measure | % count
        }
        error_info  = ''

        packages  = $aup
        startTime = $startTime
        minutes   = ((Get-Date) - $startTime).TotalMinutes.ToString('#.##')
        pushed    = $result | ? Pushed  | measure | % count
        updated   = $result | ? Updated | measure | % count
        ignored   = $result | ? Ignored | measure | % count
        stats     = ''
        options   = $Options
        plugin_results = @{}
        plugin_errors = @{}
    }
    $info.PSObject.TypeNames.Insert(0, 'AUInfo')

    $info.stats = get-stats
    $info.error_info = $errors | % {
        "`nPackage: " + $_.Name + "`n"
        $_.Error
    }

    $info
}

function get-stats {
    "Finished {0} packages after {1} minutes.  " -f $info.packages.length, $info.minutes
    "{0} updated, {1} pushed, {2} ignored  " -f $info.updated, $info.pushed, $info.ignored
    "{0} errors - {1} update, {2} push.  " -f $info.error_count.total, $info.error_count.update, $info.error_count.push
}


Set-Alias updateall Update-AuPackages
tools\AU\Public\Update-Package.ps1
# Author: Miodrag Milic <[email protected]>
# Last Change: 19-Dec-2016.

<#
.SYNOPSIS
    Update automatic package

.DESCRIPTION
    This function is used to perform necessary updates to the specified files in the package.
    It shouldn't be used on its own but must be part of the script which defines two functions:

    - au_SearchReplace
      The function should return HashTable where keys are file paths and value is another HashTable
      where keys and values are standard search and replace strings
    - au_GetLatest
      Returns the HashTable where the script specifies information about new Version, new URLs and
      any other data. You can refer to this variable as the $Latest in the script.
      While Version is used to determine if updates to the package are needed, other arguments can
      be used in search and replace patterns or for whatever purpose.

    With those 2 functions defined, calling Update-Package will:

    - Call your au_GetLatest function to get the remote version and other information.
    - If remote version is higher then the nuspec version, function will:
        - Check the returned URLs, Versions and Checksums (if defined) for validity (unless NoCheckXXX variables are specified)
        - Download files and calculate checksum(s), (unless already defined or ChecksumFor is set to 'none')
        - Update the nuspec with the latest version
        - Do the necessary file replacements
        - Pack the files into the nuget package

    You can also define au_BeforeUpdate and au_AfterUpdate functions to integrate your code into the update pipeline.
.EXAMPLE
    PS> notepad update.ps1
    # The following script is used to update the package from the github releases page.
    # After it defines the 2 functions, it calls the Update-Package.
    # Checksums are automatically calculated for 32 bit version (the only one in this case)
    import-module au

    function global:au_SearchReplace {
        ".\tools\chocolateyInstall.ps1" = @{
            "(^[$]url32\s*=\s*)('.*')"          = "`$1'$($Latest.URL32)'"
            "(^[$]checksum32\s*=\s*)('.*')"     = "`$1'$($Latest.Checksum32)'"
            "(^[$]checksumType32\s*=\s*)('.*')" = "`$1'$($Latest.ChecksumType32)'"
        }
    }

    function global:au_GetLatest {
        $download_page = Invoke-WebRequest https://github.com/hluk/CopyQ/releases -UseBasicParsing

        $re  = "copyq-.*-setup.exe"
        $url = $download_page.links | ? href -match $re | select -First 1 -expand href
        $version = $url -split '-|.exe' | select -Last 1 -Skip 2

        return @{ URL32 = $url; Version = $version }
    }

    Update-Package -ChecksumFor 32

.NOTES
    All function parameters accept defaults via global variables with prefix `au_` (example: $global:au_Force = $true).

.OUTPUTS
    PSCustomObject with type AUPackage.

.LINK
    Update-AUPackages
#>
function Update-Package {
    [CmdletBinding()]
    param(
        #Do not check URL and version for validity.
        [switch] $NoCheckUrl,

        #Do not check if latest returned version already exists in the Chocolatey community feed.
        #Ignored when Force is specified.
        [switch] $NoCheckChocoVersion,

        #Specify for which architectures to calculate checksum - all, 32 bit, 64 bit or none.
        [ValidateSet('all', '32', '64', 'none')]
        [string] $ChecksumFor='all',

        #Timeout for all web operations, by default 100 seconds.
        [int]    $Timeout,

        #Streams to process, either a string or an array. If ommitted, all streams are processed.
        #Single stream required when Force is specified.
        $IncludeStream,

        #Force package update even if no new version is found.
        #For multi streams packages, most recent stream is checked by default when Force is specified.
        [switch] $Force,

        #Do not show any Write-Host output.
        [switch] $NoHostOutput,

        #Output variable.
        [string] $Result,

        #Backup and restore package.
        [switch] $WhatIf, 

        #Disable automatic update of nuspec description from README.md files with first 2 lines skipped.
        [switch] $NoReadme
    )

    function check_urls() {
        "URL check" | result
        $Latest.Keys | ? {$_ -like 'url*' } | % {
            $url = $Latest[ $_ ]
            if ($res = check_url $url -Options $Latest.Options) { throw "${res}:$url" } else { "  $url" | result }
        }
    }

    function get_checksum()
    {
        function invoke_installer() {
            if (!(Test-Path tools\chocolateyInstall.ps1)) { "  aborted, chocolateyInstall not found for this package" | result; return }

            Import-Module "$choco_tmp_path\helpers\chocolateyInstaller.psm1" -Force -Scope Global

            if ($ChecksumFor -eq 'none') { "Automatic checksum calculation is disabled"; return }
            if ($ChecksumFor -eq 'all')  { $arch = '32','64' } else { $arch = $ChecksumFor }

            $pkg_path = [System.IO.Path]::GetFullPath("$Env:TEMP\chocolatey\$($package.Name)\" + $global:Latest.Version) #https://github.com/majkinetor/au/issues/32
            mkdir -Force $pkg_path | Out-Null

            $Env:ChocolateyPackageName         = "chocolatey\$($package.Name)"
            $Env:ChocolateyPackageVersion      = $global:Latest.Version.ToString()
            $Env:ChocolateyAllowEmptyChecksums = 'true'
            foreach ($a in $arch) {
                $Env:chocolateyForceX86 = if ($a -eq '32') { 'true' } else { '' }
                try {
                    #rm -force -recurse -ea ignore $pkg_path
                    .\tools\chocolateyInstall.ps1 | result
                } catch {
                    if ( "$_" -notlike 'au_break: *') { throw $_ } else {
                        $filePath = "$_" -replace 'au_break: '
                        if (!(Test-Path $filePath)) { throw "Can't find file path to checksum" }

                        $item = gi $filePath
                        $type = if ($global:Latest.ContainsKey('ChecksumType' + $a)) { $global:Latest.Item('ChecksumType' + $a) } else { 'sha256' }
                        $hash = (Get-FileHash $item -Algorithm $type | % Hash).ToLowerInvariant()

                        if (!$global:Latest.ContainsKey('ChecksumType' + $a)) { $global:Latest.Add('ChecksumType' + $a, $type) }
                        if (!$global:Latest.ContainsKey('Checksum' + $a)) {
                            $global:Latest.Add('Checksum' + $a, $hash)
                            "Package downloaded and hash calculated for $a bit version" | result
                        } else {
                            $expected = $global:Latest.Item('Checksum' + $a)
                            if ($hash -ne $expected) { throw "Hash for $a bit version mismatch: actual = '$hash', expected = '$expected'" }
                            "Package downloaded and hash checked for $a bit version" | result
                        }
                    }
                }
            }
        }

        function fix_choco {
            Sleep -Milliseconds (Get-Random 500) #reduce probability multiple updateall threads entering here at the same time (#29)

            # Copy choco modules once a day
            if (Test-Path $choco_tmp_path) {
                $ct = gi $choco_tmp_path | % creationtime
                if (((get-date) - $ct).Days -gt 1) { rm -recurse -force $choco_tmp_path } else { Write-Verbose 'Chocolatey copy is recent, aborting monkey patching'; return }
            }

            Write-Verbose "Monkey patching chocolatey in: '$choco_tmp_path'"
            cp -recurse -force $Env:ChocolateyInstall\helpers $choco_tmp_path\helpers
            if (Test-Path $Env:ChocolateyInstall\extensions) { cp -recurse -force $Env:ChocolateyInstall\extensions $choco_tmp_path\extensions }

            $fun_path = "$choco_tmp_path\helpers\functions\Get-ChocolateyWebFile.ps1"
            (gc $fun_path) -replace '^\s+return \$fileFullPath\s*$', '  throw "au_break: $fileFullPath"' | sc $fun_path -ea ignore
        }

        "Automatic checksum started" | result

        # Copy choco powershell functions to TEMP dir and monkey patch the Get-ChocolateyWebFile function
        $choco_tmp_path = "$Env:TEMP\chocolatey\au\chocolatey"
        fix_choco

        # This will set the new URLs before the files are downloaded but will replace checksums to empty ones so download will not fail
        #  because checksums are at that moment set for the previous version.
        # SkipNuspecFile is passed so that if things fail here, nuspec file isn't updated; otherwise, on next run
        #  AU will think that package is the most recent. 
        #
        # TODO: This will also leaves other then nuspec files updated which is undesired side effect (should be very rare)
        #
        $global:Silent = $true

        $c32 = $global:Latest.Checksum32; $c64 = $global:Latest.Checksum64          #https://github.com/majkinetor/au/issues/36
        $global:Latest.Remove('Checksum32'); $global:Latest.Remove('Checksum64')    #  -||-
        update_files -SkipNuspecFile | out-null
        if ($c32) {$global:Latest.Checksum32 = $c32}
        if ($c64) {$global:Latest.Checksum64 = $c64}                                #https://github.com/majkinetor/au/issues/36

        $global:Silent = $false

        # Invoke installer for each architecture to download files
        invoke_installer
    }

    function process_stream() {
        $package.Updated = $false

        if (!(is_version $package.NuspecVersion)) {
            Write-Warning "Invalid nuspec file Version '$($package.NuspecVersion)' - using 0.0"
            $global:Latest.NuspecVersion = $package.NuspecVersion = '0.0'
        }
        if (!(is_version $Latest.Version)) { throw "Invalid version: $($Latest.Version)" }
        $package.RemoteVersion = $Latest.Version

        # For set_fix_version to work propertly, $Latest.Version's type must be assignable from string.
        # If not, then cast its value to string.
        if (!('1.0' -as $Latest.Version.GetType())) {
            $Latest.Version = [string] $Latest.Version
        }

        if (!$NoCheckUrl) { check_urls }

        "nuspec version: " + $package.NuspecVersion | result
        "remote version: " + $package.RemoteVersion | result

        $script:is_forced = $false
        if ([AUVersion] $Latest.Version -gt [AUVersion] $Latest.NuspecVersion) {
            if (!($NoCheckChocoVersion -or $Force)) {
                if ( !$au_GalleryUrl ) { $au_GalleryUrl = 'https://chocolatey.org' } 
                $choco_url = "$au_GalleryUrl/packages/{0}/{1}" -f $global:Latest.PackageName, $package.RemoteVersion
                try {
                    request $choco_url $Timeout | out-null
                    "New version is available but it already exists in the Chocolatey community feed (disable using `$NoCheckChocoVersion`):`n  $choco_url" | result
                    return
                } catch { }
            }
        } else {
            if (!$Force) {
                'No new version found' | result
                return
            }
            else { 'No new version found, but update is forced' | result; set_fix_version }
        }

        'New version is available' | result

        $match_url = ($Latest.Keys | ? { $_ -match '^URL*' } | select -First 1 | % { $Latest[$_] } | split-Path -Leaf) -match '(?<=\.)[^.]+$'
        if ($match_url -and !$Latest.FileType) { $Latest.FileType = $Matches[0] }

        if ($ChecksumFor -ne 'none') { get_checksum } else { 'Automatic checksum skipped' | result }

        if ($WhatIf) { $package.Backup() }
        try {
            if (Test-Path Function:\au_BeforeUpdate) { 'Running au_BeforeUpdate' | result; au_BeforeUpdate $package | result }
            if (!$NoReadme -and (Test-Path "$($package.Path)\README.md")) { Set-DescriptionFromReadme $package -SkipFirst 2 | result }        
            update_files
            if (Test-Path Function:\au_AfterUpdate) { 'Running au_AfterUpdate' | result; au_AfterUpdate $package | result }
        
            choco pack --limit-output | result
            if ($LastExitCode -ne 0) { throw "Choco pack failed with exit code $LastExitCode" }
        } finally {
            if ($WhatIf) {
                $save_dir = $package.SaveAndRestore() 
                Write-Warning "Package restored and updates saved to: $save_dir"
            }
        }

        $package.Updated = $true
    }

    function set_fix_version() {
        $script:is_forced = $true

        if ($global:au_Version) {
            "Overriding version to: $global:au_Version" | result
            $package.RemoteVersion = $global:au_Version
            if (!(is_version $global:au_Version)) { throw "Invalid version: $global:au_Version" }
            $global:Latest.Version = $package.RemoteVersion
            $global:au_Version = $null
            return
        }

        $date_format = 'yyyyMMdd'
        $d = (get-date).ToString($date_format)
        $nuspecVersion = [AUVersion] $Latest.NuspecVersion
        $v = $nuspecVersion.Version
        $rev = $v.Revision.ToString()
        try { $revdate = [DateTime]::ParseExact($rev, $date_format,[System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::None) } catch {}
        if (($rev -ne -1) -and !$revdate) { return }

        $build = if ($v.Build -eq -1) {0} else {$v.Build}
        $v = [version] ('{0}.{1}.{2}.{3}' -f $v.Major, $v.Minor, $build, $d)
        $package.RemoteVersion = $nuspecVersion.WithVersion($v).ToString()
        $Latest.Version = $package.RemoteVersion -as $Latest.Version.GetType()
    }

    function set_latest( [HashTable] $latest, [string] $version, $stream ) {
        if (!$latest.NuspecVersion) { $latest.NuspecVersion = $version }
        if ($stream -and !$latest.Stream) { $latest.Stream = $stream }
        $package.NuspecVersion = $latest.NuspecVersion

        $global:Latest = $global:au_Latest
        $latest.Keys | % { $global:Latest.Remove($_) }
        $global:Latest += $latest
    }

    function update_files( [switch]$SkipNuspecFile )
    {
        'Updating files' | result
        '  $Latest data:' | result;  ($global:Latest.keys | sort | % { $v=$global:Latest[$_]; "    {0,-25} {1,-12} {2}" -f $_, "($( if ($v) { $v.GetType().Name } ))", $v }) | result

        if (!$SkipNuspecFile) {
            "  $(Split-Path $package.NuspecPath -Leaf)" | result

            "    setting id: $($global:Latest.PackageName)" | result
            $package.NuspecXml.package.metadata.id = $package.Name = $global:Latest.PackageName.ToString()

            $msg = "    updating version: {0} -> {1}" -f $package.NuspecVersion, $package.RemoteVersion
            if ($script:is_forced) {
                if ($package.RemoteVersion -eq $package.NuspecVersion) {
                    $msg = "    version not changed as it already uses 'revision': {0}" -f $package.NuspecVersion
                } else {
                    $msg = "    using Chocolatey fix notation: {0} -> {1}" -f $package.NuspecVersion, $package.RemoteVersion
                }
            }
            $msg | result

            $package.NuspecXml.package.metadata.version = $package.RemoteVersion.ToString()
            $package.SaveNuspec()
            if ($global:Latest.Stream) {
                $package.UpdateStream($global:Latest.Stream, $package.RemoteVersion)
            }
        }

        $sr = au_SearchReplace
        $sr.Keys | % {
            $fileName = $_
            "  $fileName" | result

            # If not specifying UTF8 encoding, then UTF8 without BOM encoded files
            # is detected as ANSI
            $fileContent = gc $fileName -Encoding UTF8
            $sr[ $fileName ].GetEnumerator() | % {
                ('    {0,-35} = {1}' -f $_.name, $_.value) | result
                if (!($fileContent -match $_.name)) { throw "Search pattern not found: '$($_.name)'" }
                $fileContent = $fileContent -replace $_.name, $_.value
            }

            $useBomEncoding = if ($fileName.EndsWith('.ps1')) { $true } else { $false }
            $encoding = New-Object System.Text.UTF8Encoding($useBomEncoding)
            $output = $fileContent | Out-String
            [System.IO.File]::WriteAllText((gi $fileName).FullName, $output, $encoding)
        }
    }

    function result() {
        if ($global:Silent) { return }

        $input | % {
            $package.Result += $_
            if (!$NoHostOutput) { Write-Host $_ }
        }
    }

    if ($PSCmdlet.MyInvocation.ScriptName -eq '') {
        Write-Verbose 'Running outside of the script'
        if (!(Test-Path update.ps1)) { return "Current directory doesn't contain ./update.ps1 script" } else { return ./update.ps1 }
    } else { Write-Verbose 'Running inside the script' }

    # Assign parameters from global variables with the prefix `au_` if they are bound
    (gcm $PSCmdlet.MyInvocation.InvocationName).Parameters.Keys | % {
        if ($PSBoundParameters.Keys -contains $_) { return }
        $value = gv "au_$_" -Scope Global -ea Ignore | % Value
        if ($value -ne $null) {
            sv $_ $value
            Write-Verbose "Parameter $_ set from global variable au_${_}: $value"
        }
    }

    if ($WhatIf) {  Write-Warning "WhatIf passed - package files will not be changed" }

    $package = [AUPackage]::new( $pwd )
    if ($Result) { sv -Scope Global -Name $Result -Value $package }

    $global:Latest = @{PackageName = $package.Name}

    [System.Net.ServicePointManager]::SecurityProtocol = 'Ssl3,Tls,Tls11,Tls12' #https://github.com/chocolatey/chocolatey-coreteampackages/issues/366
    $module = $MyInvocation.MyCommand.ScriptBlock.Module
    "{0} - checking updates using {1} version {2}" -f $package.Name, $module.Name, $module.Version | result
    try {
        $res = au_GetLatest | select -Last 1
        $global:au_Latest = $global:Latest
        if ($res -eq $null) { throw 'au_GetLatest returned nothing' }

        if ($res -eq 'ignore') { return $res }

        $res_type = $res.GetType()
        if ($res_type -ne [HashTable]) { throw "au_GetLatest doesn't return a HashTable result but $res_type" }

        if ($global:au_Force) { $Force = $true }
        if ($global:au_IncludeStream) { $IncludeStream = $global:au_IncludeStream }
    } catch {
        throw "au_GetLatest failed`n$_"
    }

    if ($res.ContainsKey('Streams')) {
        if (!$res.Streams) { throw "au_GetLatest's streams returned nothing" }
        if ($res.Streams -isnot [System.Collections.Specialized.OrderedDictionary] -and $res.Streams -isnot [HashTable]) {
            throw "au_GetLatest doesn't return an OrderedDictionary or HashTable result for streams but $($res.Streams.GetType())"
        }

        # Streams are expected to be sorted starting with the most recent one
        $streams = @($res.Streams.Keys)
        # In case of HashTable (i.e. not sorted), let's sort streams alphabetically descending
        if ($res.Streams -is [HashTable]) { $streams = $streams | sort -Descending }

        if ($IncludeStream) {
            if ($IncludeStream -isnot [string] -and $IncludeStream -isnot [double] -and $IncludeStream -isnot [Array]) {
                throw "`$IncludeStream must be either a String, a Double or an Array but is $($IncludeStream.GetType())"
            }
            if ($IncludeStream -is [double]) { $IncludeStream = $IncludeStream -as [string] }
            if ($IncludeStream -is [string]) { 
                # Forcing type in order to handle case when only one version is included
                [Array] $IncludeStream = $IncludeStream -split ',' | % { $_.Trim() }
            }
        } elseif ($Force) {
            # When forcing update, a single stream is expected
            # By default, we take the first one (i.e. the most recent one)
            $IncludeStream = @($streams | select -First 1)
        }
        if ($Force -and (!$IncludeStream -or $IncludeStream.Length -ne 1)) { throw 'A single stream must be included when forcing package update' }

        if ($IncludeStream) { $streams = @($streams | ? { $_ -in $IncludeStream }) }
        # Let's reverse the order in order to process streams starting with the oldest one
        [Array]::Reverse($streams)

        $res.Keys | ? { $_ -ne 'Streams' } | % { $global:au_Latest.Remove($_) }
        $global:au_Latest += $res

        $allStreams = [System.Collections.Specialized.OrderedDictionary] @{}
        $streams | % {
            $stream = $res.Streams[$_]

            '' | result
            "*** Stream: $_ ***" | result

            if ($stream -eq $null) { throw "au_GetLatest's $_ stream returned nothing" }
            if ($stream -eq 'ignore') { return }
            if ($stream -isnot [HashTable]) { throw "au_GetLatest's $_ stream doesn't return a HashTable result but $($stream.GetType())" }

            if ($package.Streams.$_.NuspecVersion -eq 'ignore') {
                'Ignored' | result
                return
            }

            set_latest $stream $package.Streams.$_.NuspecVersion $_
            process_stream

            $allStreams.$_ = if ($package.Streams.$_) { $package.Streams.$_.Clone() } else { @{} }
            $allStreams.$_.NuspecVersion = $package.NuspecVersion
            $allStreams.$_ += $package.GetStreamDetails()
        }
        $package.Updated = $false
        $package.Streams = $allStreams
        $package.Streams.Values | ? { $_.Updated } | % {
            $package.NuspecVersion = $_.NuspecVersion
            $package.RemoteVersion = $_.RemoteVersion
            $package.Updated = $true
        }
    } else {
        '' | result
        set_latest $res $package.NuspecVersion
        process_stream
    }

    if ($package.Updated) { 'Package updated' | result }

    return $package
}

Set-Alias update Update-Package
tools\chocolateyInstall.ps1
$ErrorActionPreference = 'Stop'

$toolsPath = Split-Path $MyInvocation.MyCommand.Definition
& "$toolsPath/install.ps1"
tools\chocolateyUninstall.ps1
$ErrorActionPreference = 'Stop'

$toolsPath = Split-Path $MyInvocation.MyCommand.Definition
& "$toolsPath/install.ps1" -Remove
tools\install.ps1
#requires -version 2

<#
.SYNOPSIS
    AU install script

.NOTES
    Always install AU versionless in Program Files to support older PowerShell versions ( v < 5 )
    Multiple AU versions can be installed using Install-Module if needed (on Posh 5+).
#>
param(
    #If given it is path to the module to be installed.
    #If not given, use first build directory and if doesn't exist, try scripts folder.
    [string] $module_path,

    #Remove module from the system.
    [switch] $Remove
)

$ErrorActionPreference = 'Stop'

$module_name = 'AU'
$module_dst  = "$Env:ProgramFiles\WindowsPowerShell\Modules"

rm -Force -Recurse "$module_dst\$module_name" -ErrorAction ignore
if ($Remove) { remove-module $module_name -ea ignore; Write-Host "Module $module_name removed"; return }

Write-Host "`n==| Starting $module_name installation`n"

if (!$module_path) {
    if (Test-Path $PSScriptRoot\_build\*) {
        $module_path = (ls $PSScriptRoot\_build\* -ea ignore | sort CreationDate -desc | select -First 1 -Expand FullName) + '/' + $module_name
    } else {
        $module_path = "$PSScriptRoot\$module_name"
        if (!(Test-Path $module_path)) { throw "module_path not specified and scripts directory doesn't contain the module" }
    }
}
$module_path = Resolve-Path $module_path

if (!(Test-Path $module_path)) { throw "Module path invalid: '$module_path'" }

Write-Host "Module path: '$module_path'"

cp -Recurse -Force  $module_path $module_dst

$res = Get-Module $module_name -ListAvailable | ? { (Split-Path $_.ModuleBase) -eq $module_dst }
if (!$res) { throw 'Module installation failed' }

Write-Host "`n$($res.Name) version $($res.Version) installed successfully at '$module_dst\$module_name'"

$functions = $res.ExportedFunctions.Keys

import-module $module_dst\$module_name -force
$aliases = get-alias | ? { $_.Source -eq $module_name }

if ($functions.Length) {
$functions | % {
    [PSCustomObject]@{ Function = $_; Alias = $aliases | ? Definition -eq $_ }
} | % { Write-Host ("`n  {0,-20} {1}`n  --------             -----" -f 'Function', 'Alias') } {
    Write-Host ("  {0,-20} {1}" -f $_.Function, "$($_.Alias)")
}
}

remove-module $module_name
Write-Host "`nTo learn more about ${module_name}:      man about_${module_name}"
Write-Host "See help for any function:   man updateall`n"

In cases where actual malware is found, the packages are subject to removal. Software sometimes has false positives. Moderators do not necessarily validate the safety of the underlying software, only that a package retrieves software from the official distribution point and/or validate embedded software against official distribution point (where distribution rights allow redistribution).

Chocolatey Pro provides runtime protection from possible malware.

Version Downloads Last Updated Status
Chocolatey Automatic Package Updater Module 2018.5.18 2376 Friday, May 18, 2018 Approved
Chocolatey Automatic Package Updater Module 2018.1.11 1099 Thursday, January 11, 2018 Approved
Chocolatey Automatic Package Updater Module 2017.8.30 990 Wednesday, August 30, 2017 Approved
Chocolatey Automatic Package Updater Module 2017.3.29 956 Wednesday, March 29, 2017 Approved
Chocolatey Automatic Package Updater Module 2017.1.14 624 Saturday, January 14, 2017 Approved
Chocolatey Automatic Package Updater Module 2016.12.17 353 Saturday, December 17, 2016 Approved
Chocolatey Automatic Package Updater Module 2016.11.5 388 Saturday, November 5, 2016 Approved
Chocolatey Automatic Package Updater Module 2016.10.30 272 Sunday, October 30, 2016 Approved

This package has no dependencies.

Discussion for the Chocolatey Automatic Package Updater Module Package

Ground Rules:

  • This discussion is only about Chocolatey Automatic Package Updater Module and the Chocolatey Automatic Package Updater Module package. If you have feedback for Chocolatey, please contact the Google Group.
  • This discussion will carry over multiple versions. If you have a comment about a particular version, please note that in your comments.
  • The maintainers of this Chocolatey Package will be notified about new comments that are posted to this Disqus thread, however, it is NOT a guarantee that you will get a response. If you do not hear back from the maintainers after posting a message below, please follow up by using the link on the left side of this page or follow this link to contact maintainers. If you still hear nothing back, please follow the package triage process.
  • Tell us what you love about the package or Chocolatey Automatic Package Updater Module, or tell us what needs improvement.
  • Share your experiences with the package, or extra configuration or gotchas that you've found.
  • If you use a url, the comment will be flagged for moderation until you've been whitelisted. Disqus moderated comments are approved on a weekly schedule if not sooner. It could take between 1-5 days for your comment to show up.
comments powered by Disqus