Creating_Package_Providers
about_Creating_Package_Providers
Short Description
Describes how to create a package provider.
Long Description
A package provider is the way for module authors to extend the AnyPackage
module. Providers can be created in PowerShell or C#. Package providers are implemented by defining a class that inherits from PackageProvider
class and has the PackageProvider
attribute.
Supporting List Available
In order to support Get-PackageProvider -ListAvailable
the module needs to define shipped providers. In the module manifest have the following key:
@{
PrivateData = @{
AnyPackage = @{
Providers = @('ProviderName')
}
}
}
PackageProvider Attribute
The PackageProvider
attribute defines the package provider name. Additional configuration may be added in the future to define optional features.
[PackageProvider("ProviderName")]
PackageByName
The PackageByName
optional property can be set to $false
in order for the provider to indicate that finding/installing/updating packages by package name is not supported. This is useful to set if the provider only supports either by Path
or Uri
. The default value is $true
.
FileExtensions
The FileExtensions
optional property is used to indicate which file types are supported by the package provider when finding/installing/updating packages. To use the value a string array is used including the proceeding dot. For example in PowerShell it would be defined like:
[PackageProvider('Msi', FileExtensions = ('.msi', '.msp')]
UriSchemes
The UriSchemes
optional property is used to indicate which Uri schemes are supported by the package provider when finding/installing/updating packages. For example in PowerShell it would be defined like:
[PackageProvider('MyProvider', UriSchemes = ('http', 'https')]
PackageProvider Class
The PackageProvider
base class serves as the foundation for all package providers.
Constructor
The package provider must have a public parameter-less constructor for AnyPackage
to call.
Initializing
AnyPackage
creates a new instance of the PackageProvider
each time a cmdlet is called. This makes the package provider stateless. If a package provider requires one-time initialization override the Initialize
method.
If your provider needs to maintain state between instances then you can create a class that inherits from PackageProviderInfo
to store state or user accessible information. The derived PackageProviderInfo
will be sent to each instance of the package provider.
[PackageProvider('Test')]
class TestProvider : PackageProvider {
[PackageProviderInfo] Initialize([PackageProviderInfo] $providerInfo) {
return [MyProviderInfo]::new()
}
}
class MyProviderInfo : PackageProviderInfo {
[SqlConnection] $Connection
}
Uninitializing
To perform one-time provider clean-up to free up any resources or connections override the Clean
method.
[PackageProvider('Test')]
class TestProvider : PackageProvider {
[void] Clean() {
# Clean-up logic
}
}
Supported Sources
If the package provider supports finding/updating/installing/saving packages with a source override the IsSource([string] $source)
method. The default implementation is to always return $true
.
In this example, the method defines production
and testing
as supported sources. When an AnyPackage
cmdlet is called it will call the IsSource
method and validate the provider supports the source.
[bool] IsSource([string] $source) {
return $source -in 'production', 'testing'
}
Dynamic Parameters
To add provider specific parameters for a command override the GetDynamicParameters
method. The $commandName
parameter will be one of the cmdlets such as Get-Package
. The method can return $null
, a [RuntimeDefinedParameterDictionary]
object or an object with properties that have [Parameter()]
attribute.
Defining a class with properties is the easiest way to create dynamic parameters. The syntax is very similar to the param()
block in a PowerShell function. There are few notable differences, one being that each property must have a [Parameter()]
attribute in order for the PowerShell runtime to treat it as a parameter. Secondly there is no comma after each parameter.
[PackageProvider('Test')]
class TestProvider : PackageProvider {
[object] GetDynamicParameters([string] $commandName) {
if ($commandName -eq 'Get-Package') {
return [GetPackageDynamicParameters]::new()
} else {
return $null
}
}
}
class GetPackageDynamicParameters {
[Parameter()]
[string] $Path
[Parameter()]
[ScopeType] $Scope
}
Supporting Operations
The package provider indicates support for each individual AnyPackage
cmdlet by adding a corresponding interface. For example, if the package provider supports Get-Package
cmdlet then the IGetPackage
interface would be implemented.
Cmdlet | Interface |
---|---|
Find-Package | IFindPackage |
Get-Package | IGetPackage |
Install-Package | IInstallPackage |
Publish-Package | IPublishPackage |
Save-Package | ISavePackage |
Update-Package | IUpdatePackage |
Uninstall-Package | IUninstallPackage |
Get-PackageSource | IGetSource |
Set-PackageSource | ISetSource |
Register-PackageSource | ISetSource |
Unregister-PackageSource | ISetSource |
If a Package
method was successful WritePackage
must be called with the package details. In the event a package is not found by the provider do not throw an exception as AnyPackage
will write an error.
The package interfaces follow the structure as follows.
[PackageProvider('Test')]
class TestProvider : PackageProvider, IGetPackage {
[void] GetPackage([PackageRequest] $request) { }
}
If a Source
method was successful WriteSource
must be called with the source details. In the event a source is not found by the provider do not throw an exception as AnyPackage
will write an error.
The package source interfaces follow the structure as follows.
[PackageProvider('Test')]
class TestProvider : PackageProvider, IGetSource {
[void] GetPackageSource([SourceRequest] $request) { }
}
The ISetSource
interface is different as it requires three methods compared to the rest.
[PackageProvider('Test')]
class TestProvider : PackageProvider, ISetSource {
[void] SetPackageSource([SourceRequest] $request) { }
[void] RegisterPackageSource([SourceRequest] $request) { }
[void] UnregisterPackageSource([SourceRequest] $request) { }
}
Package Request
The [PackageRequest]
type contains information about the request and methods to interact with AnyPackage
.
class PackageRequest {
[string] $Name
[PackageVersionRange] $Version
[string] $Source
[bool] $Prerelease
[PackageInfo] $Package
[string] $Path
[Uri] $Uri
[object] $DynamicParameters
[PackageProviderInfo] $ProviderInfo
[bool] $Stopping
[bool] IsMatch([string] $name)
[bool] IsMatch([PackageVersion] $version)
[bool] IsMatch([string] $name, [PackageVersion] $version)
[bool] PromptUntrustedSource([string] $source)
[void] WritePackage([PackageInfo] $package)
Source Request
The [SourceRequest]
type contains information about the request and methods to interact with AnyPackage
.
class SourceRequest {
[string] $Name
[string] $Location
[bool] $Trusted
[bool] $Force
[object] $DynamicParameters
[PackageProviderInfo] $ProviderInfo
[bool] $Stopping
[void] WriteSource([PackageSourceInfo] $source)
Register a Package Provider
To register a package provider with AnyPackage
the following method must be called from within your module. In this example the [TestProvider]
is the type that implements the package provider and e5491948-72b3-4f00-aa64-f93060d9b242
is the provider’s unique ID..
[guid] $id = 'e5491948-72b3-4f00-aa64-f93060d9b242'
[PackageProviderManager]::RegisterProvider($id, [TestProvider], $MyInvocation.MyCommand.ScriptBlock.Module)
If you are unable to pass the PSModuleInfo
then the module name can be passed instead.
[guid] $id = 'e5491948-72b3-4f00-aa64-f93060d9b242'
[PackageProviderManager]::RegisterProvider($id, [TestProvider], 'TestModule')
Unregister a Package Provider
To remove a package provider on when the provider module is removed set the OnRemove
property for the module calling the [PackageProviderManager]::UnregisterProvider([Guid] $id)
method.
In the example the following example, the e5491948-72b3-4f00-aa64-f93060d9b242
is the provider’s unique ID.
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
[PackageProviderManager]::UnregisterProvider('e5491948-72b3-4f00-aa64-f93060d9b242')
}
Packages without a Version
If your package provider supports packages without a version special consideration needs to take place. The user may specify all versions to be returned and in that case packages with a null
version should also be returned.
In this example the $request.IsMatch($name)
method is used to filter package names. Then an additional check is used if the $request.Version
is null
or is a *
all version wildcard.
if ($request.IsMatch($name) -and ($null -eq $request.Version -or $request.Version -eq '*') {
# Write package
}
Examples
Basic Provider
The following example is the minimum code required to define a package provider.
using module AnyPackage
using namespace AnyPackage.Provider
[PackageProvider('Test')]
class TestProvider : PackageProvider { }
[guid] $id = 'e5491948-72b3-4f00-aa64-f93060d9b242'
[PackageProviderManager]::RegisterProvider($id, [TestProvider], $MyInvocation.MyCommand.ScriptBlock.Module)
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
[PackageProviderManager]::UnregisterProvider($id)
}
Documenting
The package provider should come with an about topic to describe the provider any capabilities it has and any dynamic parameters.